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