Пример #1
0
    def fully_unlock(self):
        """This fully unlocks the mutex, removing all levels
           of recursion

           Returns:
                None
        """
        if self._is_locked == 0:
            return

        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.ObjectStore import get_datetime_now as _get_datetime_now

        try:
            holder = _ObjectStore.get_string_object(self._bucket, self._key)
        except:
            holder = None

        if holder == self._lockstring:
            # we hold the mutex - delete the key
            _ObjectStore.delete_object(self._bucket, self._key)

        self._lockstring = None
        self._is_locked = 0

        if self._end_lease < _get_datetime_now():
            self._end_lease = None
            from Acquire.ObjectStore import MutexTimeoutError
            raise MutexTimeoutError("The lease on this mutex expired before "
                                    "this mutex was unlocked!")
        else:
            self._end_lease = None
Пример #2
0
def untrust_service(service):
    """Stop trusting the passed service. This will remove the service
       as being trusted. You must pass in a valid admin_user authorisation
       for this service
    """
    from Acquire.Service import is_running_service as _is_running_service

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

        bucket = _get_service_account_bucket()
        urlkey = "_trusted/url/%s" % _url_to_encoded(service.canonical_url())
        uidkey = "_trusted/uid/%s" % service.uid()

        # delete the trusted service by both canonical_url and uid
        try:
            _ObjectStore.delete_object(bucket, uidkey)
        except:
            pass

        try:
            _ObjectStore.delete_object(bucket, urlkey)
        except:
            pass

        from Acquire.Service import clear_services_cache \
            as _clear_services_cache
        _clear_services_cache()
    else:
        from Acquire.Client import Wallet as _Wallet
        wallet = _Wallet()
        wallet.remove_service(service)
Пример #3
0
    def _delete_note(self, note, bucket=None):
        """Internal function called to delete the passed note from the
           record. This is unsafe and should only be called from
           DebitNote.return_value or CreditNote.return_value (which
           themselves are only called from Ledger)
        """
        if note is None:
            return

        if isinstance(note, _DebitNote) or isinstance(note, _CreditNote):
            item_key = "%s/%s" % (self._key(), note.uid())

            if bucket is None:
                bucket = _login_to_service_account()

            # remove the note
            try:
                _ObjectStore.delete_object(bucket, item_key)
            except:
                pass

            # now remove all day-balances from the day before this note
            # to today. Hopefully this will prevent any ledger errors...
            day0 = _datetime.datetime.fromtimestamp(
                note.timestamp()).toordinal() - 1
            day1 = _datetime.datetime.now().toordinal()

            for day in range(day0, day1 + 1):
                balance_key = self._get_balance_key(
                    _datetime.datetime.fromordinal(day))

                try:
                    _ObjectStore.delete_object(bucket, balance_key)
                except:
                    pass
Пример #4
0
def remove_trusted_service_info(service_url):
    """Remove the passed 'service_url' from the list of trusted services"""
    bucket = _login_to_service_account()
    try:
        _ObjectStore.delete_object(bucket,
                                   "services/%s" % url_to_encoded(service_url))
    except:
        pass
Пример #5
0
    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)
Пример #6
0
def delete_object(bucket, key):
    """ Delete the object at key in bucket

        Args:
            bucket (str): Bucket containing data
            key (str): Key for data in bucket
        Returns:
            None
    """
    return ObjectStore.delete_object(bucket=bucket, key=key)
Пример #7
0
    def _set_status(self, status):
        """Internal function to set the status of the session.
           This ensures that the data for the session is saved
           into the correct part of the object store
        """
        if self.is_null():
            raise PermissionError(
                "Cannot set the status of a null LoginSession")

        if status not in [
                "approved", "pending", "denied", "suspicious", "logged_out"
        ]:
            raise ValueError("Cannot set an invalid status '%s'" % status)

        if status == self._status:
            return

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

        bucket = _get_service_account_bucket()
        key = self._get_key()

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

        self._status = status
        key = self._get_key()
        _ObjectStore.set_object_from_json(bucket=bucket,
                                          key=key,
                                          data=self.to_data())
        key = "%s/status/%s" % (_sessions_key, self._uid)
        _ObjectStore.set_string_object(bucket=bucket,
                                       key=key,
                                       string_data=status)
Пример #8
0
    def create_account(self,
                       name,
                       description=None,
                       overdraft_limit=None,
                       bucket=None):
        """Create a new account called 'name' in this group. This will
           return the existing account if it already exists
        """
        if name is None:
            raise ValueError("You must pass a name of the new account")

        account_key = self._account_key(name)

        if bucket is None:
            bucket = _login_to_service_account()

        try:
            account_uid = _ObjectStore.get_string_object(bucket, account_key)
        except:
            account_uid = None

        if account_uid is not None:
            # this account already exists - just return it
            account = _Account(uid=account_uid, bucket=bucket)

            if overdraft_limit is not None:
                account.set_overdraft_limit(overdraft_limit, bucket=bucket)

            return account

        # make sure that no-one has created this account before
        m = _Mutex(account_key, timeout=600, lease_time=600, bucket=bucket)

        try:
            account_uid = _ObjectStore.get_string_object(bucket, account_key)
        except:
            account_uid = None

        if account_uid is not None:
            m.unlock()
            # this account already exists - just return it
            account = _Account(uid=account_uid, bucket=bucket)

            if overdraft_limit is not None:
                account.set_overdraft_limit(overdraft_limit, bucket=bucket)

            return account

        # write a temporary UID to the object store so that we
        # can ensure we are the only function to create it
        try:
            _ObjectStore.set_string_object(bucket, account_key,
                                           "under_construction")
        except:
            m.unlock()
            raise

        m.unlock()

        # ok - we are the only function creating this account. Let's try
        # to create it properly
        try:
            account = _Account(name=name,
                               description=description,
                               bucket=bucket)
        except:
            try:
                _ObjectStore.delete_object(bucket, account_key)
            except:
                pass

            raise

        if overdraft_limit is not None:
            account.set_overdraft_limit(overdraft_limit, bucket=bucket)

        _ObjectStore.set_string_object(bucket, account_key, account.uid())

        return account
Пример #9
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
Пример #10
0
    def _debit(self, transaction, authorisation, is_provisional, bucket=None):
        """Debit the value of the passed transaction from this account based
           on the authorisation contained
           in 'authorisation'. This will create a unique ID (UID) for
           this debit and will return this together with the timestamp of the
           debit. If this transaction 'is_provisional' then it will be
           recorded as a liability.

           The UID will encode both the date of the debit and provide a random
           ID that together can be used to identify the transaction associated
           with this debit in the future.

           This will raise an exception if the debit cannot be completed, e.g.
           if the authorisation is invalid, if the debit exceeds a limit or
           there are insufficient funds in the account

           Note that this function is private as it should only be called
           by the DebitNote class
        """
        if self.is_null() or transaction.value() <= 0:
            return None

        if not isinstance(transaction, _Transaction):
            raise TypeError("The passed transaction must be a Transaction!")

        self.assert_valid_authorisation(authorisation)

        if bucket is None:
            bucket = _login_to_service_account()

        if self.available_balance(bucket) < transaction.value():
            raise InsufficientFundsError(
                "You cannot debit '%s' from account %s as there "
                "are insufficient funds in this account." %
                (transaction, str(self)))

        # create a UID and timestamp for this debit and record
        # it in the account
        now = self._get_safe_now()

        # we need to record the exact timestamp of this debit...
        timestamp = now.timestamp()

        # and to create a key to find this debit later. The key is made
        # up from the date and timestamp of the debit and a random string
        day_key = "%4d-%02d-%02d/%s" % (now.year, now.month, now.day,
                                        timestamp)
        uid = "%s/%s" % (day_key, str(_uuid.uuid4())[0:8])

        # the key in the object store is a combination of the key for this
        # account plus the uid for the debit plus the actual debit value.
        # We record the debit value in the key so that we can accumulate
        # the balance from just the key names
        if is_provisional:
            encoded_value = _TransactionInfo.encode(
                _TransactionCode.CURRENT_LIABILITY, transaction.value())
        else:
            encoded_value = _TransactionInfo.encode(_TransactionCode.DEBIT,
                                                    transaction.value())

        item_key = "%s/%s/%s" % (self._key(), uid, encoded_value)

        # create a line_item for this debit and save it to the object store
        line_item = _LineItem(uid, authorisation)

        _ObjectStore.set_object_from_json(bucket, item_key,
                                          line_item.to_data())

        if self.is_beyond_overdraft_limit(bucket):
            # This transaction has helped push the account beyond the
            # overdraft limit. Delete the transaction and raise
            # an InsufficientFundsError

            _ObjectStore.delete_object(bucket, item_key)
            raise InsufficientFundsError(
                "You cannot debit '%s' from account %s as there "
                "are insufficient funds in this account." %
                (transaction, str(self)))

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

        this_service = _get_this_service(need_private_access=False)

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

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

        bucket = self.get_bucket()

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

        service = None

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

        if service is not None:
            must_write = False

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

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

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

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

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

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

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

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

        # we only get here if we can't find the service on this registry.
        # In the future, we will use the initial part of the UID of
        # the service to ask its registering registry for its data.
        # For now, we just raise an error
        from Acquire.Service import MissingServiceError
        raise MissingServiceError(
            "No service available: service_url=%s  service_uid=%s" %
                                  (service_url, service_uid))
Пример #12
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
Пример #13
0
    def create_account(self,
                       name,
                       description=None,
                       overdraft_limit=None,
                       bucket=None,
                       authorisation=None):
        """Create a new account called 'name' in this group. This will
           return the existing account if it already exists

           Args:
                name (str): Name of account to create
                description (default=None): Description of account
                overdraft_limit (int, default=None): Limit of overdraft
                bucket (dict, default=None): Bucket to load data from

            Returns:
                Account: New Account object
        """
        if name is None:
            raise ValueError("You must pass a name of the new account")

        from Acquire.Identity import Authorisation as _Authorisation

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

            if self._user_guid is not None:
                if self._user_guid != authorisation.user_guid():
                    raise PermissionError(
                        "The same user who opened this accounts group (%s) "
                        "must create accounts in this group (%s)" %
                        (self._user_guid, authorisation.user_guid()))

            authorisation.verify("create_account %s" % name)

        self._assert_is_writeable()

        account_key = self._account_key(name)

        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.Accounting import Account as _Account

        try:
            account_uid = _ObjectStore.get_string_object(bucket, account_key)
        except:
            account_uid = None

        if account_uid is not None:
            # this account already exists - just return it
            account = _Account(uid=account_uid, bucket=bucket)

            if account.group_name() != self.name():
                account.set_group(self)

            if overdraft_limit is not None:
                account.set_overdraft_limit(overdraft_limit, bucket=bucket)

            return account

        # make sure that no-one has created this account before
        from Acquire.ObjectStore import Mutex as _Mutex
        m = _Mutex(account_key, timeout=600, lease_time=600, bucket=bucket)

        try:
            account_uid = _ObjectStore.get_string_object(bucket, account_key)
        except:
            account_uid = None

        if account_uid is not None:
            m.unlock()
            # this account already exists - just return it
            account = _Account(uid=account_uid, bucket=bucket)

            if account.group_name() != self.name():
                account.set_group(self)

            if overdraft_limit is not None:
                account.set_overdraft_limit(overdraft_limit, bucket=bucket)

            return account

        # write a temporary UID to the object store so that we
        # can ensure we are the only function to create it
        try:
            _ObjectStore.set_string_object(bucket, account_key,
                                           "under_construction")
        except:
            m.unlock()
            raise

        m.unlock()

        # ok - we are the only function creating this account. Let's try
        # to create it properly. The account is created with the same
        # ACLRules of the group.
        try:
            from Acquire.Identity import ACLRules as _ACLRules
            account = _Account(name=name,
                               description=description,
                               group_name=self.name(),
                               aclrules=_ACLRules.inherit(),
                               bucket=bucket)
        except:
            try:
                _ObjectStore.delete_object(bucket, account_key)
            except:
                pass

            raise

        if overdraft_limit is not None:
            account.set_overdraft_limit(overdraft_limit, bucket=bucket)

        _ObjectStore.set_string_object(bucket, account_key, account.uid())

        return account
Пример #14
0
def prune_expired_sessions(bucket, user_account, root, sessions, log=[]):
    """This function will scan through all open requests and
       login sessions and will prune away old, expired or otherwise
       weird sessions. It will also use the ipaddress of the source
       to rate limit or blacklist sources"""

    for name in sessions:
        key = "%s/%s" % (root, name)
        request_key = "requests/%s/%s" % (name[:8], name)

        try:
            session = ObjectStore.get_object_from_json(bucket, key)
        except:
            log.append("Session %s does not exist!" % name)
            session = None

        if session:
            should_delete = False
            should_logout = False

            try:
                session = LoginSession.from_data(session)
                if session.is_approved() or session.is_suspicious():
                    if session.hours_since_creation() > user_account \
                                                            .login_timeout():
                        should_logout = True
                        should_delete = True
                else:
                    if session.hours_since_creation() > user_account \
                                                    .login_request_timeout():
                        log.append("Expired login request: %s > %s" %
                                   (session.hours_since_creation(),
                                    user_account.login_request_timeout()))
                        should_delete = True
            except Exception as e:
                # this is corrupt - delete it
                log.append("Deleting session as corrupt? %s" % str(e))
                should_delete = True

            if should_logout:
                # auto-logout expired sessions
                log.append("Auto-logging out expired session '%s'" % key)
                session.logout()
                expire_session_key = "expired_sessions/%s/%s" % \
                                     (user_account.sanitised_name(),
                                      session.uuid())

                ObjectStore.set_object_from_json(bucket, expire_session_key,
                                                 session.to_data())

            # now delete any expired sessions
            if should_delete:
                log.append("Deleting expired session '%s'" % key)

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

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