예제 #1
0
    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
예제 #2
0
    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")
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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()
예제 #6
0
    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
예제 #7
0
    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
예제 #8
0
    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
예제 #9
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
예제 #10
0
    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")
예제 #11
0
    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
예제 #12
0
    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
예제 #13
0
    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
예제 #14
0
    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
예제 #15
0
    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
예제 #16
0
    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()
예제 #17
0
    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)
예제 #18
0
    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