This repository has been archived by the owner on Jul 30, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 512
in memory cache verify password implementation #839
Open
eregnier
wants to merge
1
commit into
pallets-eco:develop
Choose a base branch
from
eregnier:develop
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from flask import current_app | ||
from cachetools import TTLCache | ||
|
||
|
||
class VerifyHashCache: | ||
"""Cache handler to make it quick password check by bypassing | ||
already checked passwords against exact same couple of token/password. | ||
This cache handler is more efficient on small apps that | ||
run on few processes as cache is only shared between threads.""" | ||
|
||
def __init__(self): | ||
ttl = current_app.config.get("VERIFY_HASH_CACHE_TTL", 60 * 5) | ||
max_size = current_app.config.get("VERIFY_HASH_CACHE_MAX_SIZE", 500) | ||
self._cache = TTLCache(max_size, ttl) | ||
|
||
def has_verify_hash_cache(self, user): | ||
"""Check given user id is in cache.""" | ||
return self._cache.get(user.id) | ||
|
||
def set_cache(self, user): | ||
"""When a password is checked, then result is put in cache.""" | ||
self._cache[user.id] = True | ||
|
||
def clear(self): | ||
"""Clear cache""" | ||
self._cache.clear() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
test_cache | ||
~~~~~~~~~~~~ | ||
|
||
verify hash cache tests | ||
""" | ||
|
||
from flask_security.cache import VerifyHashCache | ||
from flask_security.core import _request_loader, local_cache | ||
|
||
|
||
class MockRequest: | ||
@property | ||
def headers(self): | ||
return {"token-header": "mock-token-header"} | ||
|
||
@property | ||
def args(self): | ||
return {} | ||
|
||
@property | ||
def is_json(self): | ||
return True | ||
|
||
def get_json(self, silent=None): | ||
return {} | ||
|
||
|
||
class MockUser: | ||
def __init__(self, id, password): | ||
self.id = id | ||
self.password = password | ||
|
||
|
||
class MockExtensionSecurity: | ||
@property | ||
def token_authentication_header(self): | ||
return "token-header" | ||
|
||
@property | ||
def token_authentication_key(self): | ||
return "token-key" | ||
|
||
@property | ||
def login_manager(self): | ||
class MockLoginManager: | ||
def anonymous_user(self): | ||
return None | ||
|
||
return MockLoginManager() | ||
|
||
@property | ||
def remember_token_serializer(self): | ||
class MockLoader: | ||
def loads(self, token, max_age): | ||
return [1, "token"] | ||
|
||
return MockLoader() | ||
|
||
@property | ||
def token_max_age(self): | ||
return 1 | ||
|
||
@property | ||
def datastore(self): | ||
class MockDataStore: | ||
def find_user(self, id=None): | ||
return MockUser(id, "token") | ||
|
||
return MockDataStore() | ||
|
||
@property | ||
def hashing_context(self): | ||
class MockHashingContext: | ||
def verify(self, encoded_data, hashed_data): | ||
return encoded_data.decode() == hashed_data | ||
|
||
return MockHashingContext() | ||
|
||
|
||
def test_verify_password_cache_init(app): | ||
with app.app_context(): | ||
vhc = VerifyHashCache() | ||
assert len(vhc._cache) == 0 | ||
assert vhc._cache.ttl == 60 * 5 | ||
assert vhc._cache.maxsize == 500 | ||
app.config["VERIFY_HASH_CACHE_TTL"] = 10 | ||
app.config["VERIFY_HASH_CACHE_MAX_SIZE"] = 10 | ||
vhc = VerifyHashCache() | ||
assert vhc._cache.ttl == 10 | ||
assert vhc._cache.maxsize == 10 | ||
|
||
|
||
def test_verify_password_cache_set_get(app): | ||
class MockUser: | ||
def __init__(self, id): | ||
self.id = id | ||
|
||
user = MockUser(1) | ||
with app.app_context(): | ||
vhc = VerifyHashCache() | ||
assert vhc.has_verify_hash_cache(user) is None | ||
vhc.set_cache(user) | ||
assert len(vhc._cache) == 1 | ||
assert vhc.has_verify_hash_cache(user) | ||
vhc.clear() | ||
assert vhc.has_verify_hash_cache(user) is None | ||
|
||
|
||
def test_request_loader_not_using_cache(app): | ||
with app.app_context(): | ||
app.extensions["security"] = MockExtensionSecurity() | ||
_request_loader(MockRequest()) | ||
assert getattr(local_cache, "verify_hash_cache", None) is None | ||
|
||
|
||
def test_request_loader_using_cache(app): | ||
with app.app_context(): | ||
app.config["USE_VERIFY_PASSWORD_CACHE"] = True | ||
app.extensions["security"] = MockExtensionSecurity() | ||
_request_loader(MockRequest()) | ||
assert local_cache.verify_hash_cache is not None | ||
assert local_cache.verify_hash_cache.has_verify_hash_cache(MockUser(1, "token")) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think to be 2.7 compatible this will need to be: VerifyHashCache(object):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not an issue for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The drawbacks of not doing so is that some class related feature are not available with a class declared this way and some of these points are described here https://stackoverflow.com/questions/4015417/python-class-inherits-object
The cache object I made does not uses any of these feature, so this is not required, but I guess for the sake of python the inheritance should be written.
On the other hand 2020 is comming very fast :)