def __init__(self, username=None, public_key=None, public_cert=None, ipaddr=None, hostname=None, login_message=None, scope=None, permissions=None): """Start a new login session for the user with specified username, passing in the additional data needed to request a login """ if public_key is not None: from Acquire.Crypto import PublicKey as _PublicKey if not isinstance(public_key, _PublicKey): raise TypeError("The public key must be of type PublicKey") if not isinstance(public_cert, _PublicKey): raise TypeError("The public certificate must be of " "type PublicKey") if username is None or len(username) == 0: raise PermissionError("You must supply a valid username!") self._username = username self._pubkey = public_key self._pubcert = public_cert from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now from Acquire.ObjectStore import create_uuid as _create_uuid self._uid = _create_uuid() self._request_datetime = _get_datetime_now() self._status = None self._ipaddr = ipaddr self._hostname = hostname self._login_message = login_message self._scope = scope self._permissions = permissions # make sure this session is saved to the object store self._set_status("pending") else: self._uid = None
def 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)
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 __init__(self, resource=None, user=None, testing_key=None, testing_user_guid=None): """Create an authorisation for the passed resource that is authorised by the passed user (who must be authenticated) If testing_key is passed, then this authorisation is being tested as part of the unit tests """ if resource is not None: resource = str(resource) self._signature = None self._last_validated_datetime = None self._scope = None self._permissions = None self._pubcert = None if resource is not None: if user is None and testing_key is None: raise ValueError( "You must pass in an authenticated user who will " "provide authorisation for resource '%s'" % resource) from Acquire.ObjectStore import get_datetime_now \ as _get_datetime_now from Acquire.ObjectStore import create_uuid as _create_uuid if user is not None: from Acquire.Client import User as _User if not isinstance(user, _User): raise TypeError("The passed user must be of type User") elif not user.is_logged_in(): raise PermissionError( "The passed user '%s' must be authenticated to enable " "you to generate an authorisation for the account") self._user_uid = user.uid() self._session_uid = user.session_uid() self._identity_url = user.identity_service().canonical_url() self._identity_uid = user.identity_service_uid() self._auth_datetime = _get_datetime_now() self._uid = _create_uuid(short_uid=True, include_date=self._auth_datetime) self._siguid = user.signing_key().sign(self._uid) message = self._get_message(resource) self._signature = user.signing_key().sign(message) self._last_validated_datetime = _get_datetime_now() self._last_verified_resource = resource self._last_verified_key = None if user.guid() != self.user_guid(): # interesting future case when we allow individual users # to be identified by multiple identity services... raise PermissionError( "We do not yet support a single user being identified " "by multiple identity services: %s versus %s" % (user.guid(), self.user_guid())) elif testing_key is not None: self._user_uid = "some user uid" self._session_uid = "some session uid" self._identity_url = "some identity_url" self._identity_uid = "some identity uid" self._auth_datetime = _get_datetime_now() self._uid = _create_uuid(short_uid=True, include_date=self._auth_datetime) self._is_testing = True self._testing_key = testing_key if testing_user_guid is not None: parts = testing_user_guid.split("@") self._user_uid = parts[0] self._identity_uid = parts[1] message = self._get_message(resource) self._signature = testing_key.sign(message) self._siguid = testing_key.sign(self._uid) self._last_validated_datetime = _get_datetime_now() self._last_verified_resource = resource self._last_verified_key = testing_key.public_key()
def regenerate_uid(self): """Regenerate the UUID as there has been a clash""" if not self.is_null(): from Acquire.ObjectStore import create_uuid as _create_uuid self._uid = _create_uuid()
def validate_password(user_uid, username, device_uid, secrets, password, otpcode, remember_device): """Validate that the passed password and one-time-code are valid. If they are, then return a tuple of the UserAccount of the unlocked user, the OTP that is used to generate secrets, and the device_uid of the login device If 'remember_device' is True and 'device_uid' is None, then this creates a new OTP for the login device, which is returned, and a new device_uid for that device. The password needed to match this device is a MD5 of the normal user password. """ from Acquire.Crypto import PrivateKey as _PrivateKey from Acquire.Crypto import OTP as _OTP from Acquire.ObjectStore import string_to_bytes as _string_to_bytes privkey = _PrivateKey.from_data(data=secrets["private_key"], passphrase=password) # decrypt and validate the OTP code data = _string_to_bytes(secrets["otpsecret"]) otpsecret = privkey.decrypt(data) otp = _OTP(secret=otpsecret) otp.verify(code=otpcode, once_only=True) # everything is ok - we can load the user account via the # decrypted primary password primary_password = _string_to_bytes(secrets["primary_password"]) primary_password = privkey.decrypt(primary_password) from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket data = None secrets = None key = "%s/uids/%s" % (_user_root, user_uid) bucket = _get_service_account_bucket() try: data = _ObjectStore.get_object_from_json(bucket=bucket, key=key) except: pass if data is None: from Acquire.Identity import UserValidationError raise UserValidationError( "Unable to validate user as no account data is present!") from Acquire.Identity import UserAccount as _UserAccount user = _UserAccount.from_data(data=data, passphrase=primary_password) if user.uid() != user_uid: from Acquire.Identity import UserValidationError raise UserValidationError( "Unable to validate user as mismatch in user_uids!") if device_uid is None and remember_device: # create a new OTP that is unique for this device from Acquire.ObjectStore import create_uuid as _create_uuid from Acquire.Client import Credentials as _Credentials device_uid = _create_uuid() device_password = _Credentials.encode_device_uid( encoded_password=password, device_uid=device_uid) otp = UserCredentials.create(user_uid=user_uid, password=device_password, primary_password=primary_password, device_uid=device_uid) # now save a lookup so that we can find the user_uid from # the username and device-specific password encoded_password = UserCredentials.hash( username=username, password=device_password) key = "%s/passwords/%s/%s" % (_user_root, encoded_password, user_uid) from Acquire.ObjectStore import get_datetime_now_to_string \ as _get_datetime_now_to_string _ObjectStore.set_string_object( bucket=bucket, key=key, string_data=_get_datetime_now_to_string()) return {"user": user, "otp": otp, "device_uid": device_uid}
def _get_subdrive(self, drive_uid, name, autocreate=True): """Return the DriveInfo for the Drive that the user has called 'name' in the drive with UID 'drive_uid'. If 'autocreate' is True then this drive is automatically created if it does not exist. """ if self.is_null(): raise PermissionError( "You cannot get a DriveInfo from a null UserDrives") from Acquire.ObjectStore import string_to_filepath_parts \ as _string_to_filepath_parts parts = _string_to_filepath_parts(name) if len(parts) != 1: raise ValueError("The passed drive name '%s' is not valid!" % name) from Acquire.Service import get_service_account_bucket \ as _get_service_account_bucket from Acquire.ObjectStore import ObjectStore as _ObjectStore from Acquire.ObjectStore import string_to_encoded as _string_to_encoded encoded_name = _string_to_encoded(name) bucket = _get_service_account_bucket() drive_key = "%s/%s/%s/%s" % (_subdrives_root, self._user_guid, drive_uid, encoded_name) try: drive_uid = _ObjectStore.get_string_object(bucket, drive_key) except: drive_uid = None if drive_uid is not None: from Acquire.Storage import DriveInfo as _DriveInfo drive = _DriveInfo(drive_uid=drive_uid, identifiers=self._identifiers, is_authorised=self._is_authorised) else: drive = None if drive is None: if self._is_authorised and autocreate: # create a new UID for the drive and write this to the # object store from Acquire.ObjectStore import create_uuid as _create_uuid drive_uid = _create_uuid() drive_uid = _ObjectStore.set_ins_string_object( bucket, drive_key, drive_uid) from Acquire.Storage import DriveInfo as _DriveInfo drive = _DriveInfo(drive_uid=drive_uid, identifiers=self._identifiers, is_authorised=self._is_authorised, autocreate=True) return drive