From 3586cfaba4678c93c4ec985beb50d7c65937d2ab Mon Sep 17 00:00:00 2001 From: Miguel Barao Date: Tue, 20 Nov 2018 17:00:50 +0000 Subject: [PATCH] fixed async login --- learnapp.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------- serve.py | 16 +++++++++++++--- 2 files changed, 79 insertions(+), 52 deletions(-) diff --git a/learnapp.py b/learnapp.py index 86d9eef..87fb66a 100644 --- a/learnapp.py +++ b/learnapp.py @@ -1,8 +1,9 @@ # python standard library -from contextlib import contextmanager # `with` statement in db sessions -import logging from os import path, sys +import logging +from contextlib import contextmanager # `with` statement in db sessions +import asyncio from datetime import datetime # user installed libraries @@ -20,15 +21,42 @@ from tools import load_yaml # setup logger for this module logger = logging.getLogger(__name__) - +# ============================================================================ class LearnAppException(Exception): pass # ============================================================================ +# helper functions +# ============================================================================ +async def check_password(try_pw, password): + try_pw = try_pw.encode('utf-8') + loop = asyncio.get_running_loop() + hashed_pw = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) + return password == hashed_pw + + +# ============================================================================ # LearnApp - application logic # ============================================================================ class LearnApp(object): + # ------------------------------------------------------------------------ + # helper to manage db sessions using the `with` statement, for example + # with self.db_session() as s: s.query(...) + # ------------------------------------------------------------------------ + @contextmanager + def db_session(self, **kw): + session = self.Session(**kw) + try: + yield session + session.commit() + except: + session.rollback() + logger.error('DB rollback!!!') + finally: + session.close() + + # ------------------------------------------------------------------------ def __init__(self, config_file): # state of online students self.online = {} @@ -47,38 +75,44 @@ class LearnApp(object): # ------------------------------------------------------------------------ # login # ------------------------------------------------------------------------ - def login(self, uid, pw): + async def login(self, uid, try_pw): + if uid.startswith('l'): # remove prefix 'l' + uid = uid[1:] with self.db_session() as s: - student = s.query(Student).filter(Student.id == uid).one_or_none() - if student is None: + try: + name, password = s.query(Student.name, Student.password).filter_by(id=uid).one() + except: logger.info(f'User "{uid}" does not exist!') - return False # student does not exist - - if bcrypt.checkpw(pw.encode('utf-8'), student.password): - if uid in self.online: - logger.warning(f'User "{uid}" already logged in, overwriting state') - else: - logger.info(f'User "{uid}" logged in') - - tt = s.query(StudentTopic).filter(StudentTopic.student_id == uid) - state = {t.topic_id: - { - 'level': t.level, - 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f") - } for t in tt} - - self.online[uid] = { - 'number': student.id, - 'name': student.name, - 'state': StudentKnowledge(self.deps, state=state), - # 'learning': None, # current topic learning - } - return True + return False + pw_ok = await check_password(try_pw, password) # async bcrypt + if pw_ok: + if uid in self.online: + logger.warning(f'User "{uid}" already logged in, overwriting state') else: - logger.info(f'User "{uid}" wrong password!') - return False + logger.info(f'User "{uid}" logged in successfully') + + with self.db_session() as s: + tt = s.query(StudentTopic).filter_by(student_id=uid) + + state = {t.topic_id: + { + 'level': t.level, + 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f") + } for t in tt} + + self.online[uid] = { + 'number': uid, + 'name': name, + 'state': StudentKnowledge(self.deps, state=state), + # 'learning': None, # current topic learning + } + + else: + logger.info(f'User "{uid}" wrong password!') + + return pw_ok # ------------------------------------------------------------------------ # logout @@ -206,31 +240,14 @@ class LearnApp(object): n = s.query(Student).count() m = s.query(Topic).count() q = s.query(Answer).count() - except Exception: + except Exception as e: logger.critical(f'Database "{db}" not usable!') - sys.exit(1) + raise e else: logger.info(f'{n:6} students') logger.info(f'{m:6} topics') logger.info(f'{q:6} answers') - # ------------------------------------------------------------------------ - # helper to manage db sessions using the `with` statement, for example - # with self.db_session() as s: s.query(...) - # ------------------------------------------------------------------------ - @contextmanager - def db_session(self, **kw): - session = self.Session(**kw) - try: - yield session - session.commit() - except Exception as e: - session.rollback() - raise e - finally: - session.close() - - # ======================================================================== # methods that do not change state (pure functions) diff --git a/serve.py b/serve.py index 0a109fd..b868bfa 100755 --- a/serve.py +++ b/serve.py @@ -16,12 +16,23 @@ import functools import tornado.ioloop import tornado.web import tornado.httpserver -from tornado import iostream +# from tornado import iostream # this project from learnapp import LearnApp from tools import load_yaml, md_to_html +# ---------------------------------------------------------------------------- +# Decorator used to restrict access to the administrator +# ---------------------------------------------------------------------------- +def admin_only(func): + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + if self.current_user != '0': + raise tornado.web.HTTPError(403) # forbidden + else: + func(self, *args, **kwargs) + return wrapper # ============================================================================ # WebApplication - Tornado Web Server @@ -82,8 +93,7 @@ class LoginHandler(BaseHandler): uid = self.get_body_argument('uid') pw = self.get_body_argument('pw') - loop = asyncio.get_event_loop() - login_ok = await loop.run_in_executor(None, self.learn.login, uid, pw) + login_ok = await self.learn.login(uid, pw) if login_ok: self.set_secure_cookie("user", str(uid), expires_days=30) -- libgit2 0.21.2