|
@@ -8,22 +8,45 @@ import os
|
8
|
8
|
import pickle
|
9
|
9
|
import re
|
10
|
10
|
import time
|
11
|
|
-
|
|
11
|
+from .filesystem_session import FileSystemSession
|
12
|
12
|
from lodel.context import LodelContext
|
|
13
|
+
|
|
14
|
+
|
13
|
15
|
LodelContext.expose_modules(globals(), {
|
14
|
16
|
'lodel.logger': 'logger',
|
15
|
17
|
'lodel.auth.exceptions': ['ClientAuthenticationFailure'],
|
16
|
18
|
'lodel.settings': ['Settings']})
|
17
|
19
|
|
18
|
|
-from .filesystem_session import FileSystemSession
|
19
|
|
-
|
20
|
20
|
__sessions = dict()
|
21
|
21
|
|
22
|
22
|
SESSION_TOKENSIZE = 150
|
23
|
23
|
|
24
|
24
|
|
25
|
|
-## @brief generates a new session token
|
|
25
|
+##
|
|
26
|
+# @brief generates a new session token
|
|
27
|
+#
|
26
|
28
|
# @return str
|
|
29
|
+#
|
|
30
|
+# @warning The tokensize should absolutely be used as set! os.urandom function
|
|
31
|
+# takes a number of bytes as a parameter, dividing it by 2 is an
|
|
32
|
+# extremely dangerous idea as it drastically decrease the token expected
|
|
33
|
+# entropy expected from the value set in configs.
|
|
34
|
+# @remarks There is no valid reason for checking the generated token uniqueness:
|
|
35
|
+# - checking for uniqueness is slow ;
|
|
36
|
+# - keeping a dict with a few thousand keys of hundred bytes also is
|
|
37
|
+# memory expensive ;
|
|
38
|
+# - should the system get distributed while sharing session storage, there
|
|
39
|
+# would be no reasonable way to efficiently check for uniqueness ;
|
|
40
|
+# - sessions do have a really short life span, drastically reducing
|
|
41
|
+# even more an already close to inexistent risk of collision. A 64 bits
|
|
42
|
+# id would perfectly do the job, or to be really cautious, a 128 bits
|
|
43
|
+# one (actual size of UUIDs) ;
|
|
44
|
+# - if we are still willing to ensure uniqueness, then simply salt it
|
|
45
|
+# with a counter, or a timestamp, and hash the whole thing with a
|
|
46
|
+# cryptographically secured method such as sha-2 if we are paranoids
|
|
47
|
+# and trying to avoid what will never happen, ever ;
|
|
48
|
+# - sure, two hexadecimal characters is one byte long. Simply go for
|
|
49
|
+# bit length, not chars length.
|
27
|
50
|
def generate_token():
|
28
|
51
|
token = binascii.hexlify(os.urandom(SESSION_TOKENSIZE//2))
|
29
|
52
|
if token in __sessions.keys():
|
|
@@ -31,9 +54,18 @@ def generate_token():
|
31
|
54
|
return token.decode('utf-8')
|
32
|
55
|
|
33
|
56
|
|
34
|
|
-## @brief checks the validity of a given session token
|
|
57
|
+##
|
|
58
|
+# @brief checks the validity of a given session token
|
|
59
|
+#
|
35
|
60
|
# @param token str
|
36
|
|
-# @throw ClientAuthenticationFailure for invalid or not found session token
|
|
61
|
+# @raise ClientAuthenticationFailure for invalid or not found session token
|
|
62
|
+#
|
|
63
|
+# @remarks It is useless to check the token size, unless urandom you don't
|
|
64
|
+# trust in PRNG such as urandom.
|
|
65
|
+# @remarks Linear key search...
|
|
66
|
+# @remarks Consider renaming. The "validity of a session token" usually means
|
|
67
|
+# that it is a active session token and/or that it was actually
|
|
68
|
+# produced by the application (signed for exemple).
|
37
|
69
|
def check_token(token):
|
38
|
70
|
if len(token) != SESSION_TOKENSIZE:
|
39
|
71
|
raise ClientAuthenticationFailure("Invalid token string")
|
|
@@ -49,8 +81,13 @@ def generate_file_path(token):
|
49
|
81
|
|
50
|
82
|
|
51
|
83
|
##
|
|
84
|
+# @brief Retrieve the token from the file system
|
|
85
|
+#
|
52
|
86
|
# @param filepath str
|
53
|
87
|
# @return str|None : returns the token or None if no token was found
|
|
88
|
+#
|
|
89
|
+# @remarks What is the purpose of the regex right here? There should be a way
|
|
90
|
+# to avoid slow operations.
|
54
|
91
|
def get_token_from_filepath(filepath):
|
55
|
92
|
token_regex = re.compile(os.path.abspath(os.path.join(Settings.sessions.directory, Settings.sessions.file_template % '(?P<token>.*)')))
|
56
|
93
|
token_search_result = token_regex.match(filepath)
|
|
@@ -59,10 +96,15 @@ def get_token_from_filepath(filepath):
|
59
|
96
|
return None
|
60
|
97
|
|
61
|
98
|
|
62
|
|
-## @brief returns the session's last modification timestamp
|
|
99
|
+##
|
|
100
|
+# @brief Returns the session's last modification timestamp
|
|
101
|
+#
|
63
|
102
|
# @param token str
|
64
|
103
|
# @return float
|
65
|
|
-# @throw ValueError if the given token doesn't match with an existing session
|
|
104
|
+# @raise ValueError if the given token doesn't match with an existing session
|
|
105
|
+#
|
|
106
|
+# @remarks Consider renaming
|
|
107
|
+# @warning Linear search in array, again. See @ref generate_token().
|
66
|
108
|
def get_session_last_modified(token):
|
67
|
109
|
if token in __sessions[token]:
|
68
|
110
|
return os.stat(__sessions[token]).st_mtime
|
|
@@ -70,19 +112,26 @@ def get_session_last_modified(token):
|
70
|
112
|
raise ValueError("The given token %s doesn't match with an existing session")
|
71
|
113
|
|
72
|
114
|
|
73
|
|
-## @brief returns the token of a new session
|
74
|
|
-# @return str
|
|
115
|
+##
|
|
116
|
+# @brief Starts a new session and returns a new token
|
|
117
|
+#
|
|
118
|
+# @return str : the new token
|
75
|
119
|
def start_session():
|
76
|
120
|
session = FileSystemSession(generate_token())
|
77
|
121
|
session.path = generate_file_path(session.token)
|
|
122
|
+
|
78
|
123
|
with open(session.path, 'wb') as session_file:
|
79
|
124
|
pickle.dump(session, session_file)
|
|
125
|
+
|
80
|
126
|
__sessions[session.token] = session.path
|
81
|
127
|
logger.debug("New session created")
|
|
128
|
+
|
82
|
129
|
return session.token
|
83
|
130
|
|
84
|
131
|
|
85
|
|
-## @brief destroys a session given its token
|
|
132
|
+##
|
|
133
|
+# @brief destroys a session given its token
|
|
134
|
+#
|
86
|
135
|
# @param token str
|
87
|
136
|
def destroy_session(token):
|
88
|
137
|
check_token(token)
|
|
@@ -93,7 +142,9 @@ def destroy_session(token):
|
93
|
142
|
logger.debug("Session %s unregistered" % token)
|
94
|
143
|
|
95
|
144
|
|
96
|
|
-## @brief restores a session's content
|
|
145
|
+##
|
|
146
|
+# @brief Restores a session's content
|
|
147
|
+#
|
97
|
148
|
# @param token str
|
98
|
149
|
# @return FileSystemSession|None
|
99
|
150
|
def restore_session(token):
|
|
@@ -128,6 +179,8 @@ def save_session(token, datas):
|
128
|
179
|
|
129
|
180
|
|
130
|
181
|
## @brief session store's garbage collector
|
|
182
|
+#
|
|
183
|
+# @remarks
|
131
|
184
|
def gc():
|
132
|
185
|
# Unregistered files in the session directory
|
133
|
186
|
session_files_directory = os.path.abspath(Settings.sessions.directory)
|
|
@@ -164,8 +217,12 @@ def get_session_value(token, key):
|
164
|
217
|
return session[key]
|
165
|
218
|
|
166
|
219
|
##
|
|
220
|
+# @brief deletes a session value
|
|
221
|
+#
|
167
|
222
|
# @param token str
|
168
|
223
|
# @param key str
|
|
224
|
+#
|
|
225
|
+# @todo Should we add a save_session at the end of this method?
|
169
|
226
|
def del_session_value(token, key):
|
170
|
227
|
session = restore_session(token)
|
171
|
228
|
if key in session:
|