1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
# coding=utf-8
import uuid
import threading # multiprocessing.managers.BaseManager uses threading to serve incoming requests
import hashlib
import base64
import random
from . import prolog_session
import db
from errors.session import NoSuchSession, AuthenticationFailed
__all__ = ['get_session_by_id', 'get_or_create_session', 'UserSession']
sessions = {} # maps session IDs to session objects
module_access_lock = threading.Lock() # use this lock to access the sessions dictionary
class UserSession(object):
"""Abstracts a user session.
Only public methods are available to the outside world due to the use of multiprocessing managers.
Therefore prefix any private methods with an underscore (_).
No properties are accessible; use getters and setters instead.
Values are passed by value instead of by reference (deep copy!).
"""
def __init__(self, uid, username):
self._access_lock = threading.Lock()
self.sid = uuid.uuid4().hex
self.uid = uid
self.username = username
self.prolog_session = None
def destroy(self):
"""Destroys the session."""
with self._access_lock:
with module_access_lock:
del sessions[self.sid]
if self.prolog_session is not None:
self.prolog_session.end()
self.prolog_session = None
# TODO: add any cleanups as features are added!
def get_sid(self):
return self.sid
def get_uid(self):
return self.uid
def get_prolog(self):
with self._access_lock:
if self.prolog_session is None:
self.prolog_session = prolog_session.PrologSession() # lazy init
return self.prolog_session
def __del__(self):
# no locking needed if GC is removing us, as there cannot be any concurrent access by definition
if hasattr(self, 'prolog_session') and (self.prolog_session is not None):
self.prolog_session.end()
self.prolog_session = None
# TODO: add any cleanups as features are added!
def get_session_by_id(sid):
with module_access_lock:
s = sessions.get(sid, None)
if s is None:
raise NoSuchSession('There is no session with SID {}'.format(sid))
return s
def get_or_create_session(uid, username, sid=None):
with module_access_lock:
if sid is not None:
s = sessions.get(sid)
if s is not None:
return s
s = UserSession(uid, username)
sessions[s.sid] = s
return s
def authenticate_and_create_session(username, password):
conn = db.get_connection()
try:
cur = conn.cursor()
try:
cur.execute('select id, password from codeq_user where username = %s', (username,))
row = cur.fetchone()
if row is None:
raise AuthenticationFailed('No such user: {}'.format(username))
if verify_password(password, row[1]):
return get_or_create_session(row[0], username)
raise AuthenticationFailed('Password mismatch')
finally:
cur.close()
finally:
conn.commit()
db.return_connection(conn)
def verify_password(plain_password, encrypted_password):
elts = encrypted_password.split('$')
if len(elts) != 4:
return False
if elts[0] != 'pbkdf2_sha256':
return False
try:
rounds = int(elts[1])
except:
return False
enc = hashlib.pbkdf2_hmac('sha256', plain_password.encode('utf-8'), elts[2].encode('utf-8'), rounds)
return base64.b64encode(enc).decode('utf-8') == elts[3]
_salt_chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
_salt_chars_len = len(_salt_chars)
def encrypt_password(plain_password):
rounds = 20000
chosen_chars = []
for i in range(0, 12):
chosen_chars.append(_salt_chars[random.randrange(0, _salt_chars_len)])
salt = ''.join(chosen_chars)
enc = hashlib.pbkdf2_hmac('sha256', plain_password.encode('utf-8'), salt.encode('utf-8'), rounds)
return '{0}${1}${2}${3}'.format('pbkdf2_sha256', rounds, salt, base64.b64encode(enc).decode('utf-8'))
random.seed()
# TODO: add a session timeout timer
|