Authomatic
is an authorization / authentication
client library for Python web applications
inspired by Alex Vagin’s Simpleauth.
In fact, I almost named it Deathsimpleauth,
but that name would be too long
for a succinct library.

- Tweet

# Source code for authomatic.extras.gae.openid

    # We need absolute iport to import from openid library which has the same name as this module
    from __future__ import absolute_import
    from google.appengine.ext import ndb
    import datetime
    import logging
    import openid.store.interface

    class NDBOpenIDStore(ndb.Expando, openid.store.interface.OpenIDStore):
        """
        |gae| `NDB <https://developers.google.com/appengine/docs/python/ndb/>`_
        based implementation of the :class:`openid.store.interface.OpenIDStore`
        interface of the `python-openid`_ library.
        """

        serialized = ndb.StringProperty()
        expiration_date = ndb.DateTimeProperty()
        # we need issued to sort by most recently issued
        issued = ndb.IntegerProperty()

        # Logging method.
        _log = lambda level, message: None

        @classmethod
        def storeAssociation(cls, server_url, association):
            # store an entity with key = server_url

            issued = datetime.datetime.fromtimestamp(association.issued)
            lifetime = datetime.timedelta(0, association.lifetime)

            expiration_date = issued + lifetime
            entity = cls.get_or_insert(association.handle, parent=ndb.Key('ServerUrl', server_url))

            entity.serialized = association.serialize()
            entity.expiration_date = expiration_date
            entity.issued = association.issued

            cls._log(logging.DEBUG, 'NDBOpenIDStore: Putting OpenID association to datastore.')

            entity.put()

        @classmethod
        def cleanupAssociations(cls):

            # query for all expired
            cls._log(logging.DEBUG, 'NDBOpenIDStore: Querying datastore for OpenID associations.')
            query = cls.query(cls.expiration_date <= datetime.datetime.now())

            # fetch keys only
            expired = query.fetch(keys_only=True)

            # delete all expired
            cls._log(logging.DEBUG, 'NDBOpenIDStore: Deleting expired OpenID associations from datastore.')
            ndb.delete_multi(expired)

            return len(expired)

        @classmethod
        def getAssociation(cls, server_url, handle=None):

            cls.cleanupAssociations()

            if handle:
                key = ndb.Key('ServerUrl', server_url, cls, handle)
                cls._log(logging.DEBUG, 'NDBOpenIDStore: Getting OpenID association from datastore by key.')
                entity = key.get()
            else:
                # return most recently issued association
                cls._log(logging.DEBUG, 'NDBOpenIDStore: Querying datastore for OpenID associations by ancestor.')
                entity = cls.query(ancestor=ndb.Key('ServerUrl', server_url)).order(-cls.issued).get()

            if entity and entity.serialized:
                return openid.association.Association.deserialize(entity.serialized)

        @classmethod
        def removeAssociation(cls, server_url, handle):
            key = ndb.Key('ServerUrl', server_url, cls, handle)
            cls._log(logging.DEBUG, 'NDBOpenIDStore: Getting OpenID association from datastore by key.')
            if key.get():
                cls._log(logging.DEBUG, 'NDBOpenIDStore: Deleting OpenID association from datastore.')
                key.delete()
                return True

        @classmethod
        def useNonce(cls, server_url, timestamp, salt):

            # check whether there is already an entity with the same ancestor path in the datastore
            key = ndb.Key('ServerUrl', str(server_url) or 'x', 'TimeStamp', str(timestamp), cls, str(salt))

            cls._log(logging.DEBUG, 'NDBOpenIDStore: Getting OpenID nonce from datastore by key.')
            result = key.get()

            if result:
                # if so, the nonce is not valid so return False
                cls._log(logging.WARNING, 'NDBOpenIDStore: Nonce was allready used!')
                return False
            else:
                # if not, store the key to datastore and return True
                nonce = cls(key=key)
                nonce.expiration_date = datetime.datetime.fromtimestamp(timestamp) + datetime.timedelta(0, openid.store.nonce.SKEW)
                cls._log(logging.DEBUG, 'NDBOpenIDStore: Putting new nonce to datastore.')
                nonce.put()
                return True

        @classmethod
        def cleanupNonces(cls):
            # get all expired nonces
            cls._log(logging.DEBUG, 'NDBOpenIDStore: Querying datastore for OpenID nonces ordered by expiration date.')
            expired = cls.query().filter(cls.expiration_date <= datetime.datetime.now()).fetch(keys_only=True)

            # delete all expired
            cls._log(logging.DEBUG, 'NDBOpenIDStore: Deleting expired OpenID nonces from datastore.')
            ndb.delete_multi(expired)

            return len(expired)