Browse Source

First implementation of UserContext handler

Yann Weber 9 years ago
parent
commit
6ac0984ba2
1 changed files with 216 additions and 0 deletions
  1. 216
    0
      Lodel/user.py

+ 216
- 0
Lodel/user.py View File

@@ -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"

Loading…
Cancel
Save