Example #1
0
def run(args):
    """This function is used to request access to a bucket for
       data in the object store. The user can request read-only
       or read-write access. Access is granted based on a permission
       list
    """

    status = 0
    message = None

    access_token = None

    user_uuid = args["user_uuid"]
    identity_service_url = args["identity_service"]

    # log into the central access account
    bucket = login_to_service_account()

    # is the identity service supplied by the user one that we trust?
    identity_service = Service.from_data(
        ObjectStore.get_object_from_json(bucket,
                                         "services/%s" % identity_service_url))

    if not identity_service:
        raise RequestBucketError(
            "You cannot request a bucket because "
            "this access service does not know or trust your supplied "
            "identity service (%s)" % identity_service_url)

    if not identity_service.is_identity_service():
        raise RequestBucketError(
            "You cannot request a bucket because "
            "the passed service (%s) is not an identity service. It is "
            "a %s" % (identity_service_url, identity_service.service_type()))

    # Since we trust this identity service, we can ask it to give us the
    # public certificate and signing certificate for this user.
    key = PrivateKey()

    response = call_function(identity_service_url,
                             "get_user_keys",
                             args_key=identity_service.public_key(),
                             response_key=key,
                             user_uuid=user_uuid)

    status = 0
    message = "Success: Status = %s" % str(response)

    return_value = create_return_value(status, message)

    if access_token:
        return_value["access_token"] = access_token

    return return_value
Example #2
0
def run(args):
    """This function is called to handle requests for information about
       particular accounts
    """

    status = 0
    message = None

    account = None
    balance_status = None

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

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

    if account_name is None:
        raise AccountError("You must supply the account_name")

    if authorisation is None:
        raise AccountError("You must supply a valid authorisation")

    # load the account
    bucket = login_to_service_account()
    account = Accounts(authorisation.user_uid()).get_account(account_name,
                                                             bucket=bucket)

    # validate the authorisation for this account
    authorisation.verify(resource=account.uid())

    balance_status = account.balance_status(bucket=bucket)

    status = 0
    message = "Success"

    return_value = create_return_value(status, message)

    if account:
        return_value["description"] = account.description()
        return_value["overdraft_limit"] = str(account.get_overdraft_limit())

    if balance_status:
        for key in balance_status.keys():
            return_value[key] = str(balance_status[key])

    return return_value
Example #3
0
def run(args):
    """This function will allow anyone to query the current login
       status of the session with passed UID"""

    status = 0
    message = None
    session_status = None

    session_uid = args["session_uid"]
    username = args["username"]

    # generate a sanitised version of the username
    user_account = UserAccount(username)

    # now log into the central identity account to query
    # the current status of this login session
    bucket = login_to_service_account()

    user_session_key = "sessions/%s/%s" % \
        (user_account.sanitised_name(), session_uid)

    try:
        login_session = LoginSession.from_data(
            ObjectStore.get_object_from_json(bucket, user_session_key))
    except:
        login_session = None

    if login_session is None:
        user_session_key = "expired_sessions/%s/%s" % \
                                (user_account.sanitised_name(),
                                    session_uid)

        login_session = LoginSession.from_data(
            ObjectStore.get_object_from_json(bucket, user_session_key))

    if login_session is None:
        raise InvalidSessionError("Cannot find the session '%s'" % session_uid)

    status = 0
    message = "Success: Status = %s" % login_session.status()
    session_status = login_session.status()

    return_value = create_return_value(status, message)

    if session_status:
        return_value["session_status"] = session_status

    return return_value
Example #4
0
def run(args):
    """This function will allow the current user to authorise
       a logout from the current session - this will be authorised
       by signing the request to logout"""

    status = 0
    message = None

    session_uid = args["session_uid"]
    username = args["username"]
    permission = args["permission"]
    signature = string_to_bytes(args["signature"])

    # generate a sanitised version of the username
    user_account = UserAccount(username)

    # now log into the central identity account to query
    # the current status of this login session
    bucket = login_to_service_account()

    user_session_key = "sessions/%s/%s" % \
        (user_account.sanitised_name(), session_uid)

    request_session_key = "requests/%s/%s" % (session_uid[:8], session_uid)

    login_session = LoginSession.from_data(
        ObjectStore.get_object_from_json(bucket, user_session_key))

    if login_session:
        # get the signing certificate from the login session and
        # validate that the permission object has been signed by
        # the user requesting the logout
        cert = login_session.public_certificate()

        cert.verify(signature, permission)

        # the signature was correct, so log the user out. For record
        # keeping purposes we change the loginsession to a logout state
        # and move it to another part of the object store
        if login_session.is_approved():
            login_session.logout()

    # only save sessions that were successfully approved
    if login_session:
        if login_session.is_logged_out():
            expired_session_key = "expired_sessions/%s/%s" % \
                                    (user_account.sanitised_name(),
                                     session_uid)

            ObjectStore.set_object_from_json(bucket, expired_session_key,
                                             login_session.to_data())

    try:
        ObjectStore.delete_object(bucket, user_session_key)
    except:
        pass

    try:
        ObjectStore.delete_object(bucket, request_session_key)
    except:
        pass

    status = 0
    message = "Successfully logged out"

    return_value = create_return_value(status, message)

    return return_value
Example #5
0
def run(args):
    """This function is called to handle admin setup
       of the service, e.g. setting admin passwords,
       introducing trusted services etc.
    """

    status = 0
    message = None
    provisioning_uri = None

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

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

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

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

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

    try:
        remember_device = args["remember_device"]
    except:
        remember_device = False

    # first, do we have an existing Service object? If not,
    # we grant access to the first user!
    bucket = login_to_service_account()

    # The data is stored in the object store at the key _service_info
    # and is encrypted using the value of $SERVICE_PASSWORD
    try:
        service = get_service_info(True)
    except MissingServiceAccountError:
        service = None

    if service:
        if not service.is_accounting_service():
            raise ServiceSetupError(
                "Why is the accounting service info "
                "for a service of type %s" % service.service_type())

        provisioning_uri = service.verify_admin_user(password, otpcode,
                                                     remember_device)
    else:
        # we need to create the service
        service_url = args["service_url"]
        service_type = "accounting"

        service = Service(service_type, service_url)
        provisioning_uri = service.set_admin_password(password)

        # write the service data, encrypted using the service password
        service_password = os.getenv("SERVICE_PASSWORD")
        if service_password is None:
            raise ServiceSetupError(
                "You must supply $SERVICE_PASSWORD "
                "to setup a new service!")

        service_data = service.to_data(service_password)
        ObjectStore.set_object_from_json(bucket, "_service_info", service_data)

    # we are definitely the admin user, so let's add or remove remote services
    if remove_service:
        remove_trusted_service_info(remove_service)

    if new_service:
        service = get_remote_service_info(new_service)

        if new_service:
            set_trusted_service_info(new_service, service)

    status = 0
    message = "Success"

    return_value = create_return_value(status, message)

    if provisioning_uri:
        return_value["provisioning_uri"] = provisioning_uri

    return return_value
Example #6
0
def run(args):
    """This function is called by the user to log in and validate
       that a session is authorised to connect"""

    status = 0
    message = None
    provisioning_uri = None
    assigned_device_uid = None

    short_uid = args["short_uid"]
    username = args["username"]
    password = args["password"]
    otpcode = args["otpcode"]

    try:
        remember_device = args["remember_device"]
    except:
        remember_device = False

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

    # create the user account for the user
    user_account = UserAccount(username)

    # log into the central identity account to query
    # the current status of this login session
    bucket = login_to_service_account()

    # locate the session referred to by this uid
    base_key = "requests/%s" % short_uid
    session_keys = ObjectStore.get_all_object_names(bucket, base_key)

    # try all of the sessions to find the one that the user
    # may be referring to...
    login_session_key = None
    request_session_key = None

    for session_key in session_keys:
        request_session_key = "%s/%s" % (base_key, session_key)
        session_user = ObjectStore.get_string_object(
            bucket, request_session_key)

        # did the right user request this session?
        if user_account.name() == session_user:
            if login_session_key:
                # this is an extremely unlikely edge case, whereby
                # two login requests within a 30 minute interval for the
                # same user result in the same short UID. This should be
                # signified as an error and the user asked to create a
                # new request
                raise LoginError(
                    "You have found an extremely rare edge-case "
                    "whereby two different login requests have randomly "
                    "obtained the same short UID. As we can't work out "
                    "which request is valid, the login is denied. Please "
                    "create a new login request, which will then have a "
                    "new login request UID")
            else:
                login_session_key = session_key

    if not login_session_key:
        raise LoginError(
            "There is no active login request with the "
            "short UID '%s' for user '%s'" % (short_uid, username))

    login_session_key = "sessions/%s/%s" % (user_account.sanitised_name(),
                                            login_session_key)

    # fully load the user account from the object store so that we
    # can validate the username and password
    try:
        account_key = "accounts/%s" % user_account.sanitised_name()
        user_account = UserAccount.from_data(
            ObjectStore.get_object_from_json(bucket, account_key))
    except:
        raise LoginError("No account available with username '%s'" %
                         username)

    if (not remember_device) and device_uid:
        # see if this device has been seen before
        device_key = "devices/%s/%s" % (user_account.sanitised_name(),
                                        device_uid)

        try:
            device_secret = ObjectStore.get_string_object(bucket,
                                                          device_key)
        except:
            device_secret = None

        if device_secret is None:
            raise LoginError(
                "The login device is not recognised. Please try to "
                "log in again using your master one-time-password.")
    else:
        device_secret = None

    # now try to log into this account using the supplied
    # password and one-time-code
    try:
        if device_secret:
            user_account.validate_password(password, otpcode,
                                           device_secret=device_secret)
        elif remember_device:
            (device_secret, provisioning_uri) = \
                        user_account.validate_password(
                                    password, otpcode,
                                    remember_device=True)

            device_uid = str(uuid.uuid4())
            device_key = "devices/%s/%s" % (user_account.sanitised_name(),
                                            device_uid)

            assigned_device_uid = device_uid
        else:
            user_account.validate_password(password, otpcode)
    except:
        # don't leak info about why validation failed
        raise LoginError("The password or OTP code is incorrect")

    # the user is valid - load up the actual login session
    login_session = LoginSession.from_data(
                        ObjectStore.get_object_from_json(bucket,
                                                         login_session_key))

    # we must record the session against which this otpcode has
    # been validated. This is to stop us validating an otpcode more than
    # once (e.g. if the password and code have been intercepted).
    # Any sessions validated using the same code should be treated
    # as immediately suspcious
    otproot = "otps/%s" % user_account.sanitised_name()
    sessions = ObjectStore.get_all_strings(bucket, otproot)

    utcnow = datetime.datetime.utcnow()

    for session in sessions:
        otpkey = "%s/%s" % (otproot, session)
        otpstring = ObjectStore.get_string_object(bucket, otpkey)

        (timestamp, code) = otpstring.split("|||")

        # remove all codes that are more than 10 minutes old. The
        # otp codes are only valid for 3 minutes, so no need to record
        # codes that have been used that are older than that...
        timedelta = utcnow - datetime.datetime.fromtimestamp(
                                                    float(timestamp))

        if timedelta.seconds > 600:
            try:
                ObjectStore.delete_object(bucket, otpkey)
            except:
                pass

        elif code == str(otpcode):
            # Low probability there is some recycling,
            # but very suspicious if the code was validated within the last
            # 10 minutes... (as 3 minute timeout of a code)
            suspect_key = "sessions/%s/%s" % (
                user_account.sanitised_name(), session)

            suspect_session = None

            try:
                suspect_session = LoginSession.from_data(
                        ObjectStore.get_object_from_json(bucket,
                                                         suspect_key))
            except:
                pass

            if suspect_session:
                suspect_session.set_suspicious()
                ObjectStore.set_object_from_json(bucket, suspect_key,
                                                 suspect_session.to_data())

            raise LoginError(
                "Cannot authorise the login as the one-time-code "
                "you supplied has already been used within the last 10 "
                "minutes. The chance of this happening is really low, so "
                "we are treating this as a suspicious event. You need to "
                "try another code. Meanwhile, the other login that used "
                "this code has been put into a 'suspicious' state.")

    # record the value and timestamp of when this otpcode was used
    otpkey = "%s/%s" % (otproot, login_session.uuid())
    otpstring = "%s|||%s" % (datetime.datetime.utcnow().timestamp(),
                             otpcode)

    ObjectStore.set_string_object(bucket, otpkey, otpstring)

    login_session.set_approved()

    # write this session back to the object store
    ObjectStore.set_object_from_json(bucket, login_session_key,
                                     login_session.to_data())

    # save the device secret as everything has now worked
    if assigned_device_uid:
        ObjectStore.set_string_object(bucket, device_key,
                                      device_secret)

    # finally, remove this from the list of requested logins
    try:
        ObjectStore.delete_object(bucket, request_session_key)
    except:
        pass

    status = 0
    message = "Success: Status = %s" % login_session.status()

    return_value = create_return_value(status, message)

    if provisioning_uri:
        return_value["provisioning_uri"] = provisioning_uri
        return_value["device_uid"] = assigned_device_uid

    return return_value
def bucket(tmpdir_factory):
    try:
        return login_to_service_account()
    except:
        d = tmpdir_factory.mktemp("objstore")
        return login_to_service_account(str(d))
Example #8
0
def run(args):
    """This function will allow a user to register an account with a
       username and password"""

    status = 0
    message = None
    provisioning_uri = None

    username = args["username"]
    password = args["password"]

    # generate a sanitised version of the username
    user_account = UserAccount(username)

    # generate the encryption keys and otp secret
    privkey = PrivateKey()
    pubkey = privkey.public_key()
    otp = OTP()

    provisioning_uri = otp.provisioning_uri(username)

    # save the encrypted private key (encrypted using the user's password)
    # and encrypted OTP secret (encrypted using the public key)
    user_account.set_keys(privkey.bytes(password), pubkey.bytes(),
                          otp.encrypt(pubkey))

    # remove the key and password from memory
    privkey = None
    password = None

    # now log into the central identity account to either register
    # the user, or to update to a new password
    bucket = login_to_service_account()
    account_key = "accounts/%s" % user_account.sanitised_name()

    try:
        existing_data = ObjectStore.get_object_from_json(bucket,
                                                         account_key)
    except:
        existing_data = None

    message = "Created a new account for '%s'" % username
    status = 0

    if existing_data is None:
        # save the new account details
        ObjectStore.set_object_from_json(bucket, account_key,
                                         user_account.to_data())

        # need to update the "whois" database with the uuid of this user
        ObjectStore.set_string_object(bucket,
                                      "whois/%s" % user_account.uuid(),
                                      user_account.username())
    else:
        # The account already exists. See if this is a password change
        # request
        old_password = None

        try:
            old_password = args["old_password"]
        except:
            raise ExistingAccountError(
                "An account by this name already exists!")

        if old_password != password:
            # this is a change of password request - validate that
            # the existing password unlocks the existing key
            user_account = UserAccount.from_data(existing_data)

            testkey = PrivateKey.read_bytes(user_account.private_key(),
                                            old_password)

            # decrypt the old secret
            old_secret = testkey.decrypt(user_account.otp_secret())

            # now encrypt the secret with the new key
            new_key = PublicKey.read_bytes(pubkey)
            new_secret = new_key.encrypt(old_secret)

            user_account.set_keys(privkey, pubkey, new_secret)

            # save the new account details
            ObjectStore.set_object_from_json(bucket, account_key,
                                             user_account.to_data())

            message = "Updated the password for '%s'" % username
        else:
            message = "No need to change account '%s'" % username

    return_value = create_return_value(status, message)

    if provisioning_uri:
        return_value["provisioning_uri"] = provisioning_uri

    return return_value
def bucket(tmpdir_factory):
    d = tmpdir_factory.mktemp("simple_objstore")
    return login_to_service_account(str(d))
Example #10
0
def run(args):
    """This function will allow anyone to obtain the public
       keys for the passed login session of a user with
       a specified login UID"""

    public_key = None
    public_cert = None
    login_status = None
    logout_timestamp = None

    session_uid = args["session_uid"]
    username = args["username"]

    # generate a sanitised version of the username
    user_account = UserAccount(username)

    # now log into the central identity account to query
    # the current status of this login session
    bucket = login_to_service_account()

    user_session_key = "sessions/%s/%s" % \
            (user_account.sanitised_name(), session_uid)

    try:
        login_session = LoginSession.from_data(
            ObjectStore.get_object_from_json(bucket, user_session_key))
    except:
        login_session = None

    if login_session is None:
        user_session_key = "expired_sessions/%s/%s" % \
                                (user_account.sanitised_name(),
                                 session_uid)

        login_session = LoginSession.from_data(
            ObjectStore.get_object_from_json(bucket, user_session_key))

    if login_session is None:
        raise InvalidSessionError("Cannot find the session '%s'" % session_uid)

    # only send valid keys if the user had logged in!
    if login_session.is_approved():
        public_key = login_session.public_key()
        public_cert = login_session.public_certificate()

    elif login_session.is_logged_out():
        public_cert = login_session.public_certificate()
        logout_timestamp = login_session.logout_time().timestamp()

    else:
        raise InvalidSessionError("You cannot get the keys for a session "
                                  "for which the user has not logged in!")

    login_status = login_session.status()

    status = 0
    message = "Success: Status = %s" % login_session.status()

    return_value = create_return_value(status, message)

    if public_key:
        return_value["public_key"] = public_key.to_data()

    if public_cert:
        return_value["public_cert"] = public_cert.to_data()

    if login_status:
        return_value["login_status"] = str(login_status)

    if logout_timestamp:
        return_value["logout_timestamp"] = logout_timestamp

    return return_value
Example #11
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
    """

    status = 0
    message = None

    transaction_records = None
    invoice_value = None
    invoice_user = None

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

    try:
        transaction = Transaction.from_data(args["transaction"])
    except:
        transaction = Transaction(args["value"],
                                  "Deposit on %s" % datetime.datetime.now())

    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")

    if transaction.value() > 0:
        authorisation.verify()
        user_uid = authorisation.user_uid()

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

        # deposits are made by transferring funds from the user's
        # 'billing' account to their 'deposits' account.
        deposit_account = accounts.create_account("deposits",
                                                  "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 < -50.0:
            # there are sufficient funds that need to be transferred that
            # it is worth really charging the user
            invoice_user = user_uid
            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)

    status = 0
    message = "Success"

    return_value = create_return_value(status, message)

    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
Example #12
0
def run(args):
    """This function will allow anyone to query who matches
       the passed UID or username (map from one to the other)"""

    status = 0
    message = None
    user_uid = None
    username = None
    public_key = None
    public_cert = None
    logout_timestamp = None
    login_status = None

    try:
        user_uid = args["user_uid"]
    except:
        pass

    try:
        username = args["username"]
    except:
        pass

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

    bucket = None
    user_account = None

    if user_uid is None and username is None:
        raise WhoisLookupError(
            "You must supply either a username or user_uid to look up...")

    elif user_uid is None:
        # look up the user_uid from the username
        user_account = UserAccount(username)
        bucket = login_to_service_account()
        user_key = "accounts/%s" % user_account.sanitised_name()

        try:
            user_account = UserAccount.from_data(
                ObjectStore.get_object_from_json(bucket, user_key))
        except:
            raise WhoisLookupError("Cannot find an account for name '%s'" %
                                   username)

        user_uid = user_account.uid()

    elif username is None:
        # look up the username from the uuid
        bucket = login_to_service_account()

        uid_key = "whois/%s" % user_uid

        try:
            username = ObjectStore.get_string_object(bucket, uid_key)
        except:
            raise WhoisLookupError("Cannot find an account for user_uid '%s'" %
                                   user_uid)

    else:
        raise WhoisLookupError("You must only supply one of the username "
                               "or user_uid to look up - not both!")

    if session_uid:
        # now look up the public signing key for this session, if it is
        # a valid login session
        if user_account is None:
            user_account = UserAccount(username)

        user_session_key = "sessions/%s/%s" % \
            (user_account.sanitised_name(), session_uid)

        try:
            login_session = LoginSession.from_data(
                ObjectStore.get_object_from_json(bucket, user_session_key))
        except:
            login_session = None

        if login_session is None:
            user_session_key = "expired_sessions/%s/%s" % \
                                    (user_account.sanitised_name(),
                                     session_uid)

            login_session = LoginSession.from_data(
                ObjectStore.get_object_from_json(bucket, user_session_key))

        if login_session is None:
            raise InvalidSessionError("Cannot find the session '%s'" %
                                      session_uid)

        if login_session.is_approved():
            public_key = login_session.public_key()
            public_cert = login_session.public_certificate()

        elif login_session.is_logged_out():
            public_cert = login_session.public_certificate()
            logout_timestamp = login_session.logout_time().timestamp()

        else:
            raise InvalidSessionError("You cannot get the keys for a session "
                                      "for which the user has not logged in!")

        login_status = login_session.status()

    status = 0
    message = "Success"

    return_value = create_return_value(status, message)

    if user_uid:
        return_value["user_uid"] = str(user_uid)

    if username:
        return_value["username"] = str(username)

    if public_key:
        return_value["public_key"] = public_key.to_data()

    if public_cert:
        return_value["public_cert"] = public_cert.to_data()

    if logout_timestamp:
        return_value["logout_timestamp"] = logout_timestamp

    if login_status:
        return_value["login_status"] = str(login_status)

    return return_value
Example #13
0
def run(args):
    """This function will allow a user to request a new session
       that will be validated by the passed public key and public
       signing certificate. This will return a URL that the user
       must connect to to then log in and validate that request.
    """

    status = 0
    message = None
    login_url = None
    login_uid = None
    user_uid = None

    username = args["username"]
    public_key = PublicKey.from_data(args["public_key"])
    public_cert = PublicKey.from_data(args["public_certificate"])

    ip_addr = None
    hostname = None
    login_message = None

    try:
        ip_addr = args["ipaddr"]
    except:
        pass

    try:
        hostname = args["hostname"]
    except:
        pass

    try:
        login_message = args["message"]
    except:
        pass

    # generate a sanitised version of the username
    user_account = UserAccount(username)

    # Now generate a login session for this request
    login_session = LoginSession(public_key, public_cert, ip_addr, hostname,
                                 login_message)

    # now log into the central identity account to record
    # that a request to open a login session has been opened
    bucket = login_to_service_account()

    # first, make sure that the user exists...
    account_key = "accounts/%s" % user_account.sanitised_name()

    try:
        existing_data = ObjectStore.get_object_from_json(bucket, account_key)
    except:
        existing_data = None

    if existing_data is None:
        raise InvalidLoginError("There is no user with name '%s'" % username)

    user_account = UserAccount.from_data(existing_data)
    user_uid = user_account.uid()

    # first, make sure that the user doens't have too many open
    # login sessions at once - this prevents denial of service
    user_session_root = "sessions/%s" % user_account.sanitised_name()

    open_sessions = ObjectStore.get_all_object_names(bucket, user_session_root)

    # take the opportunity to prune old user login sessions
    prune_expired_sessions(bucket, user_account, user_session_root,
                           open_sessions)

    # this is the key for the session in the object store
    user_session_key = "%s/%s" % (user_session_root, login_session.uuid())

    ObjectStore.set_object_from_json(bucket, user_session_key,
                                     login_session.to_data())

    # we will record a pointer to the request using the short
    # UUID. This way we can give a simple URL. If there is a clash,
    # then we will use the username provided at login to find the
    # correct request from a much smaller pool (likely < 3)
    request_key = "requests/%s/%s" % (login_session.short_uuid(),
                                      login_session.uuid())

    ObjectStore.set_string_object(bucket, request_key, user_account.name())

    status = 0
    # the login URL is the URL of this identity service plus the
    # short UID of the session
    login_url = "%s/s?id=%s" % (get_service_info().service_url(),
                                login_session.short_uuid())

    login_uid = login_session.uuid()

    message = "Success: Login via %s" % login_url

    return_value = create_return_value(status, message)

    if login_uid:
        return_value["session_uid"] = login_uid

    if login_url:
        return_value["login_url"] = login_url
    else:
        return_value["login_url"] = None

    if user_uid:
        return_value["user_uid"] = user_uid

    return return_value