|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
# We need absolute import to import from openid library which has the same
|
|
|
# name as this module
|
|
|
|
|
|
import logging
|
|
|
import datetime
|
|
|
|
|
|
from google.appengine.ext import ndb
|
|
|
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()
|
|
|
|
|
|
@staticmethod
|
|
|
def _log(*args, **kwargs):
|
|
|
pass
|
|
|
|
|
|
@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,
|
|
|
u'NDBOpenIDStore: Putting OpenID association to datastore.')
|
|
|
|
|
|
entity.put()
|
|
|
|
|
|
@classmethod
|
|
|
def cleanupAssociations(cls):
|
|
|
# query for all expired
|
|
|
cls._log(
|
|
|
logging.DEBUG,
|
|
|
u'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,
|
|
|
u'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,
|
|
|
u'NDBOpenIDStore: Getting OpenID association from datastore by key.')
|
|
|
entity = key.get()
|
|
|
else:
|
|
|
# return most recently issued association
|
|
|
cls._log(
|
|
|
logging.DEBUG,
|
|
|
u'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,
|
|
|
u'NDBOpenIDStore: Getting OpenID association from datastore by key.')
|
|
|
if key.get():
|
|
|
cls._log(
|
|
|
logging.DEBUG,
|
|
|
u'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,
|
|
|
u'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,
|
|
|
u'NDBOpenIDStore: Nonce was already 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,
|
|
|
u'NDBOpenIDStore: Putting new nonce to datastore.')
|
|
|
nonce.put()
|
|
|
return True
|
|
|
|
|
|
@classmethod
|
|
|
def cleanupNonces(cls):
|
|
|
# get all expired nonces
|
|
|
cls._log(
|
|
|
logging.DEBUG,
|
|
|
u'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,
|
|
|
u'NDBOpenIDStore: Deleting expired OpenID nonces from datastore.')
|
|
|
ndb.delete_multi(expired)
|
|
|
|
|
|
return len(expired)
|
|
|
|