def wait_for_login(self, timeout=None, polling_delta=5): """Block until the user has logged in. If 'timeout' is set then we will wait for a maximum of that number of seconds This will check whether we have logged in by polling the identity service every 'polling_delta' seconds. """ self._check_for_error() if not self.is_logging_in(): return self.is_logged_in() polling_delta = int(polling_delta) if polling_delta > 60: polling_delta = 60 elif polling_delta < 1: polling_delta = 1 import time as _time if timeout is None: # block forever.... while True: self._poll_session_status() if self.is_logged_in(): return True elif not self.is_logging_in(): return False _time.sleep(polling_delta) else: # only block until the timeout has been reached timeout = int(timeout) if timeout < 1: timeout = 1 from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now start_time = _get_datetime_now() while (_get_datetime_now() - start_time).seconds < timeout: self._poll_session_status() if self.is_logged_in(): return True elif not self.is_logging_in(): return False _time.sleep(polling_delta) return False
def set_logged_out(self, authorisation=None, signature=None): """Register that this request has been closed as the user has logged out. If an authorisation is passed then verify that this is correct """ if self.is_null(): raise PermissionError( "You cannot logout from a null LoginSession!") if authorisation is not None: from Acquire.Identity import Authorisation as _Authorisation if not isinstance(authorisation, _Authorisation): raise TypeError("The authorisation must be type Authorisation") authorisation.verify(resource="logout %s" % self.uid()) if authorisation.user_uid() != self.user_uid(): raise PermissionError( "The user '%s' does not have permission to logout " "a session owned by %s" % (authorisation.user_uid(), self.user_uid())) if signature is not None: message = "logout %s" % self.uid() self._pubcert.verify(signature=signature, message=message) from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now self._logout_datetime = _get_datetime_now() self._clear_keys() self._set_status("logged_out")
def seconds_remaining(self, buffer=30): """Return the number of seconds remaining before this OSPar expires. This will return 0 if the OSPar has already expired. To be safe, you should renew PARs if the number of seconds remaining is less than 60. This will subtract 'buffer' seconds from the actual validity to provide a buffer against race conditions (function says this is valid when it is not) Args: buffer (int, default=30): buffer OSPar validity (seconds) Returns: datetime: Seconds remaining on OSPar validity """ if self.is_null(): return 0 from Acquire.ObjectStore import get_datetime_now as _get_datetime_now buffer = float(buffer) if buffer < 0: buffer = 0 now = _get_datetime_now() delta = (self._expires_datetime - now).total_seconds() - buffer if delta < 0: return 0 else: return delta
def fully_unlock(self): """This fully unlocks the mutex, removing all levels of recursion Returns: None """ if self._is_locked == 0: return from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.ObjectStore import get_datetime_now as _get_datetime_now try: holder = _ObjectStore.get_string_object(self._bucket, self._key) except: holder = None if holder == self._lockstring: # we hold the mutex - delete the key _ObjectStore.delete_object(self._bucket, self._key) self._lockstring = None self._is_locked = 0 if self._end_lease < _get_datetime_now(): self._end_lease = None from Acquire.ObjectStore import MutexTimeoutError raise MutexTimeoutError("The lease on this mutex expired before " "this mutex was unlocked!") else: self._end_lease = None
def seconds_since_creation(self): """Return the number of seconds since this request was created """ if self.is_null(): return None from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now delta = _get_datetime_now() - self._request_datetime return delta.total_seconds()
def expired(self): """Return whether or not this lock has expired Returns: bool: True if lock has expired, else False """ if self._is_locked > 0: from Acquire.ObjectStore import get_datetime_now as \ _get_datetime_now return self._end_lease < _get_datetime_now() else: return False
def __init__(self, username=None, public_key=None, public_cert=None, ipaddr=None, hostname=None, login_message=None, scope=None, permissions=None): """Start a new login session for the user with specified username, passing in the additional data needed to request a login """ if public_key is not None: from Acquire.Crypto import PublicKey as _PublicKey if not isinstance(public_key, _PublicKey): raise TypeError("The public key must be of type PublicKey") if not isinstance(public_cert, _PublicKey): raise TypeError("The public certificate must be of " "type PublicKey") if username is None or len(username) == 0: raise PermissionError("You must supply a valid username!") self._username = username self._pubkey = public_key self._pubcert = public_cert from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now from Acquire.ObjectStore import create_uuid as _create_uuid self._uid = _create_uuid() self._request_datetime = _get_datetime_now() self._status = None self._ipaddr = ipaddr self._hostname = hostname self._login_message = login_message self._scope = scope self._permissions = permissions # make sure this session is saved to the object store self._set_status("pending") else: self._uid = None
def seconds_remaining_on_lease(self): """Return the number of seconds remaining on this lease. You must unlock the mutex before the lease expires, or else an exception will be raised when you unlock, and you will likely have a race condition Returns: datetime: Time remaining on lease """ if self.is_locked(): from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now now = _get_datetime_now() if self._end_lease > now: return (self._end_lease - now).seconds else: return 0 else: return 0
def is_stale(self, stale_time=7200): """Return whether or not this authorisation is stale. 'stale_time' is the number of seconds after which the authorisation is considered stale (and thus no longer valid) """ stale_time = self._fix_integer(stale_time, 365 * 24 * 7200) from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now now = _get_datetime_now() if now >= self._auth_datetime: return ((now - self._auth_datetime).seconds > stale_time) else: # datetime returns large positive numbers if time is # in the future - expect a little difference if client # clock is fast. Give up to 10 seconds of leeway return (self._auth_datetime - now).seconds > 10
def set_approved(self, user_uid=None, device_uid=None): """Register that this request has been approved, optionally providing data about the user who approved the session and the device from which the session was approved """ if self.is_null(): raise PermissionError("You cannot approve a null LoginSession!") if self.status() != "pending": from Acquire.Identity import LoginSessionError raise LoginSessionError( "You cannot approve a login session " "that is not in the 'unapproved' state. This login " "session is in the '%s' state." % self.status()) from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now self._login_datetime = _get_datetime_now() self._user_uid = user_uid self._device_uid = device_uid self._set_status("approved")
def is_verified(self, refresh_time=3600, stale_time=7200): """Return whether or not this authorisation has been verified. Note that this will cache any verification for 'refresh_time' (in seconds) 'stale_time' gives the time (in seconds) beyond which the authorisation will be considered stale (and thus not valid). By default this is 7200 seconds (2 hours), meaning that the authorisation must be used within 2 hours to be valid. """ refresh_time = self._fix_integer(refresh_time, 24 * 3600) from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now now = _get_datetime_now() if self._last_validated_datetime is not None: if (now - self._last_validated_datetime).seconds < refresh_time: # no need to re-validate return not self.is_stale(stale_time) return False
def create_par(bucket, encrypt_key, key=None, readable=True, writeable=False, duration=3600, cleanup_function=None): """Create a pre-authenticated request for the passed bucket and key (if key is None then the request is for the entire bucket). This will return a OSPar object that will contain a URL that can be used to access the object/bucket. If writeable is true, then the URL will also allow the object/bucket to be written to. PARs are time-limited. Set the lifetime in seconds by passing in 'duration' (by default this is one hour) Args: bucket (dict): Bucket to create OSPar for encrypt_key (PublicKey): Public key to encrypt PAR key (str, default=None): Key readable (bool, default=True): If bucket is readable writeable (bool, default=False): If bucket is writeable duration (int, default=3600): Duration OSPar should be valid for in seconds cleanup_function (function, default=None): Cleanup function to be passed to PARRegistry Returns: OSPar: Pre-authenticated request for the bucket """ from Acquire.Crypto import PublicKey as _PublicKey if not isinstance(encrypt_key, _PublicKey): from Acquire.Client import PARError raise PARError( "You must supply a valid PublicKey to encrypt the " "returned OSPar") # get the UTC datetime when this OSPar should expire from Acquire.ObjectStore import get_datetime_now as _get_datetime_now expires_datetime = _get_datetime_now() + \ _datetime.timedelta(seconds=duration) is_bucket = (key is None) # Limitation of OCI - cannot have a bucket OSPar with # read permissions! if is_bucket and readable: from Acquire.Client import PARError raise PARError( "You cannot create a Bucket OSPar that has read permissions " "due to a limitation in the underlying platform") try: from oci.object_storage.models import \ CreatePreauthenticatedRequestDetails as \ _CreatePreauthenticatedRequestDetails except: raise ImportError( "Cannot import OCI. Please install OCI, e.g. via " "'pip install oci' so that you can connect to the " "Oracle Cloud Infrastructure") oci_par = None request = _CreatePreauthenticatedRequestDetails() if is_bucket: request.access_type = "AnyObjectWrite" elif readable and writeable: request.access_type = "ObjectReadWrite" elif readable: request.access_type = "ObjectRead" elif writeable: request.access_type = "ObjectWrite" else: from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError( "Unsupported permissions model for OSPar!") request.name = str(_uuid.uuid4()) if not is_bucket: request.object_name = _clean_key(key) request.time_expires = expires_datetime client = bucket["client"] try: response = client.create_preauthenticated_request( client.get_namespace().data, bucket["bucket_name"], request) except Exception as e: # couldn't create the preauthenticated request from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError( "Unable to create the OSPar '%s': %s" % (str(request), str(e))) if response.status != 200: from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError( "Unable to create the OSPar '%s': Status %s, Error %s" % (str(request), response.status, str(response.data))) oci_par = response.data if oci_par is None: from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError( "Unable to create the preauthenticated request!") created_datetime = oci_par.time_created.replace( tzinfo=_datetime.timezone.utc) expires_datetime = oci_par.time_expires.replace( tzinfo=_datetime.timezone.utc) # the URI returned by OCI does not include the server. We need # to get the server based on the region of this bucket url = _get_object_url_for_region(bucket["region"], oci_par.access_uri) # get the checksum for this URL - used to validate the close # request from Acquire.ObjectStore import OSPar as _OSPar from Acquire.ObjectStore import OSParRegistry as _OSParRegistry url_checksum = _OSPar.checksum(url) driver_details = {"driver": "oci", "bucket": bucket["bucket_name"], "created_datetime": created_datetime, "par_id": oci_par.id, "par_name": oci_par.name} par = _OSPar(url=url, encrypt_key=encrypt_key, key=oci_par.object_name, expires_datetime=expires_datetime, is_readable=readable, is_writeable=writeable, driver_details=driver_details) _OSParRegistry.register(par=par, url_checksum=url_checksum, details_function=_get_driver_details_from_par, cleanup_function=cleanup_function) return par
def create_par(bucket, encrypt_key, key=None, readable=True, writeable=False, duration=3600, cleanup_function=None): """Create a pre-authenticated request for the passed bucket and key (if key is None then the request is for the entire bucket). This will return a PAR object that will contain a URL that can be used to access the object/bucket. If writeable is true, then the URL will also allow the object/bucket to be written to. PARs are time-limited. Set the lifetime in seconds by passing in 'duration' (by default this is one hour). Note that you must pass in a public key that will be used to encrypt this PAR. This is necessary as the PAR grants access to anyone who can decrypt the URL """ from Acquire.Crypto import PublicKey as _PublicKey if not isinstance(encrypt_key, _PublicKey): from Acquire.Client import PARError raise PARError("You must supply a valid PublicKey to encrypt the " "returned PAR") if key is not None: if not _os.path.exists("%s/%s._data" % (bucket, key)): from Acquire.Client import PARError raise PARError( "The object '%s' in bucket '%s' does not exist!" % (key, bucket)) elif not _os.path.exists(bucket): from Acquire.Client import PARError raise PARError("The bucket '%s' does not exist!" % bucket) url = "file://%s" % bucket if key: url = "%s/%s" % (url, key) # get the time this PAR was created from Acquire.ObjectStore import get_datetime_now as _get_datetime_now created_datetime = _get_datetime_now() # get the UTC datetime when this PAR should expire expires_datetime = created_datetime + \ _datetime.timedelta(seconds=duration) # mimic limitations of OCI - cannot have a bucket PAR with # read permissions! if (key is None) and readable: from Acquire.Client import PARError raise PARError( "You cannot create a Bucket PAR that has read permissions " "due to a limitation in the underlying platform") from Acquire.ObjectStore import OSPar as _OSPar from Acquire.ObjectStore import OSParRegistry as _OSParRegistry url_checksum = _OSPar.checksum(url) driver_details = { "driver": "testing_objstore", "bucket": bucket, "created_datetime": created_datetime } par = _OSPar(url=url, key=key, encrypt_key=encrypt_key, expires_datetime=expires_datetime, is_readable=readable, is_writeable=writeable, driver_details=driver_details) _OSParRegistry.register(par=par, url_checksum=url_checksum, details_function=_get_driver_details_from_par, cleanup_function=cleanup_function) return par
def read(self, spend, resource, receipt_by): """Read the cheque - this will read the cheque to return the decrypted contents. This will only work if this function is called on the accounting service that will cash the cheque, if the signature on the cheque matches the service that is authorised to cash the cheque, and if the passed resource matches the resource encoded in the cheque. If this is all correct, then the returned dictionary will contain; {"recipient_url": The URL of the service which was sent the cheque, "recipient_key_fingerprint": Verified fingerprint of the service key that signed this cheque "spend": The amount authorised by this cheque, "uid": The unique ID for this cheque, "resource": String that identifies the resource this cheque will be used to pay for, "account_uid": UID of the account from which funds will be drawn "authorisation" : Verified authorisation from the user who says they own the account for the spend "receipt_by" : Time when we must receipt the cheque, or we will lose the money } You must pass in the spend you want to draw from the cheque, a string representing the resource this cheque will be used to pay for, and the time by which you promise to receipt the cheque after cashing Args: spend (Decimal): Amount authorised by cheque resource (str): Resource to pay for receipt_by (datetime): Time cheque must be receipted by Returns: dict: Dictionary described above """ if self._cheque is None: raise PaymentError("You cannot read a null cheque") from Acquire.ObjectStore import string_to_decimal \ as _string_to_decimal from Acquire.ObjectStore import string_to_datetime \ as _string_to_datetime from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.Service import get_this_service as _get_this_service spend = _string_to_decimal(spend) resource = str(resource) receipt_by = _string_to_datetime(receipt_by) service = _get_this_service(need_private_access=True) # get the cheque data - this may have been signed try: cheque_data = _json.loads(self._cheque["signed_data"]) except: cheque_data = self._cheque # decrypt the cheque's data - only possible on the accounting service cheque_data = service.decrypt_data(cheque_data) # the date comprises the user-authorisation that acts as a # signature that the user wrote this cheque, and the info # for the cheque to say how it is valid from Acquire.Identity import Authorisation as _Authorisation auth = _Authorisation.from_data(cheque_data["authorisation"]) info = cheque_data["info"] # the json.dumps version is the resource used to verify # the above authorisation auth_resource = info # validate that the user authorised this cheque try: auth.verify(resource=info) except Exception as e: raise PaymentError( "The user's signature/authorisation for this cheque " "is not valid! ERROR: %s" % str(e)) info = _json.loads(info) # the user signed this cheque :-) info["authorisation"] = auth # check the signature if one was needed try: recipient_url = info["recipient_url"] except: recipient_url = None if recipient_url: # the recipient was specified - verify that we trust # the recipient, and that they have signed the cheque recipient_service = service.get_trusted_service( service_url=recipient_url) recipient_service.verify_data(self._cheque) info["recipient_key_fingerprint"] = self._cheque["fingerprint"] # validate that the item signature is correct try: cheque_resource = info["resource"] except: cheque_resource = None if cheque_resource is not None: if resource != resource: raise PaymentError( "Disagreement over the resource for which " "this cheque has been signed") info["resource"] = resource info["auth_resource"] = auth_resource try: max_spend = info["max_spend"] del info["max_spend"] except: max_spend = None if max_spend is not None: max_spend = _string_to_decimal(max_spend) if max_spend < spend: raise PaymentError( "The requested spend (%s) exceeds the authorised " "maximum value of the cheque" % (spend)) info["spend"] = spend try: expiry_date = info["expiry_date"] del expiry_date["expiry_date"] except: expiry_date = None if expiry_date is not None: expiry_date = _string_to_datetime(expiry_date) # validate that the cheque will not have expired # when we receipt it from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now now = _get_datetime_now() if now > receipt_by: raise PaymentError( "The time when you promised to receipt the cheque " "has already passed!") if receipt_by > expiry_date: raise PaymentError( "The cheque will have expired after you plan to " "receipt it!: %s versus %s" % (_datetime_to_string(receipt_by), _datetime_to_string(expiry_date))) info["receipt_by"] = receipt_by return info
def verify(self, resource=None, refresh_time=3600, stale_time=7200, force=False, accept_partial_match=False, scope=None, permissions=None, return_identifiers=True): """Verify that this is a valid authorisation provided by the user for the passed 'resource'. This will cache the verification for 'refresh_time' (in seconds), but re-verification can be forced if 'force' is True. 'stale_time' gives the time (in seconds) beyond which the authorisation will be considered stale (and thus not valid). By default this is 7200 seconds (2 hours), meaning that the authorisation must be used within 2 hours to be valid. If 'accept_partial_match' is True, then if this Authorisation has been previously validated, then this previous authorisation is valid if the previously-verified resource contains 'resource', e.g. if you have previously verified that "create ABC123" is the verified resource, then this will still verify if "ABC123" if the partially-accepted match If 'scope' is passed, then verify that the user logged in and signed the authorisation with the required 'scope'. If 'permissions' is passed, then verify that the user logged in and signed the authorisation with at least the specified 'permissions' If 'testing_key' is passed, then this object is being tested as part of the unit tests If the authorisation was verified, then if 'return_identifiers' is True then this will return the full set of identifiers associated with the user who provided the authorisation """ if self.is_null(): raise PermissionError("Cannot verify a null Authorisation") if self.is_stale(stale_time): now = _get_datetime_now() if now < self._auth_datetime: raise PermissionError( "Cannot verify an Authorisation signed " "in the future - please check your clock") else: raise PermissionError("Cannot verify a stale Authorisation") matched_resource = False try: last_resource = self._last_verified_resource except: last_resource = None if last_resource is not None: if accept_partial_match: if resource is None: matched_resource = True else: matched_resource = (last_resource.find(resource) != -1) else: matched_resource = (resource == last_resource) if not force: if self.is_verified(refresh_time=refresh_time, stale_time=stale_time): if matched_resource: if return_identifiers: return self.identifiers() else: return # Now validate that the signature of the UID is correct public_cert = self._get_user_public_cert(scope=scope, permissions=permissions) message = self._get_message(resource=resource, matched_resource=matched_resource) try: public_cert.verify(self._signature, message) except: raise PermissionError( "Cannot verify the authorisation as the signature " "for resource '%s' is invalid!" % resource) from Acquire.ObjectStore import get_datetime_now as _get_datetime_now self._last_validated_datetime = _get_datetime_now() self._last_verified_resource = resource self._last_verified_key = public_cert if return_identifiers: return self.identifiers() else: return
def __init__(self, resource=None, user=None, testing_key=None, testing_user_guid=None): """Create an authorisation for the passed resource that is authorised by the passed user (who must be authenticated) If testing_key is passed, then this authorisation is being tested as part of the unit tests """ if resource is not None: resource = str(resource) self._signature = None self._last_validated_datetime = None self._scope = None self._permissions = None self._pubcert = None if resource is not None: if user is None and testing_key is None: raise ValueError( "You must pass in an authenticated user who will " "provide authorisation for resource '%s'" % resource) from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now from Acquire.ObjectStore import create_uuid as _create_uuid if user is not None: from Acquire.Client import User as _User if not isinstance(user, _User): raise TypeError("The passed user must be of type User") elif not user.is_logged_in(): raise PermissionError( "The passed user '%s' must be authenticated to enable " "you to generate an authorisation for the account") self._user_uid = user.uid() self._session_uid = user.session_uid() self._identity_url = user.identity_service().canonical_url() self._identity_uid = user.identity_service_uid() self._auth_datetime = _get_datetime_now() self._uid = _create_uuid(short_uid=True, include_date=self._auth_datetime) self._siguid = user.signing_key().sign(self._uid) message = self._get_message(resource) self._signature = user.signing_key().sign(message) self._last_validated_datetime = _get_datetime_now() self._last_verified_resource = resource self._last_verified_key = None if user.guid() != self.user_guid(): # interesting future case when we allow individual users # to be identified by multiple identity services... raise PermissionError( "We do not yet support a single user being identified " "by multiple identity services: %s versus %s" % (user.guid(), self.user_guid())) elif testing_key is not None: self._user_uid = "some user uid" self._session_uid = "some session uid" self._identity_url = "some identity_url" self._identity_uid = "some identity uid" self._auth_datetime = _get_datetime_now() self._uid = _create_uuid(short_uid=True, include_date=self._auth_datetime) self._is_testing = True self._testing_key = testing_key if testing_user_guid is not None: parts = testing_user_guid.split("@") self._user_uid = parts[0] self._identity_uid = parts[1] message = self._get_message(resource) self._signature = testing_key.sign(message) self._siguid = testing_key.sign(self._uid) self._last_validated_datetime = _get_datetime_now() self._last_verified_resource = resource self._last_verified_key = testing_key.public_key()
def lock(self, timeout=None, lease_time=None): """Lock the mutex, blocking until the mutex is held, or until 'timeout' seconds have passed. If we time out, then an exception is raised. The lock is held for a maximum of 'lease_time' seconds. Args: timeout (int): Number of seconds to block lease_time (int): Number of seconds to hold the lock Returns: None """ # if the user does not provide a timeout, then we will set a timeout # to 10 seconds if timeout is None: timeout = 10.0 else: timeout = float(timeout) # if the user does not provide a lease_time, then we will set a # default of only 10 seconds if lease_time is None: lease_time = 10.0 else: lease_time = float(lease_time) from Acquire.ObjectStore import get_datetime_now as _get_datetime_now from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.ObjectStore import string_to_datetime \ as _string_to_datetime from Acquire.ObjectStore import ObjectStore as _ObjectStore if self.is_locked(): # renew the lease - if there is less than a second remaining # on the lease then unlock and then lock again from scratch now = _get_datetime_now() if (now > self._end_lease) or (now - self._end_lease).seconds < 1: self.fully_unlock() self.lock(timeout, lease_time) else: self._end_lease = now + _datetime.timedelta(seconds=lease_time) self._lockstring = "%s{}%s" % ( self._secret, _datetime_to_string(self._end_lease)) _ObjectStore.set_string_object(self._bucket, self._key, self._lockstring) self._is_locked += 1 return now = _get_datetime_now() endtime = now + _datetime.timedelta(seconds=timeout) # This is the first time we are trying to get a lock while now < endtime: # does anyone else hold the lock? try: holder = _ObjectStore.get_string_object( self._bucket, self._key) except: holder = None is_held = True if holder is None: is_held = False else: end_lease = _string_to_datetime(holder.split("{}")[-1]) if now > end_lease: # the lease from the other holder has expired :-) is_held = False if not is_held: # no-one holds this mutex - try to hold it now self._end_lease = now + _datetime.timedelta(seconds=lease_time) self._lockstring = "%s{}%s" % ( self._secret, _datetime_to_string(self._end_lease)) _ObjectStore.set_string_object(self._bucket, self._key, self._lockstring) holder = _ObjectStore.get_string_object( self._bucket, self._key) else: self._lockstring = None if holder == self._lockstring: # it looks like we are the holder - read and write again # just to make sure holder = _ObjectStore.get_string_object( self._bucket, self._key) if holder == self._lockstring: # write again just to make sure _ObjectStore.set_string_object(self._bucket, self._key, self._lockstring) holder = _ObjectStore.get_string_object( self._bucket, self._key) if holder == self._lockstring: # we have read and written our secret to the object store # three times. While a race condition is still possible, # I'd hope it is now highly unlikely - we now hold the mutex self._is_locked = 1 return # only try the lock 4 times a second _time.sleep(0.25) now = _get_datetime_now() from Acquire.ObjectStore import MutexTimeoutError raise MutexTimeoutError("Cannot acquire a mutex lock on the " "key '%s'" % self._key)
def create_par(bucket, encrypt_key, key=None, readable=True, writeable=False, duration=3600, cleanup_function=None): """Create a pre-authenticated request for the passed bucket and key (if key is None then the request is for the entire bucket). This will return a OSPar object that will contain a URL that can be used to access the object/bucket. If writeable is true, then the URL will also allow the object/bucket to be written to. PARs are time-limited. Set the lifetime in seconds by passing in 'duration' (by default this is one hour) Args: bucket (dict): Bucket to create OSPar for encrypt_key (PublicKey): Public key to encrypt PAR key (str, default=None): Key readable (bool, default=True): If bucket is readable writeable (bool, default=False): If bucket is writeable duration (int, default=3600): Duration OSPar should be valid for in seconds cleanup_function (function, default=None): Cleanup function to be passed to PARRegistry Returns: OSPar: Pre-authenticated request for the bucket """ from Acquire.Crypto import PublicKey as _PublicKey if not isinstance(encrypt_key, _PublicKey): from Acquire.Client import PARError raise PARError("You must supply a valid PublicKey to encrypt the " "returned OSPar") is_bucket = (key is None) if writeable: method = "PUT" elif readable: method = "GET" else: from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError("Unsupported permissions model for OSPar!") try: # get the UTC datetime when this OSPar should expire from Acquire.ObjectStore import get_datetime_now as _get_datetime_now created_datetime = _get_datetime_now() expires_datetime = _get_datetime_now() + _datetime.timedelta( seconds=duration) bucket_obj = bucket["bucket"] if is_bucket: url = bucket_obj.generate_signed_url( version='v4', expiration=expires_datetime, method=method) else: blob = bucket_obj.blob(key) url = blob.generate_signed_url(version='v4', expiration=expires_datetime, method=method) except Exception as e: # couldn't create the preauthenticated request from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError("Unable to create the OSPar '%s': %s" % (key, str(e))) if url is None: from Acquire.ObjectStore import ObjectStoreError raise ObjectStoreError("Unable to create the signed URL!") # get the checksum for this URL - used to validate the close # request from Acquire.ObjectStore import OSPar as _OSPar from Acquire.ObjectStore import OSParRegistry as _OSParRegistry url_checksum = _OSPar.checksum(url) bucket_name = bucket["bucket_name"] driver_details = { "driver": "gcp", "bucket": bucket_name, "created_datetime": created_datetime } par = _OSPar(url=url, encrypt_key=encrypt_key, key=key, expires_datetime=expires_datetime, is_readable=readable, is_writeable=writeable, driver_details=driver_details) _OSParRegistry.register(par=par, url_checksum=url_checksum, details_function=_get_driver_details_from_par, cleanup_function=cleanup_function) return par