def to_data(self): """Return this credit note as a dictionary that can be encoded to JSON Returns: dict: Dictionary of object to be encoded to JSON """ data = {} if not self.is_null(): from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string data["account_uid"] = self._account_uid data["debit_account_uid"] = self._debit_account_uid data["uid"] = self._uid data["debit_note_uid"] = self._debit_note_uid data["datetime"] = _datetime_to_string(self._datetime) data["value"] = str(self._value) data["is_provisional"] = self._is_provisional if self._is_provisional: data["receipt_by"] = _datetime_to_string(self._receipt_by) return data
def _get_message(self, resource=None, matched_resource=False): """Internal function that is used to generate the message for the resource that is signed. This message encodes information about the user and identity service that signed the message, as well as the resource. This helps prevent tamporing with the data in this authorisation. If 'matched_resource' is True then this will return the message based on the previously-verified resource (as we have already determined that the user knows what the resource is) """ from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string if matched_resource: resource = self._last_verified_resource if resource is None: return "%s|%s|%s|%s" % (self._user_uid, self._session_uid, self._identity_uid, _datetime_to_string(self._auth_datetime)) else: return "%s|%s|%s|%s|%s" % ( self._user_uid, self._session_uid, self._identity_uid, str(resource), _datetime_to_string(self._auth_datetime))
def register(par, url_checksum, details_function, cleanup_function=None): """Register the passed PAR, passing in the checksum of the PAR's secret URL (so we can verify the close), and optionally supplying a cleanup_function that is called when the PAR is closed. The passed 'details_function' should be used to extract the object-store driver-specific details from the PAR and convert them into a dictionary. The signature should be; driver_details = details_function(par) """ from Acquire.Service import is_running_service as _is_running_service if not _is_running_service(): return from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.ObjectStore import OSPar as _OSPar from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.ObjectStore import Function as _Function from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string if par is None: return if not isinstance(par, _OSPar): raise TypeError("You can only register pars of type PAR") if par.is_null(): return data = {} data["par"] = par.to_data() if details_function is None: data["driver_details"] = par._driver_details else: data["driver_details"] = details_function(par) data["url_checksum"] = url_checksum if cleanup_function is not None: if not isinstance(cleanup_function, _Function): cleanup_function = _Function(cleanup_function) data["cleanup_function"] = cleanup_function.to_data() expire_string = _datetime_to_string(par.expires_when()) key = "%s/uid/%s/%s" % (_registry_key, par.uid(), expire_string) bucket = _get_service_account_bucket() _ObjectStore.set_object_from_json(bucket, key, data) key = "%s/expire/%s/%s" % (_registry_key, expire_string, par.uid()) _ObjectStore.set_object_from_json(bucket, key, par.uid())
def to_key(self): """Return this transaction encoded to a key""" from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string return "%s/%s/%s" % (_datetime_to_string(self._datetime), self._uid, TransactionInfo.encode( code=self._code, value=self._value, receipted_value=self._receipted_value))
def __str__(self): if self.is_null(): return "PAR::null" elif not self.is_authorised(): return "PAR::unauthorised" else: from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string return "PAR( %s, %s, expires %s )" % ( self._location.to_string(), self._aclrule, _datetime_to_string(self._expires_datetime))
def fingerprint(self): """Return a fingerprint that can be used to show that the user authorised the request to create this PAR """ if self.is_null(): return None else: from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string return "%s:%s:%s" % (self._location.fingerprint(), self._aclrule.fingerprint(), _datetime_to_string(self._expires_datetime))
def close(par): """Close the passed PAR. This will remove the registration for the PAR and will also call the associated cleanup_function (if any) """ from Acquire.Service import is_running_service as _is_running_service if not _is_running_service(): return from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.ObjectStore import OSPar as _OSPar from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.ObjectStore import Function as _Function if par is None: return if not isinstance(par, _OSPar): raise TypeError("You can only close OSPar objects!") if par.is_null(): return expire_string = _datetime_to_string(par.expires_when()) bucket = _get_service_account_bucket() key = "%s/expire/%s/%s" % (_registry_key, expire_string, par.uid()) try: _ObjectStore.delete_object(bucket=bucket, key=key) except: pass key = "%s/uid/%s/%s" % (_registry_key, par.uid(), expire_string) try: data = _ObjectStore.take_object_from_json(bucket=bucket, key=key) except: data = None if data is None: # this PAR has already been closed return if "cleanup_function" in data: cleanup_function = _Function.from_data(data["cleanup_function"]) cleanup_function(par=par)
def to_data(self): """Return this DebitNote as a dictionary that can be encoded as json Returns: dict: Dictionary to be converted to JSON """ data = {} if not self.is_null(): from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string data["transaction"] = self._transaction.to_data() data["account_uid"] = self._account_uid data["authorisation"] = self._authorisation.to_data() data["is_provisional"] = self._is_provisional data["datetime"] = _datetime_to_string(self._datetime) data["uid"] = self._uid if self._is_provisional: data["receipt_by"] = _datetime_to_string(self._receipt_by) return data
def _get_driver_details_from_par(par): from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string import copy as _copy details = _copy.copy(par._driver_details) if details is None: return {} else: # fix any non-string/number objects details["created_datetime"] = _datetime_to_string( details["created_datetime"]) return details
def to_data(self): """Return a json-serialisable dictionary of this PAR""" if self.is_null(): return None from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string data = {} data["location"] = self._location.to_data() data["aclrule"] = self._aclrule.to_data() data["expires_datetime"] = _datetime_to_string(self._expires_datetime) data["uid"] = self._uid return data
def to_data(self): """Return a json-serialisable dictionary of this object""" data = {} if self.is_null(): return data data["filename"] = str(self._filename) if self._uid is not None: data["uid"] = str(self._uid) if self._filesize is not None: data["filesize"] = self._filesize if self._checksum is not None: data["checksum"] = self._checksum if self._user_guid is not None: data["user_guid"] = self._user_guid if self._compression is not None: data["compression"] = self._compression try: acl = self._acl except: acl = None if acl is not None: data["acl"] = acl.to_data() try: aclrules = self._aclrules except: aclrules = None if aclrules is not None: data["aclrules"] = aclrules.to_data() if self._datetime is not None: from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string data["datetime"] = _datetime_to_string(self._datetime) return data
def to_data(self, passphrase=None): """Return a json-serialisable dictionary that contains all data for this object Args: passphrase (str, default=None): Passphrase to use to encrypt OSPar Returns: dict: JSON serialisable dictionary """ data = {} if self._url is None: return data from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.ObjectStore import bytes_to_string \ as _bytes_to_string data["url"] = _bytes_to_string(self._url) data["uid"] = self._uid data["key"] = self._key data["expires_datetime"] = _datetime_to_string(self._expires_datetime) data["is_readable"] = self._is_readable data["is_writeable"] = self._is_writeable try: if self._service_url is not None: data["service_url"] = self._service_url except: pass try: privkey = self._privkey except: privkey = None if privkey is not None: if passphrase is not None: data["privkey"] = privkey.to_data(passphrase) # note that we don't save the driver details as these # are stored separately on the service return data
def _get_driver_details_from_par(par): """Internal function used to get the OCI driver details from the passed OSPar (pre-authenticated request) Args: par (OSPar): PAR holding details Args: dict: Dictionary holding OCI driver details """ from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string import copy as _copy details = _copy.copy(par._driver_details) if details is None: return {} else: # fix any non-string/number objects details["created_datetime"] = _datetime_to_string( details["created_datetime"]) return details
def lock(self, timeout=None, lease_time=None): """Lock the mutex, blocking until the mutex is held, or until 'timeout' seconds have passed. If we time out, then an exception is raised. The lock is held for a maximum of 'lease_time' seconds. Args: timeout (int): Number of seconds to block lease_time (int): Number of seconds to hold the lock Returns: None """ # if the user does not provide a timeout, then we will set a timeout # to 10 seconds if timeout is None: timeout = 10.0 else: timeout = float(timeout) # if the user does not provide a lease_time, then we will set a # default of only 10 seconds if lease_time is None: lease_time = 10.0 else: lease_time = float(lease_time) from Acquire.ObjectStore import get_datetime_now as _get_datetime_now from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.ObjectStore import string_to_datetime \ as _string_to_datetime from Acquire.ObjectStore import ObjectStore as _ObjectStore if self.is_locked(): # renew the lease - if there is less than a second remaining # on the lease then unlock and then lock again from scratch now = _get_datetime_now() if (now > self._end_lease) or (now - self._end_lease).seconds < 1: self.fully_unlock() self.lock(timeout, lease_time) else: self._end_lease = now + _datetime.timedelta(seconds=lease_time) self._lockstring = "%s{}%s" % ( self._secret, _datetime_to_string(self._end_lease)) _ObjectStore.set_string_object(self._bucket, self._key, self._lockstring) self._is_locked += 1 return now = _get_datetime_now() endtime = now + _datetime.timedelta(seconds=timeout) # This is the first time we are trying to get a lock while now < endtime: # does anyone else hold the lock? try: holder = _ObjectStore.get_string_object( self._bucket, self._key) except: holder = None is_held = True if holder is None: is_held = False else: end_lease = _string_to_datetime(holder.split("{}")[-1]) if now > end_lease: # the lease from the other holder has expired :-) is_held = False if not is_held: # no-one holds this mutex - try to hold it now self._end_lease = now + _datetime.timedelta(seconds=lease_time) self._lockstring = "%s{}%s" % ( self._secret, _datetime_to_string(self._end_lease)) _ObjectStore.set_string_object(self._bucket, self._key, self._lockstring) holder = _ObjectStore.get_string_object( self._bucket, self._key) else: self._lockstring = None if holder == self._lockstring: # it looks like we are the holder - read and write again # just to make sure holder = _ObjectStore.get_string_object( self._bucket, self._key) if holder == self._lockstring: # write again just to make sure _ObjectStore.set_string_object(self._bucket, self._key, self._lockstring) holder = _ObjectStore.get_string_object( self._bucket, self._key) if holder == self._lockstring: # we have read and written our secret to the object store # three times. While a race condition is still possible, # I'd hope it is now highly unlikely - we now hold the mutex self._is_locked = 1 return # only try the lock 4 times a second _time.sleep(0.25) now = _get_datetime_now() from Acquire.ObjectStore import MutexTimeoutError raise MutexTimeoutError("Cannot acquire a mutex lock on the " "key '%s'" % self._key)
def read(self, spend, resource, receipt_by): """Read the cheque - this will read the cheque to return the decrypted contents. This will only work if this function is called on the accounting service that will cash the cheque, if the signature on the cheque matches the service that is authorised to cash the cheque, and if the passed resource matches the resource encoded in the cheque. If this is all correct, then the returned dictionary will contain; {"recipient_url": The URL of the service which was sent the cheque, "recipient_key_fingerprint": Verified fingerprint of the service key that signed this cheque "spend": The amount authorised by this cheque, "uid": The unique ID for this cheque, "resource": String that identifies the resource this cheque will be used to pay for, "account_uid": UID of the account from which funds will be drawn "authorisation" : Verified authorisation from the user who says they own the account for the spend "receipt_by" : Time when we must receipt the cheque, or we will lose the money } You must pass in the spend you want to draw from the cheque, a string representing the resource this cheque will be used to pay for, and the time by which you promise to receipt the cheque after cashing Args: spend (Decimal): Amount authorised by cheque resource (str): Resource to pay for receipt_by (datetime): Time cheque must be receipted by Returns: dict: Dictionary described above """ if self._cheque is None: raise PaymentError("You cannot read a null cheque") from Acquire.ObjectStore import string_to_decimal \ as _string_to_decimal from Acquire.ObjectStore import string_to_datetime \ as _string_to_datetime from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string from Acquire.Service import get_this_service as _get_this_service spend = _string_to_decimal(spend) resource = str(resource) receipt_by = _string_to_datetime(receipt_by) service = _get_this_service(need_private_access=True) # get the cheque data - this may have been signed try: cheque_data = _json.loads(self._cheque["signed_data"]) except: cheque_data = self._cheque # decrypt the cheque's data - only possible on the accounting service cheque_data = service.decrypt_data(cheque_data) # the date comprises the user-authorisation that acts as a # signature that the user wrote this cheque, and the info # for the cheque to say how it is valid from Acquire.Identity import Authorisation as _Authorisation auth = _Authorisation.from_data(cheque_data["authorisation"]) info = cheque_data["info"] # the json.dumps version is the resource used to verify # the above authorisation auth_resource = info # validate that the user authorised this cheque try: auth.verify(resource=info) except Exception as e: raise PaymentError( "The user's signature/authorisation for this cheque " "is not valid! ERROR: %s" % str(e)) info = _json.loads(info) # the user signed this cheque :-) info["authorisation"] = auth # check the signature if one was needed try: recipient_url = info["recipient_url"] except: recipient_url = None if recipient_url: # the recipient was specified - verify that we trust # the recipient, and that they have signed the cheque recipient_service = service.get_trusted_service( service_url=recipient_url) recipient_service.verify_data(self._cheque) info["recipient_key_fingerprint"] = self._cheque["fingerprint"] # validate that the item signature is correct try: cheque_resource = info["resource"] except: cheque_resource = None if cheque_resource is not None: if resource != resource: raise PaymentError( "Disagreement over the resource for which " "this cheque has been signed") info["resource"] = resource info["auth_resource"] = auth_resource try: max_spend = info["max_spend"] del info["max_spend"] except: max_spend = None if max_spend is not None: max_spend = _string_to_decimal(max_spend) if max_spend < spend: raise PaymentError( "The requested spend (%s) exceeds the authorised " "maximum value of the cheque" % (spend)) info["spend"] = spend try: expiry_date = info["expiry_date"] del expiry_date["expiry_date"] except: expiry_date = None if expiry_date is not None: expiry_date = _string_to_datetime(expiry_date) # validate that the cheque will not have expired # when we receipt it from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now now = _get_datetime_now() if now > receipt_by: raise PaymentError( "The time when you promised to receipt the cheque " "has already passed!") if receipt_by > expiry_date: raise PaymentError( "The cheque will have expired after you plan to " "receipt it!: %s versus %s" % (_datetime_to_string(receipt_by), _datetime_to_string(expiry_date))) info["receipt_by"] = receipt_by return info
def 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
def write(account=None, resource=None, recipient_url=None, max_spend=None, expiry_date=None): """Create and return a cheque that can be used at any point in the future to authorise a transaction. If 'recipient_url' is supplied, then only the service with the matching URL can 'cash' the cheque (it will need to sign the cheque before sending it to the accounting service). If 'max_spend' is specified, then the cheque is only valid up to that maximum spend. Otherwise, it is valid up to the maximum daily spend limit (or other limits) of the account. If 'expiry_date' is supplied then this cheque is valid only before the supplied datetime. If 'resource' is supplied then this cheque is only valid to pay for the specified resource (this should be a string that everyone agrees represents the resource in question). Note that this cheque is for a future transaction. We do not check to see if there are sufficient funds now, and this does not affect the account. If there are insufficient funds when the cheque is cashed (or it breaks spending limits) then the cheque will bounce. Args: account (Account, default=None): Account to use to write cheque resource (str, default=None): Define the resource to pay for recipient_url (str, default=None): URL of service to use max_spend (Decimal, default=None): Limit of cheque expiry_date (datetime, default=None): Cheque's expiry date """ from Acquire.Client import Account as _Account if not isinstance(account, _Account): raise TypeError("You must pass a valid Acquire.Client.Account " "object to write a cheque...") if max_spend is not None: from Acquire.ObjectStore import decimal_to_string \ as _decimal_to_string max_spend = _decimal_to_string(max_spend) if expiry_date is not None: from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string expiry_date = _datetime_to_string(expiry_date) if recipient_url is not None: recipient_url = str(recipient_url) from Acquire.ObjectStore import create_uuid as _create_uuid from Acquire.Identity import Authorisation as _Authorisation info = _json.dumps({"recipient_url": recipient_url, "max_spend": max_spend, "expiry_date": expiry_date, "uid": _create_uuid(), "resource": str(resource), "account_uid": account.uid()}) auth = _Authorisation(user=account.user(), resource=info) data = {"info": info, "authorisation": auth.to_data()} cheque = Cheque() cheque._cheque = account.accounting_service().encrypt_data(data) cheque._accounting_service_url = \ account.accounting_service().canonical_url() return cheque
def download(self, filename=None, version=None, dir=None, force_par=False): """Download this file into the local directory the local directory, or 'dir' if specified, calling the file 'filename' (or whatever it is called on the Drive if not specified). If a local file exists with this name, then a new, unique filename will be used. This returns the local filename of the downloaded file (with full absolute path) Note that this only downloads files for which you have read-access. If the file is not readable then an exception is raised and nothing is returned If 'version' is specified then download a specific version of the file. Otherwise download the version associated with this file object """ if self.is_null(): raise PermissionError("Cannot download a null File!") if self._creds is None: raise PermissionError("We have not properly opened the file!") if filename is None: filename = self._metadata.name() drive_uid = self._metadata.drive().uid() from Acquire.Client import create_new_file as \ _create_new_file if self._creds.is_user(): privkey = self._creds.user().session_key() else: from Acquire.Crypto import get_private_key as _get_private_key privkey = _get_private_key("parkey") args = {"drive_uid": drive_uid, "filename": self._metadata.name(), "encryption_key": privkey.public_key().to_data()} if self._creds.is_user(): from Acquire.Client import Authorisation as _Authorisation authorisation = _Authorisation( resource="download %s %s" % (drive_uid, self._metadata.name()), user=self._creds.user()) args["authorisation"] = authorisation.to_data() elif self._creds.is_par(): par = self._creds.par() par.assert_valid() args["par_uid"] = par.uid() args["secret"] = self._creds.secret() if force_par: args["force_par"] = True if version is not None: from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string args["version"] = _datetime_to_string(version) elif self._metadata.version() is not None: args["version"] = self._metadata.version() storage_service = self._creds.storage_service() response = storage_service.call_function( function="download", args=args) from Acquire.Client import FileMeta as _FileMeta filemeta = _FileMeta.from_data(response["filemeta"]) if "filedata" in response: # we have already downloaded the file to 'filedata' filedata = response["filedata"] from Acquire.ObjectStore import string_to_bytes \ as _string_to_bytes filedata = _string_to_bytes(response["filedata"]) del response["filedata"] # validate that the size and checksum are correct filemeta.assert_correct_data(filedata) if filemeta.is_compressed(): # uncompress the data from Acquire.Client import uncompress as _uncompress filedata = _uncompress( inputdata=filedata, compression_type=filemeta.compression_type()) # write the data to the specified local file... filename = _create_new_file(filename=filename, dir=dir) with open(filename, "wb") as FILE: FILE.write(filedata) FILE.flush() elif "download_par" in response: from Acquire.ObjectStore import OSPar as _OSPar filename = _create_new_file(filename=filename, dir=dir) par = _OSPar.from_data(response["download_par"]) par.read(privkey).get_object_as_file(filename) par.close(privkey) # validate that the size and checksum are correct filemeta.assert_correct_data(filename=filename) # uncompress the file if desired if filemeta.is_compressed(): from Acquire.Client import uncompress as _uncompress _uncompress(inputfile=filename, outputfile=filename, compression_type=filemeta.compression_type()) elif "downloader" in response: from Acquire.Client import ChunkDownloader as _ChunkDownloader downloader = _ChunkDownloader.from_data(response["downloader"], privkey=privkey, service=storage_service) filename = downloader.download(filename=filename, dir=dir) filemeta._copy_credentials(self._metadata) self._metadata = filemeta return filename
def to_data(self): """Return a data version (dictionary) of this LoginSession that can be serialised to json """ if self.is_null(): return {} from Acquire.ObjectStore import datetime_to_string \ as _datetime_to_string data = {} data["uid"] = self._uid data["username"] = self._username data["request_datetime"] = _datetime_to_string(self._request_datetime) data["public_certificate"] = self._pubcert.to_data() data["status"] = self._status if self._pubkey is not None: data["public_key"] = self._pubkey.to_data() try: data["login_datetime"] = _datetime_to_string(self._login_datetime) except: pass try: data["logout_datetime"] = _datetime_to_string( self._logout_datetime) except: pass try: data["ipaddr"] = self._ipaddr except: pass try: data["hostname"] = self._hostname except: pass try: data["login_message"] = self._login_message except: pass try: data["scope"] = self._scope except: pass try: data["permissions"] = self._permissions except: pass try: data["user_uid"] = self._user_uid except: pass try: data["device_uid"] = self._device_uid except: pass return data