123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #-*- Coding: utf-8 -*-
-
- import copy
-
- from lodel.settings import Settings
- from lodel import logger
- from lodel.plugin.hooks import LodelHook
- from lodel.plugin import SessionHandlerPlugin as SessionHandler
-
- ##@brief Client metaclass designed to implements container accessor on
- #Client Class
- class ClientMetaclass(type):
-
- SESSION_ID_NAME = '__SESSION_ID__'
-
- def __init__(self, name, bases, attrs):
- self.__session = dict()
- return super(ClientMetaclass, self).__init__(name, bases, attrs)
-
- def __getitem__(self, key):
- if key not in self.__session:
- raise KeyError("This client instance does not have a '%s' data" % key)
- return self.__session[key]
-
- def __setitem__(self, key, value):
- if SESSION_ID_NAME not in self.__session:
- self.__session[SESSION_ID_NAME] = SessionHandler.start_session()
- self.__session[key] = value
-
- ##@brief Return a copy of sessions infos
- def session_dump(self):
- return copy.copy(self.__session)
-
-
-
-
- ##@brief Abstract singleton class designed to handle client informations
- #
- # This class is designed to handle client authentication and sessions
- class Client(object, metaclass = ClientMetaclass):
-
- ##@brief Singleton instance
- _instance = None
- ##@brief List of dict that stores field ref for login and password
- #
- # Storage specs :
- #
- # A list of dict, with keys 'login' and 'password', items are tuple.
- #- login tuple contains (LeObjectChild, FieldName, link_field) with:
- # - LeObjectChild the dynclass containing the login
- # - Fieldname the fieldname of LeObjectChild containing the login
- # - link_field None if both login and password are in the same
- # LeObjectChild. Else contains the field that make the link between
- # login LeObject and password LeObject
- #- password typle contains (LeObjectChild, FieldName)
- _infos_fields = None
-
-
- ##@brief Constructor
- #@param ui_instance Lodel2Ui child class instance
- #@param client_infos mixed : Depends on UI implemetation
- #@param session_token mixed : Session token provided by client to interface
- def __init__(self,ui_instance, client_infos, session_token = None):
- if self.__class__ == Client:
- raise NotImplementedError("Abstract class")
- logger.debug("New instance of Client child class %s" %
- self.__class__.__name__)
- if Client._instance is not None:
- old = Client._instance
- Client._instance = None
- del(old)
- logger.debug("Replacing old Client instance by a new one")
- else:
- #first instanciation, fetching settings
- self.fetch_settings()
- ##@brief Stores instance of UI
- self.__ui_instance = ui_instance
- ##@brief Stores infos for authenticated users (None == anonymous)
- self.__user = None
- ##@brief Stores the session handler
- Client._instance = self
- logger.debug("New client : %s" % self)
-
- ##@brief Attempt to restore a session given a session token
- #@param token mixed : a session token
- #@return Session datas (a dict)
- #@throw ClientAuthenticationFailure if token is not valid or not
- #existing
- def _restore_session(self, token):
- res = self._session_handler.restore_session(token)
- if res is False:
- raise ClientAuthenticationFailure(client = self,
- msg = "Invalid or not existing session token provided")
- pass
-
- ##@brief Try to authenticate a user with a login and a password
- #@param login str : provided login
- #@param password str : provided password (hash)
- #@warning brokes composed UID
- #@note implemets multiple login/password sources (useless ?)
- #@todo composed UID broken method
- #@todo allow to provide an authentication source
- @classmethod
- def authenticate(self, login = None, password = None):
- #Authenticate
- for infos in self._infos_fields:
- login_cls = infos['login'][0]
- pass_cls = infos['pass'][0]
- qfilter = "{passfname} = {passhash}"
- uid_fname = login_cls.uid_fieldname()[0] #COMPOSED UID BROKEN
- if login_cls == pass_cls:
- #Same EmClass for login & pass
- qfilter = qfilter.format(
- passfname = infos['pass'][1],
- passhash = password)
- else:
- #Different EmClass, building a relational filter
- passfname = "%s.%s" % (infos['login'][2], infos['pass'][1])
- qfilter = qfilter.format(
- passfname = passfname,
- passhash = password)
- getq = LeGetQuery(infos['login'][0], qfilter,
- field_list = [uid_fname], limit = 1)
- req = getq.execute()
- if len(req) == 1:
- #Authenticated
- self.__set_authenticated(infos['login'][0], req[uid_fname])
- break
- if self.is_anon():
- self.fail() #Security logging
-
- ##@brief Test wether a client is anonymous or logged in
- #@return True if client is anonymous
- @classmethod
- def is_anonymous(cls):
- cls._assert_instance()
- return Client._instance
-
- ##@brief Method to call on authentication failure
- #@throw ClientAuthenticationFailure
- #@throw LodelFatalError if no Client child instance found
- @classmethod
- def authentication_failure(cls):
- cls._generic_error(ClientAuthenticationFailure)
-
- ##@brief Method to call on authentication error
- #@throw ClientAuthenticationError
- #@throw LodelFatalError if no Client child instance found
- @classmethod
- def authentication_error(cls, msg = "Unknow error"):
- cls._generic_error(ClientAuthenticationError, msg)
-
- ##@brief Method to call on permission denied error
- #@throw ClientPermissionDenied
- #@throw LodelFatalError if no Client child instance found
- @classmethod
- def permission_denied_error(cls, msg = ""):
- cls._generic_error(ClientPermissionDenied, msg)
-
- ##@brief Generic error method
- #@see Client::authentication_failure() Client::authentication_error()
- #Client::permission_denied_error()
- #@throw LodelFatalError if no Client child instance found
- @classmethod
- def _generic_error(cls, expt, msg = ""):
- cls._assert_instance()
- raise expt(Client._instance, msg)
-
- ##@brief Assert that an instance of Client child class exists
- #@throw LodelFataError if no instance of Client child class found
- @classmethod
- def _assert_instance(cls):
- if Client._instance is None:
- raise LodelFatalError("No client instance found. Abording.")
-
- ##@brief Class method that fetches conf
- #
- #This method populates Client._infos_fields . This attribute stores
- #informations on login and password location (LeApi object & field)
- @classmethod
- def fetch_settings(cls):
- from lodel import dyncode
- if cls._infos_fields is None:
- cls._infos_fields = list()
- else:
- #Allready fetched
- return
- infos = (
- Settings.auth.login_classfield,
- Settings.auth.pass_classfield)
- res_infos = []
- for clsname, fieldname in infos:
- dcls = dyncode.lowername2class(infos[0][0])
- res_infos.append((dcls, infos[1][1]))
-
- link_field = None
- if res_infos[0][0] != res_infos[1][0]:
- # login and password are in two separated EmClass
- # determining the field that links login EmClass to password
- # EmClass
- for fname, fdh in res_infos[0][0].fields(True).items():
- if fdh.is_reference() and res_infos[1][0] in fdh.linked_classes():
- link_field = fname
- if link_field is None:
- #Unable to find link between login & password EmClasses
- raise AuthenticationError("Unable to find a link between \
- login EmClass '%s' and password EmClass '%s'. Abording..." % (
- res_infos[0][0], res_infos[1][0]))
- res_infos[0] = (res_infos[0][0], res_infos[0][1], link_field)
- cls._infos_fields.append(
- {'login':res_infos[0], 'password':res_infos[1]})
-
- ##@brief Set a user as authenticated and start a new session
- #@param leo LeObject child class : the LeObject the user is stored in
- #@param uid str : uniq id (in leo)
- #@return None
- def __set_authenticated(self, leo, uid):
- self.__user = {'classname': leo.__name__, 'uid': uid, 'leoclass': leo}
-
-
|