Commit 3586cfaba4678c93c4ec985beb50d7c65937d2ab

Authored by Miguel Barao
1 parent 5b95825d
Exists in master and in 1 other branch dev

fixed async login

Showing 2 changed files with 79 additions and 52 deletions   Show diff stats
1 1
2 # python standard library 2 # python standard library
3 -from contextlib import contextmanager # `with` statement in db sessions  
4 -import logging  
5 from os import path, sys 3 from os import path, sys
  4 +import logging
  5 +from contextlib import contextmanager # `with` statement in db sessions
  6 +import asyncio
6 from datetime import datetime 7 from datetime import datetime
7 8
8 # user installed libraries 9 # user installed libraries
@@ -20,15 +21,42 @@ from tools import load_yaml @@ -20,15 +21,42 @@ from tools import load_yaml
20 # setup logger for this module 21 # setup logger for this module
21 logger = logging.getLogger(__name__) 22 logger = logging.getLogger(__name__)
22 23
23 - 24 +# ============================================================================
24 class LearnAppException(Exception): 25 class LearnAppException(Exception):
25 pass 26 pass
26 27
27 28
28 # ============================================================================ 29 # ============================================================================
  30 +# helper functions
  31 +# ============================================================================
  32 +async def check_password(try_pw, password):
  33 + try_pw = try_pw.encode('utf-8')
  34 + loop = asyncio.get_running_loop()
  35 + hashed_pw = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password)
  36 + return password == hashed_pw
  37 +
  38 +
  39 +# ============================================================================
29 # LearnApp - application logic 40 # LearnApp - application logic
30 # ============================================================================ 41 # ============================================================================
31 class LearnApp(object): 42 class LearnApp(object):
  43 + # ------------------------------------------------------------------------
  44 + # helper to manage db sessions using the `with` statement, for example
  45 + # with self.db_session() as s: s.query(...)
  46 + # ------------------------------------------------------------------------
  47 + @contextmanager
  48 + def db_session(self, **kw):
  49 + session = self.Session(**kw)
  50 + try:
  51 + yield session
  52 + session.commit()
  53 + except:
  54 + session.rollback()
  55 + logger.error('DB rollback!!!')
  56 + finally:
  57 + session.close()
  58 +
  59 + # ------------------------------------------------------------------------
32 def __init__(self, config_file): 60 def __init__(self, config_file):
33 # state of online students 61 # state of online students
34 self.online = {} 62 self.online = {}
@@ -47,38 +75,44 @@ class LearnApp(object): @@ -47,38 +75,44 @@ class LearnApp(object):
47 # ------------------------------------------------------------------------ 75 # ------------------------------------------------------------------------
48 # login 76 # login
49 # ------------------------------------------------------------------------ 77 # ------------------------------------------------------------------------
50 - def login(self, uid, pw): 78 + async def login(self, uid, try_pw):
  79 + if uid.startswith('l'): # remove prefix 'l'
  80 + uid = uid[1:]
51 81
52 with self.db_session() as s: 82 with self.db_session() as s:
53 - student = s.query(Student).filter(Student.id == uid).one_or_none()  
54 - if student is None: 83 + try:
  84 + name, password = s.query(Student.name, Student.password).filter_by(id=uid).one()
  85 + except:
55 logger.info(f'User "{uid}" does not exist!') 86 logger.info(f'User "{uid}" does not exist!')
56 - return False # student does not exist  
57 -  
58 - if bcrypt.checkpw(pw.encode('utf-8'), student.password):  
59 - if uid in self.online:  
60 - logger.warning(f'User "{uid}" already logged in, overwriting state')  
61 - else:  
62 - logger.info(f'User "{uid}" logged in')  
63 -  
64 - tt = s.query(StudentTopic).filter(StudentTopic.student_id == uid)  
65 - state = {t.topic_id:  
66 - {  
67 - 'level': t.level,  
68 - 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f")  
69 - } for t in tt}  
70 -  
71 - self.online[uid] = {  
72 - 'number': student.id,  
73 - 'name': student.name,  
74 - 'state': StudentKnowledge(self.deps, state=state),  
75 - # 'learning': None, # current topic learning  
76 - }  
77 - return True 87 + return False
78 88
  89 + pw_ok = await check_password(try_pw, password) # async bcrypt
  90 + if pw_ok:
  91 + if uid in self.online:
  92 + logger.warning(f'User "{uid}" already logged in, overwriting state')
79 else: 93 else:
80 - logger.info(f'User "{uid}" wrong password!')  
81 - return False 94 + logger.info(f'User "{uid}" logged in successfully')
  95 +
  96 + with self.db_session() as s:
  97 + tt = s.query(StudentTopic).filter_by(student_id=uid)
  98 +
  99 + state = {t.topic_id:
  100 + {
  101 + 'level': t.level,
  102 + 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f")
  103 + } for t in tt}
  104 +
  105 + self.online[uid] = {
  106 + 'number': uid,
  107 + 'name': name,
  108 + 'state': StudentKnowledge(self.deps, state=state),
  109 + # 'learning': None, # current topic learning
  110 + }
  111 +
  112 + else:
  113 + logger.info(f'User "{uid}" wrong password!')
  114 +
  115 + return pw_ok
82 116
83 # ------------------------------------------------------------------------ 117 # ------------------------------------------------------------------------
84 # logout 118 # logout
@@ -206,31 +240,14 @@ class LearnApp(object): @@ -206,31 +240,14 @@ class LearnApp(object):
206 n = s.query(Student).count() 240 n = s.query(Student).count()
207 m = s.query(Topic).count() 241 m = s.query(Topic).count()
208 q = s.query(Answer).count() 242 q = s.query(Answer).count()
209 - except Exception: 243 + except Exception as e:
210 logger.critical(f'Database "{db}" not usable!') 244 logger.critical(f'Database "{db}" not usable!')
211 - sys.exit(1) 245 + raise e
212 else: 246 else:
213 logger.info(f'{n:6} students') 247 logger.info(f'{n:6} students')
214 logger.info(f'{m:6} topics') 248 logger.info(f'{m:6} topics')
215 logger.info(f'{q:6} answers') 249 logger.info(f'{q:6} answers')
216 250
217 - # ------------------------------------------------------------------------  
218 - # helper to manage db sessions using the `with` statement, for example  
219 - # with self.db_session() as s: s.query(...)  
220 - # ------------------------------------------------------------------------  
221 - @contextmanager  
222 - def db_session(self, **kw):  
223 - session = self.Session(**kw)  
224 - try:  
225 - yield session  
226 - session.commit()  
227 - except Exception as e:  
228 - session.rollback()  
229 - raise e  
230 - finally:  
231 - session.close()  
232 -  
233 -  
234 251
235 # ======================================================================== 252 # ========================================================================
236 # methods that do not change state (pure functions) 253 # methods that do not change state (pure functions)
@@ -16,12 +16,23 @@ import functools @@ -16,12 +16,23 @@ import functools
16 import tornado.ioloop 16 import tornado.ioloop
17 import tornado.web 17 import tornado.web
18 import tornado.httpserver 18 import tornado.httpserver
19 -from tornado import iostream 19 +# from tornado import iostream
20 20
21 # this project 21 # this project
22 from learnapp import LearnApp 22 from learnapp import LearnApp
23 from tools import load_yaml, md_to_html 23 from tools import load_yaml, md_to_html
24 24
  25 +# ----------------------------------------------------------------------------
  26 +# Decorator used to restrict access to the administrator
  27 +# ----------------------------------------------------------------------------
  28 +def admin_only(func):
  29 + @functools.wraps(func)
  30 + def wrapper(self, *args, **kwargs):
  31 + if self.current_user != '0':
  32 + raise tornado.web.HTTPError(403) # forbidden
  33 + else:
  34 + func(self, *args, **kwargs)
  35 + return wrapper
25 36
26 # ============================================================================ 37 # ============================================================================
27 # WebApplication - Tornado Web Server 38 # WebApplication - Tornado Web Server
@@ -82,8 +93,7 @@ class LoginHandler(BaseHandler): @@ -82,8 +93,7 @@ class LoginHandler(BaseHandler):
82 uid = self.get_body_argument('uid') 93 uid = self.get_body_argument('uid')
83 pw = self.get_body_argument('pw') 94 pw = self.get_body_argument('pw')
84 95
85 - loop = asyncio.get_event_loop()  
86 - login_ok = await loop.run_in_executor(None, self.learn.login, uid, pw) 96 + login_ok = await self.learn.login(uid, pw)
87 97
88 if login_ok: 98 if login_ok:
89 self.set_secure_cookie("user", str(uid), expires_days=30) 99 self.set_secure_cookie("user", str(uid), expires_days=30)