Source code for RestAuth.backends.base

# -*- coding: utf-8 -*-
#
# This file is part of RestAuth (https://restauth.net).
#
# RestAuth is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RestAuth is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with RestAuth.  If not, see <http://www.gnu.org/licenses/>.

from django.utils import importlib


[docs]class UserInstance(object): """Class representing a user. Instances of this class should provide the ``username`` and ``id`` property. * The ``username`` is the username of the user as used by the protocol. * The ``id`` may be the same as the username or some backend specific id, i.e. one that allows faster access. """ def __init__(self, id, username): self.id = id self.username = username
[docs]class GroupInstance(object): """Class representing a group. Instances of this class should provide the ``name``, ``id`` and ``service`` properties. For the ``name`` and ``id`` properties, the same semantics as for :py:class:`~RestAuth.backends.base.UserInstance` apply. The ``service`` property is a service as configured by |bin-restauth-service|. Its name is (confusingly!) available as its ``username`` property. """ def __init__(self, id, name, service): self.id = id self.name = name self.service = service
[docs]class RestAuthBackend(object): # pragma: no cover """Base class for all RestAuth data backends. ``RestAuthBackend`` provides the ``_load_library`` method that allows loading python modules upon first use. This is useful if you want to implement a backend that uses third-party libraries and do not want to cause immediate ImportErrors every time the module is loaded. To use this featurr, simply set the ``library`` class attribute and use ``self.load_library()`` to load the module into the methods namespace. Example:: from RestAuth.backends.base import UserBackend class MyCustomBackend(UserBackend): library = 'redis' def get(self, username): redis = self._load_library() # use the redis module... """ _library = None library = None def _load_library(self): """Load a library. This method is almost a 100% copy from Djangos ``django.contrib.auth.hashers.BasePasswordHasher._load_library()``. """ if self._library is not None: return self._library elif self.library is not None: if isinstance(self.library, (tuple, list)): name, mod_path = self.library else: name = mod_path = self.library try: module = importlib.import_module(mod_path) except ImportError: raise ValueError("Couldn't load %s backend library" % name) return module raise ValueError("Hasher '%s' doesn't specify a library attribute" % self.__class__)
[docs]class UserBackend(RestAuthBackend): # pragma: no cover """Provide the most basic user operations and password management."""
[docs] def get(self, username): """Get a user object for the given username. This method is used to get user objects passed to property- and group-backends. :param username: The username. :type username: str :return: A user object providing at least the properties of the UserInstance class. :rtype: :py:class:`~.UserInstance` :raise: :py:class:`~RestAuth.common.errors.UserNotFound` if the user doesn't exist. """ raise NotImplementedError
[docs] def list(self): """Get a list of all usernames. Each element of the returned list should be a valid username that can be passed to :py:meth:`~.UserBackend.get`. :return: A list of usernames. :rtype: list """ raise NotImplementedError
[docs] def create(self, username, password=None, properties=None, property_backend=None, dry=False, transaction=True): """Create a new user. The ``username`` is already validated, so you don't need to do any additional validation here. If your backend has username restrictions, please implement a :ref:`username validator <implement-validators>`. If ``properties`` are passed, please use the property backend passed to store the properties: .. code-block:: python user = ... # create the user property_backend.set_multiple(user, properties, dry=dry) return user The ``dry`` parameter tells you if you should actually create the user. The parameter will be True for `dry-runs <https://restauth.net/wiki/Specification#Doing_dry-runs>`_. In a dry-run, the method should behave as closely as possible to a normal invocation but shouldn't actually create the user. :param username: The username. :type username: str :param password: The password to set. If not given, the user should not have a valid password and is unable to log in. :type password: str :param properties: Any initial properties for the user. :type properties: dict :param property_backend: The backend to use to store properties. :type property_backend: :py:class:`~.PropertyBackend` :param dry: Wether or not to actually create the user. :type dry: boolean :param transaction: If False, execute statements outside any transactional context, if possible. This parameter is used by restauth-import to import multiple users at once with only one transaction. :type transaction: boolean :return: A user object providing at least the properties of the UserInstance class. :rtype: :py:class:`~.UserInstance` :raise: :py:class:`~RestAuth.common.errors.UserExists` if the user already exist. """ raise NotImplementedError
[docs] def rename(self, username, name): """Rename a user. This operation is only available via |bin-restauth-user-doc|. :param username: The username. :type username: str :param name: The new username. :type name: str :raise: :py:class:`~RestAuth.common.errors.UserNotFound` if the user doesn't exist. :raise: :py:class:`~RestAuth.common.errors.UserExists` if the user already exist. """ raise NotImplementedError
[docs] def exists(self, username): """Determine if the username exists. :param username: The username. :type username: str :return: True if the user exists, False otherwise. :rtype: boolean """ raise NotImplementedError
[docs] def check_password(self, username, password): """Check a users password. :param username: The username. :type username: str :param password: The password to check. :type password: str :return: True if the password is correct, False otherwise. :rtype: boolean :raise: :py:class:`~RestAuth.common.errors.UserNotFound` if the user doesn't exist. """ raise NotImplementedError
[docs] def set_password(self, username, password=None): """Set a new password. :param username: The username. :type username: str :param password: The new password. If None or empty, the user should get an unusable password. :type password: str :raise: :py:class:`~RestAuth.common.errors.UserNotFound` if the user doesn't exist. """ raise NotImplementedError
[docs] def set_password_hash(self, algorithm, hash, salt=None, **kwargs): """Set a users password hash. This method is called by |bin-restauth-import| if users with a password hash should be imported. The most common implementation is to join each given field with a '$'. :param algorithm: The algorithm used for creating the hash. :type algorithm: str :param hash: The hash created by the algorithm. :type hash: str :param salt: The salt used to create the hash, if any. If None, do not add a field to be joined, only add this field if salt is a string (empty or non-empty). :type salt: str """ raise NotImplementedError
[docs] def remove(self, username): """Remove a user. :param username: The username. :type username: str :raise: :py:class:`~RestAuth.common.errors.UserNotFound` if the user doesn't exist. """ raise NotImplementedError
[docs] def init_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def commit_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def rollback_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def testSetUp(self): """Set up your backend for a test run. This method is exclusively used in unit tests. It should perform any actions necessary to start a unit test. .. NOTE:: You do not need to implement this method, if there is nothing to do. """ pass
[docs] def testTearDown(self): """Tear down your backend after a test run. This method is exclusively used in unit tests. It should perform any actions necessary after a unit test. In general, this should completely wipe all users created during a unit test. .. NOTE:: You do not need to implement this method if the backend automatically cleans itself. """ pass
[docs]class PropertyBackend(RestAuthBackend): # pragma: no cover """Provide user properties."""
[docs] def list(self, user): """Get a full list of all user properties. :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`~.UserInstance` :return: A dictionary of key/value pairs, each describing a property. :rtype: dict """ raise NotImplementedError
[docs] def create(self, user, key, value, dry=False, transaction=True): """Create a new user property. This method should return :py:class:`~RestAuth.common.errors.PropertyExists` if a property with the given key already exists. The ``dry`` parameter tells you if you should actually create the property. The parameter will be True for `dry-runs <https://restauth.net/wiki/Specification#Doing_dry-runs>`_. In a dry-run, the method should behave as closely as possible to a normal invocation but shouldn't actually create the property. :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`~.UserInstance` :param key: The key identifying the property. :type key: str :param value: The value of the property. :type value: str :param dry: Wether or not to actually create the property. :type dry: boolean :param transaction: If False, execute statements outside any transactional context, if possible. This parameter is used by restauth-import to import multiple users at once with only one transaction. :type transaction: boolean :return: A tuple of key/value as they are stored in the database. :rtype: tuple :raise: :py:class:`~RestAuth.common.errors.PropertyExists` if the property already exists. """ raise NotImplementedError
[docs] def get(self, user, key): """Get a specific property of the user. :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`~.UserInstance` :param key: The key identifying the property. :type key: str :return: The value of the property. :rtype: str :raise: :py:class:`RestAuth.common.errors.PropertyNotFound` if the property doesn't exist. """ raise NotImplementedError
[docs] def set(self, user, key, value, dry=False, transaction=True): """Set a property for the given user. Unlike :py:meth:`~.PropertyBackend.create` this method overwrites an existing property. The ``dry`` parameter is never passed by RestAuth itself. You may pass the parameter when calling this method using :py:meth:`.set_multiple`. :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`~.UserInstance` :param key: The key identifying the property. :type key: str :param value: The value of the property. :type value: str :param transaction: If False, execute statements outside any transactional context, if possible. This parameter is used by restauth-import to import multiple users at once with only one transaction. :type transaction: boolean :return: A tuple of key/value as they are stored in the database. The value should be ``None`` if the property didn't exist previously or the old value, if it did. :rtype: tuple """ raise NotImplementedError
[docs] def set_multiple(self, user, props, dry=False, transaction=True): """Set multiple properties at once. This method may just call :py:meth:`~.PropertyBackend.set` multiple times. Some backends have faster methods for setting multiple values at once, though. The ``dry`` parameter tells you if you should actually create the properties. The parameter will be True for `dry-runs <https://restauth.net/wiki/Specification#Doing_dry-runs>`_. In a dry-run, the method should behave as closely as possible to a normal invocation but shouldn't actually create the properties. :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`~.UserInstance` :param dry: Wether or not to actually create the properties. :type dry: boolean :param transaction: If False, execute statements outside any transactional context, if possible. This parameter is used by restauth-import to import multiple users at once with only one transaction. :type transaction: boolean """ raise NotImplementedError
[docs] def remove(self, user, key): """Remove a property. :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`~.UserInstance` :param key: The key identifying the property. :type key: str :raise: :py:class:`RestAuth.common.errors.PropertyNotFound` if the property doesn't exist. """ raise NotImplementedError
[docs] def init_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def commit_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def rollback_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def testSetUp(self): """Set up your backend for a test run. This method is exclusively used in unit tests. It should perform any actions necessary to start a unit test. .. NOTE:: You do not need to implement this method, if there is nothing to do. """ pass
[docs] def testTearDown(self): """Tear down your backend after a test run. This method is exclusively used in unit tests. It should perform any actions necessary after a unit test. In general, this should completely wipe all users and properties created during a unit test. .. NOTE:: You do not need to implement this method if the backend automatically cleans itself. """ pass
[docs]class GroupBackend(RestAuthBackend): # pragma: no cover """Provide groups. A group may be identified by its name and a service. The ``service`` parameter passed in many methods is an instance of `django.contrib.auth.models.User <https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User>`_. If a :py:class:`.GroupInstance` is passed (or returned), the groups service is/should be available as the ``service`` property. """
[docs] def get(self, name, service=None): """Get a group object representing the given group. :param name: The name of the group. :type name: str :param service: The service of the named group. If None, the group should not belong to any service. :type service: :py:class:`~RestAuth.Services.models.Service` or None :return: A group object providing at least the properties of the GroupInstance class. :rtype: :py:class:`.GroupInstance` :raises: :py:class:`RestAuth.common.errors.GroupNotFound` if the named group does not exist. """ raise NotImplementedError
[docs] def list(self, service, user=None): """Get a list of group names for the given service. :param service: The service of the named group. :param user: If given, only return groups that the user is a member of. :type user: :py:class:`.UserInstance` :return: list of strings, each representing a group name. :rtype: list """ raise NotImplementedError
[docs] def create(self, name, service=None, dry=False, transaction=True): """Create a new group for the given service. The ``dry`` parameter tells you if you should actually create the group. The parameter will be True for `dry-runs <https://restauth.net/wiki/Specification#Doing_dry-runs>`_. In a dry-run, the method should behave as closely as possible to a normal invocation but shouldn't actually create the group. :param name: The name of the group. :type name: str :param service: The service of the named group. If None, the group should not belong to any service. :type service: :py:class:`~RestAuth.Services.models.Service` or None :param dry: Wether or not to actually create the group. :type dry: boolean :param transaction: If False, execute statements outside any transactional context, if possible. This parameter is used by restauth-import to import multiple users at once with only one transaction. :type transaction: boolean :return: A group object providing at least the properties of the GroupInstance class. :rtype: :py:class:`.GroupInstance` :raises: :py:class:`RestAuth.common.errors.GroupExists` if the group already exists. """ raise NotImplementedError
[docs] def rename(self, group, name): """Rename a group. This operation is only available via |bin-restauth-group-doc|. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param name: The new groupname. :type name: str :raise: :py:class:`~RestAuth.common.errors.GroupExists` if the group already exist. """ raise NotImplementedError
[docs] def set_service(self, group, service=None): """Set the service of a group. This operation is only available via |bin-restauth-group-doc|. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param service: The new service of the group. :type service: :py:class:`~RestAuth.Services.models.Service` or None """ raise NotImplementedError
[docs] def exists(self, name, service=None): """Determine if a group exists for the given service. :param name: The name of the group. :type name: str :param service: The service of the group to query. :type service: :py:class:`~RestAuth.Services.models.Service` or None :return: True if the group exists, False otherwise. :rtype: boolean """ raise NotImplementedError
[docs] def add_user(self, group, user): """Add a user to the given group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`.UserInstance` """ raise NotImplementedError
[docs] def members(self, group, depth=None): """Get a list of all members of this group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param depth: Override the recursion depth to use for meta-groups. Normally, the backend should use :setting:`GROUP_RECURSION_DEPTH`. :type depth: int :return: list of strings, each representing a username :rtype: list """ raise NotImplementedError
[docs] def is_member(self, group, user): """Determine if a user is a member of the given group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`.UserInstance` :return: True if the User is a member, False otherwise :rtype: boolean """ raise NotImplementedError
[docs] def rm_user(self, group, user): """Remove a user from the group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param user: A user as returned by :py:meth:`.UserBackend.get`. :type user: :py:class:`.UserInstance` :raises: :py:class:`RestAuth.common.errors.UserNotFound` if the user is not a member of the group. """ raise NotImplementedError
[docs] def add_subgroup(self, group, subgroup): """Make a group a subgroup of another group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param subgroup: A group as provided by :py:meth:`.GroupBackend.get`. :type subgroup: :py:class:`.GroupInstance` """ raise NotImplementedError
[docs] def subgroups(self, group, filter=True): """Get a list of subgroups. If ``filter=True``, the method should only return groups that belong to the same service as the given group. The returned list should be a list of strings, each representing a groupname. If ``filter=False``, the method should return all groups, regardless of their service. The list should contain :py:class:`.GroupInstance` objects. .. NOTE:: The filter argument is only False when called by some command line scripts. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param filter: Wether or not to filter for the groups service. See description for a detailled explanation. :type filter: boolean :return: A list of subgroups. """ raise NotImplementedError
[docs] def rm_subgroup(self, group, subgroup): """Remove a subgroup from a group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :param subgroup: A group as provided by :py:meth:`.GroupBackend.get`. :type subgroup: :py:class:`.GroupInstance` :raises: :py:class:`RestAuth.common.errors.GroupNotFound` if the named subgroup is not actually a subgroup of group. """ raise NotImplementedError
[docs] def remove(self, group): """Remove a group. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` """ raise NotImplementedError
[docs] def parents(self, group): """Get a list of all parent groups of a group. This method is only used by some command-line scripts. :param group: A group as provided by :py:meth:`.GroupBackend.get`. :type group: :py:class:`.GroupInstance` :return: List of parent groups, each being a GroupInstance object. :rtype: list """ raise NotImplementedError
[docs] def init_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def commit_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def rollback_transaction(self): """Start a transaction. This method is only used by restauth-import. """ pass
[docs] def testSetUp(self): """Set up your backend for a test run. This method is exclusively used in unit tests. It should perform any actions necessary to start a unit test. .. NOTE:: You do not need to implement this method, if there is nothing to do. """ pass
[docs] def testTearDown(self): """Tear down your backend after a test run. This method is exclusively used in unit tests. It should perform any actions necessary after a unit test. In general, this should completely wipe all users and groups created during a unit test. .. NOTE:: You do not need to implement this method if the backend automatically cleans itself. """ pass