123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- #-*- coding: utf-8 -*-
-
- ## @package Lodel.user Defines classes designed to handler users and user's context
- #
- # Classes defined in this package are "helpers" for Lodel2 UI
-
- import warnings
- import copy
- from Lodel.settings import Settings
-
- ## @brief Represent a Lodel user identity
- #
- # Class that produce immutable instance representing an identity
- class UserIdentity(object):
-
- ## Maintain a reference to a UserIdentity instance that represents an anonymous user
- __anonymous_user = None
-
- ## @brief Constructor
- # @note produce immutable instance
- # @param user_id * : user id
- # @param username str : user name
- # @param fullname str | None : user full name
- # @param identified bool : set it to True if the user is identified
- # @param authenticated bool : set it to True if the user is authenticated (force identified = True )
- def __init__(self, user_id, username, fullname = None, identified = False, authenticated = False):
- self.__user_id = user_id
- self.__username = username
- self.__fullname = fullname if fullname is not None else username
- self.__authenticated = bool(authenticated)
- self.__identified = bool(identified) or self.__authenticated
-
- ## @brief getter for user id
- @property
- def user_id(self):
- return self.__user_id
-
- ## @brief getter for username
- @property
- def username(self):
- return self.__username
-
- ## @brief getter for fullname
- @property
- def fullname(self):
- return self.__fullname
-
- ## @return True if the user is considered as authenticated
- @property
- def is_authenticated(self):
- return self.__authenticated
-
- ## @return True if the user is considered as identified
- @property
- def is_identified(self):
- return self.__identified
-
- ## @brief String representation of the instance
- def __repr__(self):
- return "User '{user_id}'( username = '{username}', fullname = '{fullname}', identified : {identified}, authentified : {auth}".format(
- user_id = self.__user_id,
- username = self.__username,
- fullname = self.__fullname,
- identified = str(self.__identified),
- auth = str(self.__authenticated),
- )
-
- ## @brief Human readable text representation of the instance
- def __str__(self):
- return self.__fullname
-
- ## @brief Provide an instance of UserIdentity representing an anonymous user
- @classmethod
- def anonymous(cls):
- if cls.__anonymous_user is None:
- cls.__anonymous_user = UserIdentity(False, "anonymous", "Anonymous user")
- return cls.__anonymous_user
-
-
- ## @brief Decorator class designed to register user authentication methods
- #
- # @note Decorated functions are expected to take 2 arguments :
- # - identifier : the user identifier
- # - proof : a proof of identity
- # and are expected to return False if authentication fails. When authentication
- # is a success the function is expected to return a UserIdentity instance
- class authentication_method(object):
-
- ## @brief Stores registered authentication functions
- __methods = set()
-
- ## @brief Constructor
- # @param method function : decorated function
- def __init__(self, method):
- ## @brief Decorated functions
- self._method = method
- self.__methods |= set([method]) # method registration
-
- ## @brief Callback called when decorated function is called
- # @return bool
- def __call__(self, identifier, proof):
- return self._method(identifier, proof)
-
- ## @brief Try to authenticate a user with registered functions
- # @param identifier * : user id
- # @param proof * : user authentication proof
- # @param cls
- # @return False or a User Identity instance
- @classmethod
- def authenticate(cls, identifier, proof):
- if len(cls.__methods) == 0:
- raise RuntimeError("No authentication method registered")
- res = False
- for method in cls.__methods:
- ret = method(identifier, proof)
- if ret is not False:
- if Settings.debug:
- if not isinstance(ret, UserIdentity):
- raise ValueError("Authentication method returns something that is not False nor a UserIdentity instance")
- if res is not False:
- warnings.warn("Multiple authentication methods returns a UserIdentity for given idetifier and proof")
- else:
- return ret
- res = ret
- return res
-
- ## @return registered identification methods
- # @param cls
- @classmethod
- def list_methods(cls):
- return list(copy.copy(cls.__methods))
-
- ## @brief Unregister all authentication methods
- # @param cls
- # @warning REALLY NOT a good idead !
- # @note implemented for testing purpose
- @classmethod
- def __reset__(cls):
- cls.__methods = set()
-
-
- ## @brief Decorator class designed to register identification methods
- #
- # @note The decorated functions are expected to take one argument :
- # - client_infos : datas for identification
- # and are expected to return False if identification fails. When identification is a success
- # the function is expected to return a UserIdentity instance
- class identification_method(object):
-
- ## @brief Stores registered identification functions
- __methods = set()
-
- ## @brief decorator constructor
- # @param method function : decorated function
- def __init__(self, method):
- ## @brief Decorated functions
- self.__method = method
- self.__methods |= set([method])
-
- ## @brief Called when decorated function is called
- def __call__(self, client_infos):
- return self._method(client_infos)
-
- ## @brief Identify someone given datas
- # @param client_infos * : datas that may identify a user
- # @param cls
- # @return False if identification fails, else returns an UserIdentity instance
- @classmethod
- def identify(cls, client_infos):
- if len(cls.__methods) == 0:
- warnings.warn("No identification methods registered")
- res = False
- for method in cls.__methods:
- ret = method(client_infos)
- if ret is not False:
- if Settings.debug:
- if not isinstance(ret, UserIdentity):
- raise ValueError("Identification method returns something that is not False nor a UserIdentity instance")
- if res is not False:
- warnings.warn("Identifying methods returns multiple identity given client_infos")
- else:
- res = ret
- else:
- return ret
- return res
-
- ## @return registered identification methods
- # @param cls
- @classmethod
- def list_methods(cls):
- return list(copy.copy(cls.__methods))
-
- ## @brief Unregister all identification methods
- # @param cls
- # @warning REALLY NOT a good idead !
- # @note implemented for testing purpose
- @classmethod
- def __reset__(cls):
- cls.__methods = set()
-
-
- ## @brief Static class designed to handle user context
- class UserContext(object):
-
- ## @brief Client infos given by user interface
- __client_infos = None
- ## @brief Stores a UserIdentity instance
- __identity = None
- ## @brief Blob of datas stored by user interface
- __context = None
-
-
- ## @brief Not callable, static class
- # @throw NotImplementedError
- def __init__(self):
- raise NotImplementedError("Static class")
-
- ## @brief User context constructor
- # @param client_infos * : datas for client identification (typically IP address)
- # @param **kwargs dict : context
- # @param cls
- # @todo find another exception to raise
- @classmethod
- def init(cls, client_infos, **kwargs):
- if cls.initialized():
- raise RuntimeError("Context allready initialised")
- if client_infos is None:
- raise ValueError("Argument clien_infos cannot be None")
- cls.__client_infos = client_infos
- cls.__context = kwargs
- cls.__identity = False
-
- ## @brief Identity getter (lazy identification implementation)
- # @param cls
- # @return a UserIdentity instance
- @classmethod
- def identity(cls):
- cls.assert_init()
- if cls.__identity is False:
- ret = identification_method.identify(cls.__client_infos)
- cls.__identity = UserIdentity.anonymous() if ret is False else ret
- return cls.__identity
-
- ## @brief authenticate a user
- # @param identifier * : user identifier
- # @param proof * : proof of identity
- # @param cls
- # @throw an exception if fails
- # @todo find a better exception to raise when auth fails
- @classmethod
- def authenticate(cls, identifier, proof):
- cls.assert_init()
- ret = authentication_method.authenticate(identifier, proof)
- if ret is False:
- raise RuntimeError("Authentication failure")
- cls.__identity = ret
-
- ## @return UserIdentity instance
- # @todo useless alias to identity()
- @classmethod
- def user_identity(cls):
- cls.assert_init()
- return cls.identity()
-
- ## @return True if UserContext is initialized
- @classmethod
- def initialized(cls):
- return cls.__client_infos is not None
-
- ## @brief Assert that UserContext is initialized
- @classmethod
- def assert_init(cls):
- assert cls.initialized(), "User context is not initialized"
-
- ## @brief Reset the UserContext
- # @warning Most of the time IT IS NOT A GOOD IDEAD
- # @note implemented for test purpose
- @classmethod
- def __reset__(cls):
- cls.__client_infos = None
- cls.__identity = None
- cls.__context = None
|