No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

user.py 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #-*- coding: utf-8 -*-
  2. ## @package Lodel.user Defines classes designed to handler users and user's context
  3. #
  4. # Classes defined in this package are "helpers" for Lodel2 UI
  5. import warnings
  6. import copy
  7. from Lodel.settings import Settings
  8. ## @brief Represent a Lodel user identity
  9. #
  10. # Class that produce immutable instance representing an identity
  11. class UserIdentity(object):
  12. ## Maintain a reference to a UserIdentity instance that represents an anonymous user
  13. __anonymous_user = None
  14. ## @brief Constructor
  15. # @note produce immutable instance
  16. # @param user_id * : user id
  17. # @param username str : user name
  18. # @param fullname str | None : user full name
  19. # @param identified bool : set it to True if the user is identified
  20. # @param authenticated bool : set it to True if the user is authenticated (force identified = True )
  21. def __init__(self, user_id, username, fullname = None, identified = False, authenticated = False):
  22. self.__user_id = user_id
  23. self.__username = username
  24. self.__fullname = fullname if fullname is not None else username
  25. self.__authenticated = bool(authenticated)
  26. self.__identified = bool(identified) or self.__authenticated
  27. ## @brief getter for user id
  28. @property
  29. def user_id(self):
  30. return self.__user_id
  31. ## @brief getter for username
  32. @property
  33. def username(self):
  34. return self.__username
  35. ## @brief getter for fullname
  36. @property
  37. def fullname(self):
  38. return self.__fullname
  39. ## @return True if the user is considered as authenticated
  40. @property
  41. def is_authenticated(self):
  42. return self.__authenticated
  43. ## @return True if the user is considered as identified
  44. @property
  45. def is_identified(self):
  46. return self.__identified
  47. ## @brief String representation of the instance
  48. def __repr__(self):
  49. return "User '{user_id}'( username = '{username}', fullname = '{fullname}', identified : {identified}, authentified : {auth}".format(
  50. user_id = self.__user_id,
  51. username = self.__username,
  52. fullname = self.__fullname,
  53. identified = str(self.__identified),
  54. auth = str(self.__authenticated),
  55. )
  56. ## @brief Human readable text representation of the instance
  57. def __str__(self):
  58. return self.__fullname
  59. ## @brief Provide an instance of UserIdentity representing an anonymous user
  60. @classmethod
  61. def anonymous(cls):
  62. if cls.__anonymous_user is None:
  63. cls.__anonymous_user = UserIdentity(False, "anonymous", "Anonymous user")
  64. return cls.__anonymous_user
  65. ## @brief Decorator class designed to register user authentication methods
  66. #
  67. # @note Decorated functions are expected to take 2 arguments :
  68. #  - identifier : the user identifier
  69. #  - proof : a proof of identity
  70. # and are expected to return False if authentication fails. When authentication
  71. # is a success the function is expected to return a UserIdentity instance
  72. class authentication_method(object):
  73. ## @brief Stores registered authentication functions
  74. __methods = set()
  75. ## @brief Constructor
  76. # @param method function : decorated function
  77. def __init__(self, method):
  78. ## @brief Decorated functions
  79. self._method = method
  80. self.__methods |= set([method]) # method registration
  81. ## @brief Callback called when decorated function is called
  82. # @return bool
  83. def __call__(self, identifier, proof):
  84. return self._method(identifier, proof)
  85. ## @brief Try to authenticate a user with registered functions
  86. # @param identifier * : user id
  87. # @param proof * : user authentication proof
  88. # @param cls
  89. # @return False or a User Identity instance
  90. @classmethod
  91. def authenticate(cls, identifier, proof):
  92. if len(cls.__methods) == 0:
  93. raise RuntimeError("No authentication method registered")
  94. res = False
  95. for method in cls.__methods:
  96. ret = method(identifier, proof)
  97. if ret is not False:
  98. if Settings.debug:
  99. if not isinstance(ret, UserIdentity):
  100. raise ValueError("Authentication method returns something that is not False nor a UserIdentity instance")
  101. if res is not False:
  102. warnings.warn("Multiple authentication methods returns a UserIdentity for given idetifier and proof")
  103. else:
  104. return ret
  105. res = ret
  106. return res
  107. ## @return registered identification methods
  108. # @param cls
  109. @classmethod
  110. def list_methods(cls):
  111. return list(copy.copy(cls.__methods))
  112. ## @brief Unregister all authentication methods
  113. # @param cls
  114. # @warning REALLY NOT a good idead !
  115. # @note implemented for testing purpose
  116. @classmethod
  117. def __reset__(cls):
  118. cls.__methods = set()
  119. ## @brief Decorator class designed to register identification methods
  120. #
  121. # @note The decorated functions are expected to take one argument :
  122. # - client_infos : datas for identification
  123. # and are expected to return False if identification fails. When identification is a success
  124. # the function is expected to return a UserIdentity instance
  125. class identification_method(object):
  126. ## @brief Stores registered identification functions
  127. __methods = set()
  128. ## @brief decorator constructor
  129. # @param method function : decorated function
  130. def __init__(self, method):
  131. ## @brief Decorated functions
  132. self.__method = method
  133. self.__methods |= set([method])
  134. ## @brief Called when decorated function is called
  135. def __call__(self, client_infos):
  136. return self._method(client_infos)
  137. ## @brief Identify someone given datas
  138. # @param client_infos * : datas that may identify a user
  139. # @param cls
  140. # @return False if identification fails, else returns an UserIdentity instance
  141. @classmethod
  142. def identify(cls, client_infos):
  143. if len(cls.__methods) == 0:
  144. warnings.warn("No identification methods registered")
  145. res = False
  146. for method in cls.__methods:
  147. ret = method(client_infos)
  148. if ret is not False:
  149. if Settings.debug:
  150. if not isinstance(ret, UserIdentity):
  151. raise ValueError("Identification method returns something that is not False nor a UserIdentity instance")
  152. if res is not False:
  153. warnings.warn("Identifying methods returns multiple identity given client_infos")
  154. else:
  155. res = ret
  156. else:
  157. return ret
  158. return res
  159. ## @return registered identification methods
  160. # @param cls
  161. @classmethod
  162. def list_methods(cls):
  163. return list(copy.copy(cls.__methods))
  164. ## @brief Unregister all identification methods
  165. # @param cls
  166. # @warning REALLY NOT a good idead !
  167. # @note implemented for testing purpose
  168. @classmethod
  169. def __reset__(cls):
  170. cls.__methods = set()
  171. ## @brief Static class designed to handle user context
  172. class UserContext(object):
  173. ## @brief Client infos given by user interface
  174. __client_infos = None
  175. ## @brief Stores a UserIdentity instance
  176. __identity = None
  177. ## @brief Blob of datas stored by user interface
  178. __context = None
  179. ## @brief Not callable, static class
  180. # @throw NotImplementedError
  181. def __init__(self):
  182. raise NotImplementedError("Static class")
  183. ## @brief User context constructor
  184. # @param client_infos * : datas for client identification (typically IP address)
  185. # @param **kwargs dict : context
  186. # @param cls
  187. # @todo find another exception to raise
  188. @classmethod
  189. def init(cls, client_infos, **kwargs):
  190. if cls.initialized():
  191. raise RuntimeError("Context allready initialised")
  192. if client_infos is None:
  193. raise ValueError("Argument clien_infos cannot be None")
  194. cls.__client_infos = client_infos
  195. cls.__context = kwargs
  196. cls.__identity = False
  197. ## @brief Identity getter (lazy identification implementation)
  198. # @param cls
  199. # @return a UserIdentity instance
  200. @classmethod
  201. def identity(cls):
  202. cls.assert_init()
  203. if cls.__identity is False:
  204. ret = identification_method.identify(cls.__client_infos)
  205. cls.__identity = UserIdentity.anonymous() if ret is False else ret
  206. return cls.__identity
  207. ## @brief authenticate a user
  208. # @param identifier * : user identifier
  209. # @param proof * : proof of identity
  210. # @param cls
  211. # @throw an exception if fails
  212. # @todo find a better exception to raise when auth fails
  213. @classmethod
  214. def authenticate(cls, identifier, proof):
  215. cls.assert_init()
  216. ret = authentication_method.authenticate(identifier, proof)
  217. if ret is False:
  218. raise RuntimeError("Authentication failure")
  219. cls.__identity = ret
  220. ## @return UserIdentity instance
  221. # @todo useless alias to identity()
  222. @classmethod
  223. def user_identity(cls):
  224. cls.assert_init()
  225. return cls.identity()
  226. ## @return True if UserContext is initialized
  227. @classmethod
  228. def initialized(cls):
  229. return cls.__client_infos is not None
  230. ## @brief Assert that UserContext is initialized
  231. @classmethod
  232. def assert_init(cls):
  233. assert cls.initialized(), "User context is not initialized"
  234. ## @brief Reset the UserContext
  235. # @warning Most of the time IT IS NOT A GOOD IDEAD
  236. # @note implemented for test purpose
  237. @classmethod
  238. def __reset__(cls):
  239. cls.__client_infos = None
  240. cls.__identity = None
  241. cls.__context = None