diff --git a/keystone/conf/credential.py b/keystone/conf/credential.py index b7877a8165..3ccf6e633c 100644 --- a/keystone/conf/credential.py +++ b/keystone/conf/credential.py @@ -46,12 +46,29 @@ share this repository with the repository used to manage keys for Fernet tokens. """)) +caching = cfg.BoolOpt( + 'caching', + default=True, + help=utils.fmt(""" +Toggle for caching only on retrieval of user credentials. This has no effect +unless global caching is enabled. +""")) + +cache_time = cfg.IntOpt( + 'cache_time', + help=utils.fmt(""" +Time to cache credential data in seconds. This has no effect unless global +caching is enabled. +""")) + GROUP_NAME = __name__.split('.')[-1] ALL_OPTS = [ driver, provider, - key_repository + key_repository, + caching, + cache_time ] diff --git a/keystone/credential/core.py b/keystone/credential/core.py index cb28b314e0..900a45e164 100644 --- a/keystone/credential/core.py +++ b/keystone/credential/core.py @@ -16,6 +16,7 @@ import json +from keystone.common import cache from keystone.common import driver_hints from keystone.common import manager from keystone.common import provider_api @@ -24,6 +25,7 @@ from keystone import exception CONF = keystone.conf.CONF +MEMOIZE = cache.get_memoization_decorator(group='credential') PROVIDERS = provider_api.ProviderAPIs @@ -91,21 +93,39 @@ class Manager(manager.Manager): return credentials def list_credentials_for_user(self, user_id, type=None): - """List credentials for a specific user.""" - credentials = self.driver.list_credentials_for_user(user_id, type=type) + credentials = self._list_credentials_for_user(user_id, type) for credential in credentials: credential = self._decrypt_credential(credential) return credentials + @MEMOIZE + def _list_credentials_for_user(self, user_id, type): + """List credentials for a specific user.""" + return self.driver.list_credentials_for_user(user_id, type) + def get_credential(self, credential_id): """Return a credential reference.""" - credential = self.driver.get_credential(credential_id) + credential = self._get_credential(credential_id) return self._decrypt_credential(credential) + @MEMOIZE + def _get_credential(self, credential_id): + return self.driver.get_credential(credential_id) + def create_credential(self, credential_id, credential): """Create a credential.""" credential_copy = self._encrypt_credential(credential) ref = self.driver.create_credential(credential_id, credential_copy) + if MEMOIZE.should_cache(ref): + self._get_credential.set(ref, + credential_copy, + credential_id) + self._list_credentials_for_user.invalidate(self, + ref['user_id'], + ref['type']) + self._list_credentials_for_user.invalidate(self, + ref['user_id'], + None) ref.pop('key_hash', None) ref.pop('encrypted_blob', None) ref['blob'] = credential['blob'] @@ -133,6 +153,14 @@ class Manager(manager.Manager): existing_credential = self.get_credential(credential_id) existing_blob = existing_credential['blob'] ref = self.driver.update_credential(credential_id, credential_copy) + if MEMOIZE.should_cache(ref): + self._get_credential.set(ref, self, credential_id) + self._list_credentials_for_user.invalidate(self, + ref['user_id'], + ref['type']) + self._list_credentials_for_user.invalidate(self, + ref['user_id'], + None) ref.pop('key_hash', None) ref.pop('encrypted_blob', None) # If the update request contains a `blob` attribute - we should return @@ -143,3 +171,44 @@ class Manager(manager.Manager): else: ref['blob'] = existing_blob return ref + + def delete_credential(self, credential_id): + """Delete a credential.""" + cred = self.get_credential(credential_id) + self.driver.delete_credential(credential_id) + self._get_credential.invalidate(self, credential_id) + self._list_credentials_for_user.invalidate(self, + cred['user_id'], + cred['type']) + self._list_credentials_for_user.invalidate(self, + cred['user_id'], + None) + + def delete_credentials_for_project(self, project_id): + """Delete all credentials for a project.""" + hints = driver_hints.Hints() + hints.add_filter('project_id', project_id) + creds = self.driver.list_credentials(hints) + + self.driver.delete_credentials_for_project(project_id) + for cred in creds: + self._get_credential.invalidate(self, cred['id']) + self._list_credentials_for_user.invalidate(self, + cred['user_id'], + cred['type']) + self._list_credentials_for_user.invalidate(self, + cred['user_id'], + None) + + def delete_credentials_for_user(self, user_id): + """Delete all credentials for a user.""" + creds = self.driver.list_credentials_for_user(user_id) + self.driver.delete_credentials_for_user(user_id) + for cred in creds: + self._get_credential.invalidate(self, cred['id']) + self._list_credentials_for_user.invalidate(self, + user_id, + cred['type']) + self._list_credentials_for_user.invalidate(self, + cred['user_id'], + None) diff --git a/releasenotes/notes/bug-1815771-ae0e4118c552f01e.yaml b/releasenotes/notes/bug-1815771-ae0e4118c552f01e.yaml new file mode 100644 index 0000000000..058754b75d --- /dev/null +++ b/releasenotes/notes/bug-1815771-ae0e4118c552f01e.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + [`bug 1815771 `_] + Allows operators to cache credentials to avoid lookups on the database. + This operation can be turned on/off through the configuration parameter of + keystone.conf [credential] caching.