Viewing file: backend.py (5.93 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
""" Keyring implementation support """
from __future__ import absolute_import
import abc import logging import operator
import entrypoints
from . import credentials, errors, util from .util import properties from .py27compat import add_metaclass, filter
__metaclass__ = type
log = logging.getLogger(__name__)
by_priority = operator.attrgetter('priority') _limit = None
class KeyringBackendMeta(abc.ABCMeta): """ A metaclass that's both an ABCMeta and a type that keeps a registry of all (non-abstract) types. """ def __init__(cls, name, bases, dict): super(KeyringBackendMeta, cls).__init__(name, bases, dict) if not hasattr(cls, '_classes'): cls._classes = set() classes = cls._classes if not cls.__abstractmethods__: classes.add(cls)
@add_metaclass(KeyringBackendMeta) class KeyringBackend: """The abstract base class of the keyring, every backend must implement this interface. """
# @abc.abstractproperty def priority(cls): """ Each backend class must supply a priority, a number (float or integer) indicating the priority of the backend relative to all other backends. The priority need not be static -- it may (and should) vary based attributes of the environment in which is runs (platform, available packages, etc.).
A higher number indicates a higher priority. The priority should raise a RuntimeError with a message indicating the underlying cause if the backend is not suitable for the current environment.
As a rule of thumb, a priority between zero but less than one is suitable, but a priority of one or greater is recommended. """
@properties.ClassProperty @classmethod def viable(cls): with errors.ExceptionRaisedContext() as exc: cls.priority return not bool(exc)
@classmethod def get_viable_backends(cls): """ Return all subclasses deemed viable. """ return filter(operator.attrgetter('viable'), cls._classes)
@properties.ClassProperty @classmethod def name(cls): """ The keyring name, suitable for display.
The name is derived from module and class name. """ parent, sep, mod_name = cls.__module__.rpartition('.') mod_name = mod_name.replace('_', ' ') return ' '.join([mod_name, cls.__name__])
def __str__(self): keyring_class = type(self) return ("%s.%s (priority: %g)" % (keyring_class.__module__, keyring_class.__name__, keyring_class.priority))
@abc.abstractmethod def get_password(self, service, username): """Get password of the username for the service """ return None
@abc.abstractmethod def set_password(self, service, username, password): """Set password for the username of the service.
If the backend cannot store passwords, raise NotImplementedError. """ raise errors.PasswordSetError("reason")
# for backward-compatibility, don't require a backend to implement # delete_password # @abc.abstractmethod def delete_password(self, service, username): """Delete the password for the username of the service.
If the backend cannot store passwords, raise NotImplementedError. """ raise errors.PasswordDeleteError("reason")
# for backward-compatibility, don't require a backend to implement # get_credential # @abc.abstractmethod def get_credential(self, service, username): """Gets the username and password for the service. Returns a Credential instance.
The *username* argument is optional and may be omitted by the caller or ignored by the backend. Callers must use the returned username. """ # The default implementation requires a username here. if username is not None: password = self.get_password(service, username) if password is not None: return credentials.SimpleCredential( username, password, ) return None
class Crypter: """Base class providing encryption and decryption """
@abc.abstractmethod def encrypt(self, value): """Encrypt the value. """ pass
@abc.abstractmethod def decrypt(self, value): """Decrypt the value. """ pass
class NullCrypter(Crypter): """A crypter that does nothing """
def encrypt(self, value): return value
def decrypt(self, value): return value
def _load_plugins(): """ Locate all setuptools entry points by the name 'keyring backends' and initialize them. Any third-party library may register an entry point by adding the following to their setup.py::
entry_points = { 'keyring.backends': [ 'plugin_name = mylib.mymodule:initialize_func', ], },
`plugin_name` can be anything, and is only used to display the name of the plugin at initialization time.
`initialize_func` is optional, but will be invoked if callable. """ group = 'keyring.backends' entry_points = entrypoints.get_group_all(group=group) for ep in entry_points: try: log.info('Loading %s', ep.name) init_func = ep.load() if callable(init_func): init_func() except Exception: log.exception("Error initializing plugin %s." % ep)
@util.once def get_all_keyring(): """ Return a list of all implemented keyrings that can be constructed without parameters. """ _load_plugins() viable_classes = KeyringBackend.get_viable_backends() rings = util.suppress_exceptions(viable_classes, exceptions=TypeError) return list(rings)
|