Implement a simple credential cache

master
Florent Daigniere 4 years ago
parent 9c57f2ac39
commit ae9206e968

@ -305,6 +305,7 @@ class User(Base, Email):
"""
__tablename__ = "user"
_ctx = None
_credential_cache = {}
domain = db.relationship(Domain,
backref=db.backref('users', cascade='all, delete-orphan'))
@ -382,6 +383,17 @@ class User(Base, Email):
return User._ctx
def check_password(self, password):
cache_result = self._credential_cache.get(self.get_id())
current_salt = self.password.split('$')[3] if len(self.password.split('$')) == 5 else None
if cache_result and current_salt:
cache_salt, cache_hash = cache_result
if cache_salt == current_salt:
return hash.pbkdf2_sha256.verify(password, cache_hash)
else:
# the cache is local per gunicorn; the password has changed
# so the local cache can be invalidated
del self._credential_cache[self.get_id()]
reference = self.password
# strip {scheme} if that's something mailu has added
# passlib will identify *crypt based hashes just fine
@ -396,6 +408,17 @@ class User(Base, Email):
self.password = new_hash
db.session.add(self)
db.session.commit()
if result:
"""The credential cache uses a low number of rounds to be fast.
While it's not meant to be persisted to cold-storage, no additional measures
are taken to ensure it isn't (mlock(), encrypted swap, ...) on the basis that
we have little control over GC and string interning anyways.
An attacker that can dump the process' memory is likely to find credentials
in clear-text regardless of the presence of the cache.
"""
self._credential_cache[self.get_id()] = (self.password.split('$')[3], hash.pbkdf2_sha256.using(rounds=1).hash(password))
return result
def set_password(self, password, hash_scheme=None, raw=False):

@ -0,0 +1 @@
Add a credential cache to speedup authentication requests.
Loading…
Cancel
Save