Example #1
0
    def __init__(self):
        """Constructor"""
        self._bucket = None

        from Acquire.Service import get_this_service as _get_this_service
        self._registry_uid = _get_this_service(
                                need_private_access=False).uid()
Example #2
0
def _validate_file_upload(par, file_bucket, file_key, objsize, checksum):
    """Call this function to signify that the file associated with
       this PAR has been uploaded. This will check that the
       objsize and checksum match with what was promised
    """
    from Acquire.ObjectStore import ObjectStore as _ObjectStore
    from Acquire.Service import get_service_account_bucket \
        as _get_service_account_bucket
    from Acquire.Service import get_this_service as _get_this_service

    service = _get_this_service()
    bucket = _get_service_account_bucket()

    file_bucket = _ObjectStore.get_bucket(bucket=bucket,
                                          bucket_name=file_bucket,
                                          create_if_needed=True)

    # check that the file uploaded matches what was promised
    (real_objsize, real_checksum) = _ObjectStore.get_size_and_checksum(
        file_bucket, file_key)

    if real_objsize != objsize or real_checksum != checksum:
        # probably should delete the broken object here...

        from Acquire.Storage import FileValidationError
        raise FileValidationError(
            "The file uploaded does not match what was promised. "
            "size: %s versus %s, checksum: %s versus %s. Please try "
            "to upload the file again." %
            (real_objsize, objsize, real_checksum, checksum))
Example #3
0
    def __init__(self):
        """Construct a wallet to hold all user credentials"""
        from Acquire.Service import is_running_service \
            as _is_running_service

        if _is_running_service():
            from Acquire.Service import get_this_service \
                as _get_this_service
            service = _get_this_service(need_private_access=False)
            raise PermissionError(
                "You cannot open a Wallet on a running Service (%s)" %
                service)

        self._wallet_key = None
        self._service_info = {}

        import os as _os

        wallet_dir = _get_wallet_dir()

        if not _os.path.exists(wallet_dir):
            _os.makedirs(wallet_dir, mode=0o700, exist_ok=False)
        elif not _os.path.isdir(wallet_dir):
            raise TypeError("The wallet directory must be a directory!")

        self._wallet_dir = wallet_dir
Example #4
0
    def submit(worksheet_uid, request, par, secret, cheque):
        """Submit a job which has;
           worksheet_uid -

        """
        from Acquire.Service import get_this_service as _get_this_service
        from Acquire.Access import RunRequest as _RunRequest
        from Acquire.Client import PAR as _PAR
        from Acquire.Client import Cheque as _Cheque
        from Acquire.ObjectStore import create_uid as _create_uid
        from Acquire.Compute import Cluster as _Cluster

        if not isinstance(request, _RunRequest):
            raise TypeError("The request must be type RunRequest")

        if not isinstance(par, _PAR):
            raise TypeError("The PAR must be type PAR")

        if not isinstance(cheque, _Cheque):
            raise TypeError("The cheque must be type Cheque")

        service = _get_this_service(need_private_access=True)
        cluster = _Cluster.get_cluster()

        job = ComputeJob()

        job._secret = cluster.encrypt_data(service.decrypt_data(secret))
        job._par = par
        job._request = request

        cost = 10  # TODO - calculate cost of job again from request

        try:
            credit_notes = cheque.cash(spend=cost,
                                       resource="work %s" % worksheet_uid)
        except Exception as e:
            from Acquire.Service import exception_to_string
            from Acquire.Accounting import PaymentError
            raise PaymentError(
                "Problem cashing the cheque used to pay for the calculation: "
                "\n\nCAUSE: %s" % exception_to_string(e))

        if credit_notes is None or len(credit_notes) == 0:
            from Acquire.Accounting import PaymentError
            raise PaymentError("Cannot be paid!")

        job._credit_notes = credit_notes

        job._uid = _create_uid(include_date=True,
                               short_uid=True,
                               separator="/")

        job.save()

        # signal server running on cluster to fetch job and actually
        # submit to slurm
        cluster.submit_job(job._uid)

        return job
Example #5
0
 def compute_service(self):
     """Return the compute service from which this cluster is managed"""
     if self.is_null():
         return None
     elif Cluster._is_running_service():
         from Acquire.Service import get_this_service as _get_this_service
         return _get_this_service()
     else:
         return self._compute_service
    def hash(username, password, service_uid=None):
        """Return a secure hash of the passed username and password"""
        from Acquire.Crypto import Hash as _Hash
        from Acquire.Service import get_this_service as _get_this_service

        if service_uid is None:
            service_uid = _get_this_service(need_private_access=False).uid()

        result = _Hash.multi_md5(service_uid, username+password)

        return result
Example #7
0
    def compute_service(self):
        """Return the compute service that will be used to actually
           perform the calculation associated with this work

           TODO - will eventually have to choose which compute service
                  to use. Currently returning the compute service that
                  is at the same root URL as this access service
        """
        from Acquire.Service import get_this_service as _get_this_service
        from Acquire.Service import get_trusted_service as _get_trusted_service
        service = _get_this_service()
        compute_url = service.canonical_url().replace("access", "compute")
        return _get_trusted_service(service_url=compute_url)
Example #8
0
    def storage_service(self):
        """Return the storage service that will be used to store
           the output data associated with this work

           TODO - will eventually have to choose which storage service
                  to use. Currently returning the storage service that
                  is at the same root URL as this access service
        """
        from Acquire.Service import get_this_service as _get_this_service
        from Acquire.Service import get_trusted_service as _get_trusted_service
        service = _get_this_service()
        storage_url = service.canonical_url().replace("access", "storage")
        return _get_trusted_service(service_url=storage_url)
Example #9
0
    def login_url(self):
        """Return the login URL to login to this session. This is
           the URL of this identity service plus the
           short UID of the session
        """
        from Acquire.Service import get_this_service as _get_this_service
        service = _get_this_service(need_private_access=False)
        service_uid = service.uid()
        short_uid = self.short_uid()
        short_uid = ".".join(
            [short_uid[i:i + 2] for i in range(0, len(short_uid), 2)])
        # eventually allow the service provider to configure this url
        url = "https://login.acquire-aaai.com"

        return "%s?id=%s/%s" % (url, service_uid, short_uid)
Example #10
0
def get_trusted_services():
    """Return a dictionary of all trusted services indexed by
       their type
    """
    from Acquire.Service import is_running_service as _is_running_service

    if _is_running_service():
        from Acquire.Service import get_this_service as _get_this_service
        from Acquire.Service import Service as _Service

        from Acquire.Service import get_service_account_bucket as \
            _get_service_account_bucket
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.ObjectStore import url_to_encoded as \
            _url_to_encoded

        # we already trust ourselves
        service = _get_this_service()

        trusted_services = {}
        trusted_services[service.service_type()] = [service]

        bucket = _get_service_account_bucket()

        uidkey = "_trusted/uid/"
        datas = _ObjectStore.get_all_objects(bucket, uidkey)

        for data in datas:
            remote_service = _Service.from_data(data)

            if remote_service.should_refresh_keys():
                # need to update the keys in our copy of the service
                remote_service.refresh_keys()
                key = "%s/%s" % (uidkey, remote_service.uid())
                _ObjectStore.set_object_from_json(bucket, key,
                                                  remote_service.to_data())

            if remote_service.service_type() in datas:
                datas[remote_service.service_type()].append(remote_service)
            else:
                datas[remote_service.service_type()] = [remote_service]

        return datas
    else:
        # this is running on the client
        from Acquire.Client import Wallet as _Wallet
        wallet = _Wallet()
        return wallet.get_services()
Example #11
0
    def set_cluster(cluster, authorisation=None, passphrase=None, user=None):
        """Function used to set the single compute cluster that is
           connected to this compute service. This must be authorised
           by an admin user of this compute service
        """
        if not isinstance(cluster, Cluster):
            raise TypeError("The cluster must be type Cluster")

        resource = "set_cluster %s" % cluster.fingerprint()

        from Acquire.Client import Authorisation as _Authorisation
        if Cluster._is_running_service():
            from Acquire.Service import get_this_service as _get_this_service

            service = _get_this_service(need_private_access=True)

            if authorisation is not None:
                if not isinstance(authorisation, _Authorisation):
                    raise TypeError(
                        "The authorisation must be type Authorisation")

                service.assert_admin_authorised(authorisation, resource)
            else:
                # we are rotating keys, so check the passphrase against
                # the old passphrase
                cluster = Cluster.get_cluster()
                cluster.verify_passphrase(passphrase=passphrase,
                                          resource="set_cluster")

            from Acquire.ObjectStore import ObjectStore as _ObjectStore
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket

            bucket = _get_service_account_bucket()
            key = "compute/cluster"
            _ObjectStore.set_object_from_json(bucket, key, cluster.to_data())
        else:
            authorisation = _Authorisation(user=user, resource=resource)
            compute_service = cluster.compute_service()

            args = {"authorisation": authorisation.to_data(),
                    "cluster": cluster.to_data()}

            compute_service.call_function(function="set_cluster", args=args)
Example #12
0
    def _get_file_bucket(self, filekey=None):
        """Return the bucket that contains the file data for the
           file associated with 'filekey' in this drive
        """
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.Service import get_service_account_bucket \
            as _get_service_account_bucket
        from Acquire.Service import get_this_service as _get_this_service

        service = _get_this_service()
        bucket = _get_service_account_bucket()
        bucket_name = self._get_file_bucketname(filekey=filekey)

        try:
            return _ObjectStore.get_bucket(bucket=bucket,
                                           bucket_name=bucket_name,
                                           create_if_needed=True)
        except Exception as e:
            from Acquire.ObjectStore import RequestBucketError
            raise RequestBucketError("Unable to open the bucket '%s': %s" %
                                     (bucket_name, str(e)))
Example #13
0
    def _get_metadata_bucket(self):
        """Return the bucket that contains all of the metadata about
           the files for this drive
        """
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.Service import get_service_account_bucket \
            as _get_service_account_bucket
        from Acquire.Service import get_this_service as _get_this_service

        service = _get_this_service()
        bucket = _get_service_account_bucket()
        bucket_name = "user_metadata"

        try:
            return _ObjectStore.get_bucket(bucket=bucket,
                                           bucket_name=bucket_name,
                                           create_if_needed=True)
        except Exception as e:
            from Acquire.ObjectStore import RequestBucketError
            raise RequestBucketError("Unable to open the bucket '%s': %s" %
                                     (bucket_name, str(e)))
Example #14
0
    def accounting_service(self):
        """Return the accounting service that will honour this cheque.
           Note that this will only return the service if it is trusted
           by the service on which this function is called

           Returns:
                Service: Trusted accounting service

        """
        from Acquire.Service import get_this_service as _get_this_service
        service = _get_this_service()
        accounting_service = service.get_trusted_service(
                                            self.accounting_service_url())

        if not accounting_service.is_accounting_service():
            from Acquire.Service import ServiceError
            raise ServiceError(
                "The service that is supposed to honour the cheque (%s) "
                "does not appear to be a valid accounting service" %
                (str(accounting_service)))

        return accounting_service
Example #15
0
    def challenge_service(self, service):
        """Send a challenge to the passed service, returning the actual
           service returned. This will only pass if our copy of the
           service matches us with the copy returned from the actual
           service. This verifies that there is a real service sitting
           at that URL, and that we have the right keys and certs
        """
        from Acquire.Crypto import PrivateKey as _PrivateKey
        from Acquire.ObjectStore import bytes_to_string as _bytes_to_string
        from Acquire.Service import Service as _Service

        challenge = _PrivateKey.random_passphrase()
        pubkey = service.public_key()
        encrypted_challenge = pubkey.encrypt(challenge)

        args = {"challenge": _bytes_to_string(encrypted_challenge),
                "fingerprint": pubkey.fingerprint()}

        if service.uid().startswith("STAGE"):
            # we cannot call using the service directly, as it is
            # not yet fully constructed
            from Acquire.Service import get_this_service as _get_this_service
            from Acquire.Service import call_function as _call_function
            this_service = _get_this_service(need_private_access=True)
            result = _call_function(service_url=service.service_url(),
                                    function=None,
                                    args=args,
                                    args_key=service.public_key(),
                                    response_key=this_service.private_key(),
                                    public_cert=service.public_certificate())
        else:
            result = service.call_function(function=None, args=args)

        if result["response"] != challenge:
            raise PermissionError(
                "Failure of the service %s to correctly respond "
                "to the challenge!" % service)

        return _Service.from_data(result["service_info"])
Example #16
0
    def create(username,
               password,
               _service_uid=None,
               _service_public_key=None):
        """Create a new account with username 'username', which will
           be secured using the passed password.

           Note that this will create an account with a specified
           user UID, meaning that different users can have the same
           username. We identify the right user via the combination
           of username, password and OTP code.

           Normally the UID of the service, and the skeleton key
           used to encrypt the backup password are obtained
           directly from the service. However, when initialising
           a new service we must pass these directly. In those
           cases, pass the object using _service_uid and
           _service_public_key

           This returns a tuple of the user_uid and OTP for the
           newly-created account
        """
        from Acquire.ObjectStore import create_uuid as _create_uuid
        from Acquire.Crypto import PrivateKey as _PrivateKey
        from Acquire.Crypto import PublicKey as _PublicKey
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.Service import get_service_account_bucket \
            as _get_service_account_bucket
        from Acquire.ObjectStore import bytes_to_string as _bytes_to_string
        from Acquire.Identity import UserCredentials as _UserCredentials
        from Acquire.ObjectStore import get_datetime_now_to_string \
            as _get_datetime_now_to_string

        if _service_public_key is None:
            from Acquire.Service import get_this_service as _get_this_service
            service_pubkey = _get_this_service().public_skeleton_key()
            assert (service_pubkey is not None)
        else:
            service_pubkey = _service_public_key

        if not isinstance(service_pubkey, _PublicKey):
            raise TypeError("The service public key must be type PublicKey")

        if _service_uid is None:
            from Acquire.Service import get_this_service \
                as _get_this_service
            service_uid = _get_this_service(need_private_access=False).uid()
        else:
            service_uid = _service_uid

        # create a UID for this new user
        user_uid = _create_uuid()

        # now create the primary password for this user and use
        # this to encrypt the special keys for this user
        privkey = _PrivateKey(name="user_secret_key %s %s" %
                              (username, user_uid))
        primary_password = _PrivateKey.random_passphrase()

        bucket = _get_service_account_bucket()

        # now create the credentials used to validate a login
        otp = _UserCredentials.create(user_uid=user_uid,
                                      password=password,
                                      primary_password=primary_password)

        # create the user account
        user = UserAccount(username=username,
                           user_uid=user_uid,
                           private_key=privkey,
                           status="active")

        # now save a lookup from the username to this user_uid
        # (many users can have the same username). Use this lookup
        # to hold a recovery password for this account
        recovery_password = _bytes_to_string(
            service_pubkey.encrypt(primary_password))

        key = "%s/names/%s/%s" % (_user_root, user.encoded_name(), user_uid)
        _ObjectStore.set_string_object(bucket=bucket,
                                       key=key,
                                       string_data=recovery_password)

        # now save a lookup from the hashed username+password
        # to the user_uid, so that we can
        # quickly find matching user_uids (expect few people will have
        # exactly the same username and password). This will
        # save the exact time this username-password combination
        # was set
        encoded_password = _UserCredentials.hash(username=username,
                                                 password=password,
                                                 service_uid=service_uid)

        key = "%s/passwords/%s/%s" % (_user_root, encoded_password, user_uid)
        _ObjectStore.set_string_object(
            bucket=bucket, key=key, string_data=_get_datetime_now_to_string())

        # finally(!) save the account itself to the object store
        key = "%s/uids/%s" % (_user_root, user_uid)
        data = user.to_data(passphrase=primary_password)
        _ObjectStore.set_object_from_json(bucket=bucket, key=key, data=data)

        # return the OTP and user_uid
        return (user_uid, otp)
Example #17
0
 def login_root_url(self):
     """Return the root URL used to log into this account"""
     from Acquire.Service import get_this_service as _get_this_service
     return _get_this_service().canonical_url()
Example #18
0
    def __init__(self, url=None, key=None,
                 encrypt_key=None,
                 expires_datetime=None,
                 is_readable=False,
                 is_writeable=False,
                 driver_details=None):
        """Construct an OSPar result by passing in the URL at which the
           object can be accessed, the UTC datetime when this expires,
           whether this is readable or writeable, and
           the encryption key to use to encrypt the OSPar.

           You can optionally pass in the key for the object in the
           object store that
           this provides access to. If this is not supplied, then an
           entire bucket is accessed). If 'is_readable', then read-access
           has been granted, while if 'is_writeable' then write
           access has been granted.

           Otherwise no access is possible.

           driver_details is provided by the machinery that creates
           the OSPar, and supplies extra details that are used by the
           driver to create, register and manage OSPars... You should
           not do anything with driver_details yourself
        """
        service_url = None

        if url is None:
            is_readable = True
            self._uid = None
        else:
            from Acquire.Crypto import PublicKey as _PublicKey
            from Acquire.Crypto import PrivateKey as _PrivateKey

            if isinstance(encrypt_key, _PrivateKey):
                encrypt_key = encrypt_key.public_key()

            if not isinstance(encrypt_key, _PublicKey):
                raise TypeError(
                    "You must supply a valid PublicKey to encrypt a OSPar")

            url = encrypt_key.encrypt(url)

            from Acquire.ObjectStore import create_uid as _create_uid
            self._uid = _create_uid()

            try:
                from Acquire.Service import get_this_service \
                    as _get_this_service
                service_url = _get_this_service().canonical_url()
            except:
                pass

        self._url = url
        self._key = key
        self._expires_datetime = expires_datetime
        self._service_url = service_url

        if driver_details is not None:
            if not isinstance(driver_details, dict):
                raise TypeError("The driver details must be a dictionary")

        self._driver_details = driver_details

        if is_readable:
            self._is_readable = True
        else:
            self._is_readable = False

        if is_writeable:
            self._is_writeable = True
        else:
            self._is_writeable = False

        if not (self._is_readable or self._is_writeable):
            from Acquire.Client import PARPermissionsError
            raise PARPermissionsError(
                "You cannot create a OSPar that has no read or write "
                "permissions!")
Example #19
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
Example #20
0
    def cash(self, spend, resource, receipt_within=3600):
        """Cash this cheque, specifying how much to be cashed,
           and the resource that will be paid for
           using this cheque. This will send the cheque to the
           accounting service (if we trust that accounting service).
           The accounting service will check that the cheque is valid,
           and the signature of the item is correct. It will then
           withdraw 'spend' funds from the account that signed the
           cheque, returning valid CreditNote(s) that can be trusted
           to show that the funds exist.

           If 'receipt_within' is set, then the CreditNotes will
           be automatically cancelled if they are not
           receipted within 'receipt_within' seconds

           It is your responsibility to receipt the note for
           the actual valid incurred once the service has been
           delivered, thereby actually transferring the cheque
           funds into your account (on that accounting service)

           This returns a list of the CreditNote(s) that were
           cashed from the cheque

           Args:
                spend (Decimal): Value to withdraw
                resource (str): Resource to spend value on
                receipt_within (datetime, default=3600): Time to receipt
                the cashing of this cheque by
           Returns:
                list: List of CreditNotes

        """
        if self._cheque is None:
            raise PaymentError("You cannot cash a null cheque!")

        # sign the cheque to show we have seen it
        from Acquire.Service import get_this_service as _get_this_service
        service = _get_this_service(need_private_access=True)
        self._cheque = service.sign_data(self._cheque)

        # get the trusted accounting service that will honour the cheque
        accounting_service = self.accounting_service()

        # when do we guarantee to receipt the credit notes by?
        from Acquire.ObjectStore import get_datetime_future \
            as _get_datetime_future
        receipt_by = _get_datetime_future(receipt_within)

        # which account should the money be paid into?
        account_uid = service.service_user_account_uid(
                                accounting_service=accounting_service)

        # next - send the cheque to the accounting service to
        # show that we know the item_id and want to cash it
        from Acquire.ObjectStore import decimal_to_string \
            as _decimal_to_string
        from Acquire.ObjectStore import datetime_to_string \
            as _datetime_to_string
        from Acquire.ObjectStore import string_to_list \
            as _string_to_list

        result = accounting_service.call_function(
            function="cash_cheque",
            args={"cheque": self.to_data(),
                  "spend": _decimal_to_string(spend),
                  "resource": str(resource),
                  "account_uid": account_uid,
                  "receipt_by": _datetime_to_string(receipt_by)})

        credit_notes = None

        try:
            from Acquire.Accounting import CreditNote as _CreditNote
            credit_notes = _string_to_list(result["credit_notes"],
                                           _CreditNote)
        except Exception as e:
            raise PaymentError(
                "Attempt to cash the cheque has not resulted in a "
                "valid CreditNote? Error = %s" % str(e))

        total_cashed = 0

        for note in credit_notes:
            total_cashed = total_cashed + note.value()
            if note.account_uid() != account_uid:
                raise PaymentError(
                    "The cashed cheque is paying into the wrong account! "
                    "%s. It should be going to %s" %
                    (note.account_uid(), account_uid))

        if total_cashed != spend:
            raise PaymentError(
                "The value of the cheque (%s) does not match the total value "
                "of the credit note(s) returned (%s)" % (spend, total_cashed))

        return credit_notes
Example #21
0
    def get_service(self, service_uid=None, service_url=None):
        """Load and return the service with specified url or uid
           from the registry. This will consult with other
           registry services to find the matching service
        """
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.Service import Service as _Service
        from Acquire.ObjectStore import string_to_encoded \
            as _string_to_encoded
        from Acquire.Service import get_this_service as _get_this_service

        this_service = _get_this_service(need_private_access=False)

        if service_url is not None:
            from Acquire.Service import Service as _Service
            service_url = _Service.get_canonical_url(service_url)

        if this_service.uid() == service_uid:
            return this_service
        elif this_service.canonical_url() == service_url:
            return this_service

        bucket = self.get_bucket()

        service_key = self.get_service_key(service_uid=service_uid,
                                           service_url=service_url)

        service = None

        if service_key is not None:
            try:
                data = _ObjectStore.get_object_from_json(bucket=bucket,
                                                         key=service_key)
                service = _Service.from_data(data)
            except:
                pass

        if service is not None:
            must_write = False

            if service.uid() == "STAGE1":
                # we need to directly ask the service for its info
                service = self.challenge_service(service)

                if service.uid() == "STAGE1":
                    from Acquire.Service import MissingServiceError
                    raise MissingServiceError(
                        "Service %s|%s not available as it is still under "
                        "construction!" % (service_uid, service))

                # we can now move this service from pending to active
                uidkey = self._get_key_for_uid(service.uid())
                domain = self._get_domain(service.service_url())
                domainroot = self._get_root_key_for_domain(domain=domain)

                pending_key = "%s/pending/%s" % (domainroot, service.uid())
                active_key = "%s/active/%s" % (domainroot, service.uid())

                try:
                    _ObjectStore.delete_object(bucket=bucket,
                                               key=pending_key)
                except:
                    pass

                try:
                    _ObjectStore.set_string_object(bucket=bucket,
                                                   key=active_key,
                                                   string_data=uidkey)
                except:
                    pass

                must_write = True
            elif service.should_refresh_keys():
                service.refresh_keys()
                must_write = True

            if must_write:
                data = service.to_data()
                _ObjectStore.set_object_from_json(bucket=bucket,
                                                  key=service_key,
                                                  data=data)
            return service

        # we only get here if we can't find the service on this registry.
        # In the future, we will use the initial part of the UID of
        # the service to ask its registering registry for its data.
        # For now, we just raise an error
        from Acquire.Service import MissingServiceError
        raise MissingServiceError(
            "No service available: service_url=%s  service_uid=%s" %
                                  (service_url, service_uid))
Example #22
0
def call_function(service_url,
                  function=None,
                  args=None,
                  args_key=None,
                  response_key=None,
                  public_cert=None):
    """Call the remote function called 'function' at 'service_url' passing
       in named function arguments in 'kwargs'. If 'args_key' is supplied,
       then encrypt the arguments using 'args'. If 'response_key'
       is supplied, then tell the remote server to encrypt the response
       using the public version of 'response_key', so that we can
       decrypt it in the response. If 'public_cert' is supplied then
       we will ask the service to sign their response using their
       service signing certificate, and we will validate the
       signature using 'public_cert'
    """
    if args is None:
        args = {}

    from Acquire.Service import is_running_service as _is_running_service
    from Acquire.ObjectStore import get_datetime_now_to_string \
        as _get_datetime_now_to_string

    service = None

    if _is_running_service():
        from Acquire.Service import get_this_service as _get_this_service
        try:
            service = _get_this_service(need_private_access=False)
        except:
            pass

        if service is not None:
            if service.canonical_url() == service_url:
                result = service._call_local_function(function=function,
                                                      args=args)
                return unpack_return_value(return_value=result)

    response_key = _get_key(response_key)

    if response_key:
        args_json = pack_arguments(function=function,
                                   args=args,
                                   key=args_key,
                                   response_key=response_key.public_key(),
                                   public_cert=public_cert)
    else:
        args_json = pack_arguments(function=function, args=args, key=args_key)

    response = None
    try:
        from Acquire.Stubs import requests as _requests
        response = _requests.post(service_url, data=args_json, timeout=60.0)
    except Exception as e:
        from Acquire.Service import RemoteFunctionCallError
        raise RemoteFunctionCallError(
            "Cannot call remote function '%s' at '%s' because of a possible "
            "network issue: requests exeption = '%s'" %
            (function, service_url, str(e)))

    args = None
    args_json = None
    args_key = None

    if response.status_code != 200:
        from Acquire.Service import RemoteFunctionCallError
        raise RemoteFunctionCallError(
            "Cannot call remote function '%s' as '%s'. Invalid error code "
            "%d returned. Message:\n%s" %
            (function, service_url, response.status_code, str(
                response.content)))

    if response.encoding == "utf-8" or response.encoding is None:
        result = response.content.decode("utf-8")
    else:
        from Acquire.Service import RemoteFunctionCallError
        raise RemoteFunctionCallError(
            "Cannot call remote function '%s' as '%s'. Invalid data encoding "
            "%s returned. Message:\n%s" %
            (function, service_url, response.encoding, str(response.content)))

    return unpack_return_value(return_value=result,
                               key=response_key,
                               public_cert=public_cert,
                               function=function,
                               service=service_url)
Example #23
0
def get_trusted_service(service_url=None,
                        service_uid=None,
                        service_type=None,
                        autofetch=True):
    """Return the trusted service info for the service with specified
       service_url or service_uid"""
    if service_url is not None:
        from Acquire.Service import Service as _Service
        service_url = _Service.get_canonical_url(service_url,
                                                 service_type=service_type)

    from Acquire.Service import is_running_service as _is_running_service

    if _is_running_service():
        from Acquire.Service import get_this_service as _get_this_service
        from Acquire.Service import Service as _Service
        from Acquire.Service import get_service_account_bucket as \
            _get_service_account_bucket
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.ObjectStore import url_to_encoded as \
            _url_to_encoded

        service = _get_this_service()

        if service_url is not None and service.canonical_url() == service_url:
            # we trust ourselves :-)
            return service

        if service_uid is not None and service.uid() == service_uid:
            # we trust ourselves :-)
            return service

        bucket = _get_service_account_bucket()
        uidkey = None
        data = None

        if service_uid is not None:
            uidkey = "_trusted/uid/%s" % service_uid
            try:
                data = _ObjectStore.get_object_from_json(bucket, uidkey)
            except:
                pass
        elif service_url is not None:
            urlkey = "_trusted/url/%s" % _url_to_encoded(service_url)
            try:
                uidkey = _ObjectStore.get_string_object(bucket, urlkey)
                if uidkey is not None:
                    data = _ObjectStore.get_object_from_json(bucket, uidkey)
            except:
                pass

        if data is not None:
            remote_service = _Service.from_data(data)

            if remote_service.should_refresh_keys():
                # need to update the keys in our copy of the service
                remote_service.refresh_keys()

                if uidkey is not None:
                    _ObjectStore.set_object_from_json(bucket, uidkey,
                                                      remote_service.to_data())

            return remote_service

        if not autofetch:
            from Acquire.Service import ServiceAccountError
            if service_uid is not None:
                raise ServiceAccountError(
                    "We do not trust the service with UID '%s'" % service_uid)
            else:
                raise ServiceAccountError(
                    "We do not trust the service at URL '%s'" % service_url)

        # we can try to fetch this data - we will ask our own
        # registry
        from Acquire.Registry import get_trusted_registry_service \
            as _get_trusted_registry_service
        registry = _get_trusted_registry_service(service_uid=service.uid())
        service = registry.get_service(service_uid=service_uid,
                                       service_url=service_url)

        from Acquire.Service import trust_service as _trust_service
        _trust_service(service)
        return service
    else:
        # this is running on the client
        from Acquire.Client import Wallet as _Wallet
        wallet = _Wallet()
        service = wallet.get_service(service_uid=service_uid,
                                     service_url=service_url,
                                     service_type=service_type,
                                     autofetch=autofetch)
        return service
Example #24
0
    def execute(self, cheque):
        """Execute (start) this work, using the passed cheque for
           payment. Note that you can't perform the same work twice
        """
        if self.is_null():
            from Acquire.Accounting import PaymentError
            raise PaymentError("You cannot try to execute null work!")

        from Acquire.Client import Cheque as _Cheque
        if not isinstance(cheque, _Cheque):
            raise TypeError("You must pass a valid Cheque as payment "
                            "for the work")

        if self._credit_notes is not None:
            raise PermissionError("You cannot start a piece of work twice!")

        from Acquire.Service import get_this_service as _get_this_service

        access_service = _get_this_service(need_private_access=True)
        compute_service = self.compute_service()
        storage_service = self.storage_service()
        accounting_service = cheque.accounting_service()

        access_user = access_service.login_service_user()

        account_uid = access_service.service_user_account_uid(
            accounting_service=accounting_service)

        from Acquire.Client import Account as _Account
        access_account = _Account(user=access_user,
                                  account_uid=account_uid,
                                  accounting_service=accounting_service)

        # TODO - validate that the cost of the work on the compute
        #        and storage services is covered by the passed cheque

        try:
            credit_notes = cheque.cash(spend=self.total_cost(),
                                       resource=self.request().fingerprint())
        except Exception as e:
            from Acquire.Service import exception_to_string
            from Acquire.Accounting import PaymentError
            raise PaymentError(
                "Problem cashing the cheque used to pay for the calculation: "
                "\n\nCAUSE: %s" % exception_to_string(e))

        if credit_notes is None or len(credit_notes) == 0:
            from Acquire.Accounting import PaymentError
            raise PaymentError("Cannot be paid!")

        # make sure that we have been paid!
        for credit_note in credit_notes:
            if credit_note.credit_account_uid() != access_account.uid():
                raise PaymentError("The wrong account has been paid!?!")

        self._status = "awaiting (paid)"
        self._credit_notes = credit_notes

        # work out when this job MUST have finished. If the job
        # has not completed before this time then it will be killed
        from Acquire.ObjectStore import get_datetime_future \
            as _get_datetime_future

        endtime = _get_datetime_future(days=2)  # this should be calculated

        # save the WorkSheet to the object store so we don't lose the
        # value in the credit notes
        self.save()

        compute_cheque = _Cheque.write(
            account=access_account,
            resource="work %s" % self.uid(),
            max_spend=10.0,
            recipient_url=compute_service.canonical_url(),
            expiry_date=endtime)

        storage_cheque = _Cheque.write(
            account=access_account,
            resource="work %s" % self.uid(),
            max_spend=10.0,
            recipient_url=storage_service.canonical_url(),
            expiry_date=endtime)

        self._compute_cheque = compute_cheque
        self._storage_cheque = storage_cheque

        # now create a Drive on the storage service that will hold
        # the output for this job
        from Acquire.Client import Drive as _Drive
        from Acquire.Client import StorageCreds as _StorageCreds
        from Acquire.Client import ACLRule as _ACLRule
        from Acquire.Client import ACLRules as _ACLRules
        from Acquire.Client import ACLUserRules as _ACLUserRules

        creds = _StorageCreds(user=access_user,
                              storage_service=storage_service)

        rule = _ACLUserRules.owner(user_guid=access_user.guid()).add(
            user_guid=self.user_guid(), rule=_ACLRule.reader())

        aclrules = _ACLRules(rule=rule, default_rule=_ACLRule.denied())

        output_drive = _Drive(name="output_%s" % self.uid(),
                              creds=creds,
                              aclrules=aclrules,
                              cheque=storage_cheque,
                              max_size="10MB",
                              autocreate=True)

        self._output_loc = output_drive.metadata().location()
        self._status = "awaiting (paid, have drive)"
        self.save()

        from Acquire.Client import PAR as _PAR
        par = _PAR(location=self._output_loc,
                   user=access_user,
                   aclrule=_ACLRule.writer(),
                   expires_datetime=endtime)

        secret = compute_service.encrypt_data(par.secret())

        args = {
            "worksheet_uid": self.uid(),
            "request": self.request().to_data(),
            "par": par.to_data(),
            "secret": secret,
            "cheque": compute_cheque.to_data()
        }

        self._status = "submitting"
        self.save()

        response = compute_service.call_function(function="submit_job",
                                                 args=args)

        print(response)

        self._status = "submitted"
        self.save()

        # the service user will log out automatically on destruction, but
        # let us make sure!
        access_user.logout()