예제 #1
0
def run(args):
    """Call this function to initiate the two-step file-upload process.

       Step 1: upload - tells the service that a file of specific
               size and checksum will be uploaded. This gives the service
               the chance to decide whether this will be allowed. If the
               file is small, and was thus included in the FileHandle,
               then it is uploaded immediately and the operation completes.
               If the file is large, then we now returns a OSPar
               that can be used for this upload (Step 2)

       Step 2: after the user has used the OSPar to upload
               the file, they should call OSPar.close() to notify
               the service that the file has been successfully uploaded.
               This will verify that the file has been uploaded correctly,
               will receipt the storage cost and will delete the OSPar
    """

    filehandle = FileHandle.from_data(args["filehandle"])

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    try:
        par_uid = args["par_uid"]
    except:
        par_uid = None

    try:
        secret = args["secret"]
    except:
        secret = None

    try:
        public_key = PublicKey.from_data(args["encryption_key"])
    except:
        public_key = None

    if par_uid is not None:
        registry = PARRegistry()
        (par, identifiers) = registry.load(par_uid=par_uid, secret=secret)
    else:
        par = None
        identifiers = None

    drive_uid = filehandle.drive_uid()

    drive = DriveInfo(drive_uid=drive_uid)

    return_value = {}

    (filemeta, par) = drive.upload(filehandle=filehandle,
                                   authorisation=authorisation,
                                   encrypt_key=public_key,
                                   par=par,
                                   identifiers=identifiers)

    if filemeta is not None:
        return_value["filemeta"] = filemeta.to_data()

    if par is not None:
        return_value["upload_par"] = par.to_data()

    return return_value
예제 #2
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
예제 #3
0
def run(args):
    """This function is called to handle requests from a user to deposit
       more funds into their account. This will add this deposit as a
       debt for the user. Once the debt exceeds a certain value, then the
       backend-payment system will charge the user's real account to
       recover the funds

       Args:
            args (dict): data for deposit of funds into the account

        Returns:
            dict: contains status, status message and details regarding
                the deposit into the account and invoice data
    """

    transaction_records = None
    invoice_value = None
    invoice_user = None

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    transaction = Transaction.from_data(args["transaction"])

    if authorisation is None:
        raise PermissionError("You must supply a valid authorisation "
                              "to deposit funds into your account")

    if transaction is None or transaction.is_null():
        raise ValueError("You must supply a valid transaction that "
                         "represents the deposit")

    try:
        account_name = str(args["account_name"])
    except:
        account_name = "deposits"

    if transaction.value() > 0:
        user_guid = authorisation.user_guid()

        # load the account from which the transaction will be performed
        bucket = get_service_account_bucket()
        accounts = Accounts(user_guid=user_guid)

        # deposits are made by transferring funds from the user's
        # 'billing' account to their named account (or 'deposits' account)
        deposit_account = accounts.create_account(account_name,
                                                  "Deposit account",
                                                  bucket=bucket)

        billing_account = accounts.create_account("billing",
                                                  "Billing account",
                                                  overdraft_limit=150,
                                                  bucket=bucket)

        billing_balance = billing_account.balance() - transaction.value()

        if billing_balance.balance() < -50.0:
            # there are sufficient funds that need to be transferred that
            # it is worth really charging the user
            invoice_user = user_guid
            invoice_value = billing_balance

        # we have enough information to perform the transaction
        transaction_records = Ledger.perform(transactions=transaction,
                                             debit_account=billing_account,
                                             credit_account=deposit_account,
                                             authorisation=authorisation,
                                             is_provisional=False,
                                             bucket=bucket)

    return_value = {}

    if transaction_records:
        try:
            transaction_records[0]
        except:
            transaction_records = [transaction_records]

        for i in range(0, len(transaction_records)):
            transaction_records[i] = transaction_records[i].to_data()

        return_value["transaction_records"] = transaction_records

    if invoice_user:
        return_value["invoice_user"] = invoice_user
        return_value["invoice_value"] = str(invoice_value)

    return return_value
예제 #4
0
def run(args):
    """Call this function to initiate the two-step file-download process.

       Step 1: download - tells the service to download the file. If the
               file is small then the file will be in the response.
               Otherwise a OSPar will be returned that will let you
               download the file. If this is the case, then you must
               call step 2...

       Step 2: downloaded - after you have downloaded the file from the OSPar
               call OSPar.close() so that the service knows that the OSPar
               is no longer needed and can be deleted
    """

    drive_uid = args["drive_uid"]
    filename = args["filename"]

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    try:
        par_uid = args["par_uid"]
    except:
        par_uid = None

    try:
        secret = args["secret"]
    except:
        secret = None

    public_key = PublicKey.from_data(args["encryption_key"])

    if "version" in args:
        version = str(args["version"])
    else:
        version = None

    if "force_par" in args:
        force_par = args["force_par"]
    else:
        force_par = None

    if "must_chunk" in args:
        must_chunk = args["must_chunk"]
    else:
        must_chunk = False

    if must_chunk:
        must_chunk = True

    if force_par:
        force_par = True

    if par_uid is not None:
        registry = PARRegistry()
        (par, identifiers) = registry.load(par_uid=par_uid, secret=secret)
    else:
        par = None
        identifiers = None

    drive = DriveInfo(drive_uid=drive_uid)

    return_value = {}

    (filemeta, filedata, par,
     downloader) = drive.download(filename=filename,
                                  version=version,
                                  authorisation=authorisation,
                                  encrypt_key=public_key,
                                  force_par=force_par,
                                  must_chunk=must_chunk,
                                  par=par,
                                  identifiers=identifiers)

    if filemeta is not None:
        return_value["filemeta"] = filemeta.to_data()

    if filedata is not None:
        from Acquire.ObjectStore import bytes_to_string as _bytes_to_string
        return_value["filedata"] = _bytes_to_string(filedata)

    if par is not None:
        return_value["download_par"] = par.to_data()

    if downloader is not None:
        return_value["downloader"] = downloader.to_data(pubkey=public_key)

    return return_value
예제 #5
0
def run(args):
    """This function is called to handle requests for the UIDs of accounts
    
        Args:
            args (dict): data regarding account to be queried

        Returns:
            dict: contains status, status message and account UIDs
    
    """

    status = 0
    message = None

    account_uids = None

    try:
        account_name = str(args["account_name"])
    except:
        account_name = None

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    try:
        user_guid = str(args["user_guid"])
    except:
        user_guid = None

    is_authorised = False

    if authorisation is not None:
        if not isinstance(authorisation, Authorisation):
            raise TypeError("All authorisations must be of type "
                            "Authorisation")

        if user_guid:
            if user_guid == authorisation.user_guid():
                authorisation.verify(resource="get_account_uids")
                is_authorised = True
        else:
            authorisation.verify(resource="get_account_uids")
            user_guid = authorisation.user_guid()
            is_authorised = True

    if user_guid is None:
        raise ValueError("You must supply either an Authorisation or the "
                         "user_guid")

    # try to create a 'main' account for this user
    account_uids = {}
    accounts = Accounts(user_guid=user_guid)

    if account_name is None:
        if not is_authorised:
            raise PermissionError(
                "You cannot list general information about a user's "
                "accounts unless you have authenticated as the user!")

        bucket = get_service_account_bucket()
        account_names = accounts.list_accounts(bucket=bucket)

        for account_name in account_names:
            account = accounts.get_account(account_name, bucket=bucket)
            account_uids[account.uid()] = account.name()

    else:
        if not is_authorised:
            try:
                account = accounts.get_account(account_name)
            except:
                # don't leak any information
                raise ListAccountsError(
                    "No account called '%s' for user '%s'" %
                    (account_name, user_guid))
        else:
            # allow the user to see the real exception if this
            # account doesn't exist
            account = accounts.get_account(account_name)

        account_uids[account.uid()] = account.name()

    return_value = {}

    if account_uids:
        return_value["account_uids"] = account_uids

    return return_value
예제 #6
0
def run(args):
    """This function is called to handle requests to perform transactions
       between accounts

       Args:
            args (dict): data for account transfers

        Returns:
            dict: contains status, status message and transaction
            records if any are available
    """

    transaction_records = None

    try:
        debit_account_uid = str(args["debit_account_uid"])
    except:
        debit_account_uid = None

    try:
        credit_account_uid = str(args["credit_account_uid"])
    except:
        credit_account_uid = None

    try:
        authorisation = Authorisation.from_data(args["authorisation"])
    except:
        authorisation = None

    try:
        transaction = Transaction.from_data(args["transaction"])
    except:
        transaction = None

    try:
        is_provisional = bool(args["is_provisional"])
    except:
        is_provisional = None

    if debit_account_uid is None:
        raise TransactionError("You must supply the account UID "
                               "for the debit account")

    if credit_account_uid is None:
        raise TransactionError("You must supply the account UID "
                               "for the credit account")

    if debit_account_uid == credit_account_uid:
        raise TransactionError(
            "You cannot perform a transaction where the debit and credit "
            "accounts are the same!")

    if transaction is None or transaction.is_null():
        raise TransactionError("You must supply a valid transaction to "
                               "perform!")

    if is_provisional is None:
        raise TransactionError("You must say whether or not the "
                               "transaction is provisional using "
                               "is_provisional")

    if authorisation is None:
        raise PermissionError("You must supply a valid authorisation "
                              "to perform transactions between accounts")

    authorisation.assert_once()
    user_guid = authorisation.user_guid()

    # load the account from which the transaction will be performed
    bucket = get_service_account_bucket()
    debit_account = Account(uid=debit_account_uid, bucket=bucket)

    # validate that this account is in a group that can be authorised
    # by the user - This should eventually go as this is all
    # handled by the ACLs
    if not Accounts(user_guid).contains(account=debit_account,
                                        bucket=bucket):
        raise PermissionError(
            "The user with GUID '%s' cannot authorise transactions from "
            "the account '%s' as they do not own this account." %
            (user_guid, str(debit_account)))

    # now load the two accounts involved in the transaction
    credit_account = Account(uid=credit_account_uid, bucket=bucket)

    # we have enough information to perform the transaction
    transaction_records = Ledger.perform(transactions=transaction,
                                         debit_account=debit_account,
                                         credit_account=credit_account,
                                         authorisation=authorisation,
                                         is_provisional=is_provisional,
                                         bucket=bucket)

    return_value = {}

    if transaction_records:
        try:
            transaction_records[0]
        except:
            transaction_records = [transaction_records]

        for i in range(0, len(transaction_records)):
            transaction_records[i] = transaction_records[i].to_data()

        return_value["transaction_records"] = transaction_records

    return return_value