暫無描述
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 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #-*- coding: utf-8 -*-
  2. ## @package Lodel.user
  3. # @brief Defines Classes designed to handle users and user's context.
  4. #
  5. # Classes defined in this package are "helpers" for Lodel2 UI
  6. import warnings
  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 : printable name for user identity
  18. def __init__(self, user_id, username, fullname = None, identified = False, authenticated = False):
  19. self.__user_id = user_id
  20. self.__username = username
  21. self.__fullname = fullname if fullname is not None else username
  22. self.__authenticated = bool(authenticated)
  23. self.__identified = bool(identified) or self.__authenticated
  24. ## @brief getter for user id
  25. @property
  26. def user_id(self):
  27. return self.__user_id
  28. ## @brief getter for username
  29. @property
  30. def username(self):
  31. return self.__username
  32. ## @brief getter for fullname
  33. @property
  34. def fullname(self):
  35. return self.__fullname
  36. @property
  37. def is_authenticated(self):
  38. return self.__authenticated
  39. @property
  40. def is_identified(self):
  41. return self.__identified
  42. def __repr__(self):
  43. return "User '{user_id}'( username = '{username}', fullname = '{fullname}', identified : {identified}, authentified : {auth}".format(
  44. user_id = self.__user_id,
  45. username = self.__username,
  46. fullname = self.__fullname,
  47. identified = str(self.__identified),
  48. auth = str(self.__authenticated),
  49. )
  50. def __str__(self):
  51. return self.__fullname
  52. ## @brief Provide an instance of UserIdentity representing an anonymous user
  53. @classmethod
  54. def anonymous(cls):
  55. if cls.__anonymous_user is None:
  56. cls.__anonymous_user = UserIdentity(False, "anonymous", "Anonymous user")
  57. return cls.__anonymous_user
  58. ## @brief Decorator class designed to register user authentication methods
  59. #
  60. # Example :
  61. # <pre>
  62. # @authentication_method
  63. # def foo_auth(identity, proof):
  64. # if ok:
  65. # return True
  66. # else:
  67. # return False
  68. # </pre>
  69. #
  70. class authentication_method(object):
  71. ## @brief Stores registered authentication functions
  72. __methods = set()
  73. ## @brief Constructor
  74. # @param method function : decorated function
  75. def __init__(self, method):
  76. ## @brief Decorated functions
  77. self._method = method
  78. self.__methods |= set([method]) # method registration
  79. ## @brief Callback called when decorated function is called
  80. # @return bool
  81. def __call__(self, identifier, proof):
  82. return self._method(identifier, proof)
  83. ## @brief Try to authenticate a user with registered functions
  84. # @param identity * : user id
  85. # @param proof * : user authentication proof
  86. # @return False or a User Identity instance
  87. @classmethod
  88. def authenticate(cls, identifier, proof):
  89. if len(cls.__methods) == 0:
  90. raise RuntimeError("Not authentication method registered")
  91. res = False
  92. for method in cls.__methods:
  93. ret = method(identifier, proof)
  94. if ret is not False:
  95. if Settings.debug:
  96. if not isinstance(ret, UserIdentity):
  97. raise ValueError("Authentication method returns something that is not False nor a UserIdentity instance")
  98. if res is not False:
  99. warnings.warn("Multiple authentication methods returns a UserIdentity for given idetifier and proof")
  100. else:
  101. return ret
  102. res = ret
  103. return res
  104. ## @brief Decorator class designed to register identification methods
  105. #
  106. # The decorated methods should take one client_infos argument and returns a UserIdentity instance
  107. class identification_method(object):
  108. ## @brief Stores registered identification functions
  109. __methods = set()
  110. ## @brief decorator constructor
  111. # @param method function : decorated function
  112. def __init__(self, method):
  113. ## @brief Decorated functions
  114. self.__method = method
  115. self.__methods |= set([method])
  116. ## @brief Called when decorated function is called
  117. def __call__(self, client_infos):
  118. return self._method(client_infos)
  119. ## @brief Identify someone given datas
  120. # @param datas * : datas that may identify a user
  121. # @return False if identification fails, else returns an UserIdentity instance
  122. @classmethod
  123. def identify(cls, client_infos):
  124. if len(cls.__methods) == 0:
  125. raise RuntimeError("Not identification method registered")
  126. res = False
  127. for method in cls.__methods:
  128. ret = method(client_infos)
  129. if ret is not False:
  130. if Settings.debug:
  131. if not isinstance(ret, UserIdentity):
  132. raise ValueError("Identification method returns something that is not False nor a UserIdentity instance")
  133. if res is not False:
  134. warnings.warn("Identifying methods returns multiple identity given client_infos")
  135. else:
  136. return ret
  137. res = ret
  138. return res
  139. ## @brief Static class designed to handle user context
  140. class UserContext(object):
  141. ## @brief Client infos given by user interface
  142. __client_infos = None
  143. ## @brief Stores a UserIdentity instance
  144. __identity = None
  145. ## @brief Blob of datas stored by user interface
  146. __context = None
  147. ## @brief Not callable, static class
  148. # @throw NotImplementedError
  149. def __init__(self):
  150. raise NotImplementedError("Static class")
  151. ## @brief User context constructor
  152. # @param client str : client id (typically IP addr)
  153. # @param login str|None : given when a client try to be authenticated
  154. # @param proof str|None : given when a client try to be authenticated
  155. # @param **kwargs dict : context
  156. # @todo find another exception to raise
  157. @classmethod
  158. def init(cls, client_infos, **kwargs):
  159. if cls.initialized():
  160. raise RuntimeError("Context allready initialised")
  161. if client_infos is None:
  162. raise ValueError("Argument clien_infos cannot be None")
  163. cls.__client_infos = client_infos
  164. cls.__context = kwargs
  165. cls.__identity = False
  166. ## @brief Identity getter (lazy identification implementation)
  167. # @param cls
  168. # @return a UserIdentity instance
  169. @classmethod
  170. def identity(cls):
  171. cls.assert_init()
  172. if cls.__identity is False:
  173. ret = identification_method.identify(cls.__client_infos)
  174. cls.__identity = UserIdentity.anonymous() if ret is False else ret
  175. return cls.__identity
  176. ## @brief authenticate a user
  177. # @param identifier * : user identifier
  178. # @param proof * : proof of identity
  179. # @throw an exception if fails
  180. # @todo find a better exception to raise when auth fails
  181. @classmethod
  182. def authenticate(cls, identifier, proof):
  183. cls.assert_init()
  184. ret = authentication_method.authenticate(identifier, proof)
  185. if ret is False:
  186. raise RuntimeError("Authentication failure")
  187. cls.__identity = ret
  188. ## @return UserIdentity instance
  189. @classmethod
  190. def user_identity(cls):
  191. cls.assert_init()
  192. return cls.identity()
  193. ## @return True if UserContext is initialized
  194. @classmethod
  195. def initialized(cls):
  196. return cls.__client_infos is not None
  197. ## @brief Assert that UserContext is initialized
  198. @classmethod
  199. def assert_init(cls):
  200. assert cls.initialized(), "User context is not initialized"