Commit 50eac3c3b44c79bc30180587742f3b45435b0364

Authored by Miguel Barão
1 parent 08a607f3
Exists in master and in 1 other branch dev

async update_student_password

- was using bcrypt in synchronous way, which is slow on first login of many students.
- commented functions from app.py that are not being used.
perguntations/app.py
@@ -32,6 +32,11 @@ async def check_password(try_pw, password): @@ -32,6 +32,11 @@ async def check_password(try_pw, password):
32 hashed = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) 32 hashed = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password)
33 return password == hashed 33 return password == hashed
34 34
  35 +async def hash_password(pw):
  36 + pw = pw.encode('utf-8')
  37 + loop = asyncio.get_running_loop()
  38 + return await loop.run_in_executor(None, bcrypt.hashpw, pw, bcrypt.gensalt())
  39 +
35 40
36 # ============================================================================ 41 # ============================================================================
37 # Application 42 # Application
@@ -108,7 +113,7 @@ class App(object): @@ -108,7 +113,7 @@ class App(object):
108 113
109 # first login updates the password 114 # first login updates the password
110 if password == '': # update password on first login 115 if password == '': # update password on first login
111 - self.update_student_password(uid, try_pw) 116 + await self.update_student_password(uid, try_pw)
112 pw_ok = True 117 pw_ok = True
113 else: # check password 118 else: # check password
114 pw_ok = await check_password(try_pw, password) # async bcrypt 119 pw_ok = await check_password(try_pw, password) # async bcrypt
@@ -209,14 +214,14 @@ class App(object): @@ -209,14 +214,14 @@ class App(object):
209 # ----------------------------------------------------------------------- 214 # -----------------------------------------------------------------------
210 215
211 # --- helpers (getters) 216 # --- helpers (getters)
212 - def get_student_name(self, uid):  
213 - return self.online[uid]['student']['name'] 217 + # def get_student_name(self, uid):
  218 + # return self.online[uid]['student']['name']
214 219
215 def get_student_test(self, uid, default=None): 220 def get_student_test(self, uid, default=None):
216 return self.online[uid].get('test', default) 221 return self.online[uid].get('test', default)
217 222
218 - def get_questions_path(self):  
219 - return self.testfactory['questions_dir'] 223 + # def get_questions_dir(self):
  224 + # return self.testfactory['questions_dir']
220 225
221 def get_student_grades_from_all_tests(self, uid): 226 def get_student_grades_from_all_tests(self, uid):
222 with self.db_session() as s: 227 with self.db_session() as s:
@@ -226,8 +231,8 @@ class App(object): @@ -226,8 +231,8 @@ class App(object):
226 with self.db_session() as s: 231 with self.db_session() as s:
227 return s.query(Test.filename).filter_by(id=test_id).scalar() 232 return s.query(Test.filename).filter_by(id=test_id).scalar()
228 233
229 - def get_online_students(self): # [('uid', 'name', 'starttime')]  
230 - return [(k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k, v in self.online.items() if k != '0'] 234 + # def get_online_students(self): # [('uid', 'name', 'starttime')]
  235 + # return [(k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k, v in self.online.items() if k != '0']
231 236
232 def get_all_students(self): 237 def get_all_students(self):
233 with self.db_session() as s: 238 with self.db_session() as s:
@@ -249,16 +254,16 @@ class App(object): @@ -249,16 +254,16 @@ class App(object):
249 # 'focus': self.online.get(uid, {}).get('student', {}).get('focus', True), # FIXME 254 # 'focus': self.online.get(uid, {}).get('student', {}).get('focus', True), # FIXME
250 } for uid, name, pw in self.get_all_students()] 255 } for uid, name, pw in self.get_all_students()]
251 256
252 - def get_allowed_students(self):  
253 - # set of 'uid' allowed to login  
254 - return self.allowed 257 + # def get_allowed_students(self):
  258 + # # set of 'uid' allowed to login
  259 + # return self.allowed
255 260
256 - def get_file(self, uid, ref, key):  
257 - # get filename of (uid, ref, name) if declared in the question  
258 - t = self.get_test(uid)  
259 - for q in t['questions']:  
260 - if q['ref'] == ref and key in q['files']:  
261 - return path.abspath(path.join(q['path'], q['files'][key])) 261 + # def get_file(self, uid, ref, key):
  262 + # # get filename of (uid, ref, name) if declared in the question
  263 + # t = self.get_student_test(uid)
  264 + # for q in t['questions']:
  265 + # if q['ref'] == ref and key in q['files']:
  266 + # return path.abspath(path.join(q['path'], q['files'][key]))
262 267
263 # --- helpers (change state) 268 # --- helpers (change state)
264 def allow_student(self, uid): 269 def allow_student(self, uid):
@@ -269,9 +274,9 @@ class App(object): @@ -269,9 +274,9 @@ class App(object):
269 self.allowed.discard(uid) 274 self.allowed.discard(uid)
270 logger.info(f'Student {uid}: denied to login') 275 logger.info(f'Student {uid}: denied to login')
271 276
272 - def update_student_password(self, uid, pw=''): 277 + async def update_student_password(self, uid, pw=''):
273 if pw: 278 if pw:
274 - pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt()) 279 + pw = await hash_password(pw)
275 with self.db_session() as s: 280 with self.db_session() as s:
276 student = s.query(Student).filter_by(id=uid).one() 281 student = s.query(Student).filter_by(id=uid).one()
277 student.password = pw 282 student.password = pw
perguntations/factory.py
@@ -31,8 +31,8 @@ from os import path @@ -31,8 +31,8 @@ from os import path
31 import logging 31 import logging
32 32
33 # this project 33 # this project
34 -from .tools import load_yaml, run_script  
35 -from .questions import (QuestionRadio, QuestionCheckbox, QuestionText, 34 +from perguntations.tools import load_yaml, run_script
  35 +from perguntations.questions import (QuestionRadio, QuestionCheckbox, QuestionText,
36 QuestionTextRegex, QuestionNumericInterval, 36 QuestionTextRegex, QuestionNumericInterval,
37 QuestionTextArea, QuestionInformation) 37 QuestionTextArea, QuestionInformation)
38 38
perguntations/serve.py
@@ -10,7 +10,6 @@ import logging.config @@ -10,7 +10,6 @@ import logging.config
10 import argparse 10 import argparse
11 import mimetypes 11 import mimetypes
12 import signal 12 import signal
13 -# import asyncio  
14 import functools 13 import functools
15 import json 14 import json
16 import ssl 15 import ssl
@@ -19,8 +18,6 @@ import ssl @@ -19,8 +18,6 @@ import ssl
19 import tornado.ioloop 18 import tornado.ioloop
20 import tornado.web 19 import tornado.web
21 import tornado.httpserver 20 import tornado.httpserver
22 -# from tornado import template, gen, websocket  
23 -# import yaml  
24 21
25 # this project 22 # this project
26 from perguntations.app import App, AppException 23 from perguntations.app import App, AppException
@@ -33,11 +30,11 @@ from perguntations import APP_NAME @@ -33,11 +30,11 @@ from perguntations import APP_NAME
33 # ---------------------------------------------------------------------------- 30 # ----------------------------------------------------------------------------
34 def admin_only(func): 31 def admin_only(func):
35 @functools.wraps(func) 32 @functools.wraps(func)
36 - def wrapper(self, *args, **kwargs): 33 + async def wrapper(self, *args, **kwargs):
37 if self.current_user != '0': 34 if self.current_user != '0':
38 raise tornado.web.HTTPError(403) # forbidden 35 raise tornado.web.HTTPError(403) # forbidden
39 else: 36 else:
40 - func(self, *args, **kwargs) 37 + await func(self, *args, **kwargs)
41 return wrapper 38 return wrapper
42 39
43 40
@@ -53,7 +50,7 @@ class WebApplication(tornado.web.Application): @@ -53,7 +50,7 @@ class WebApplication(tornado.web.Application):
53 (r'/review', ReviewHandler), 50 (r'/review', ReviewHandler),
54 (r'/admin', AdminHandler), 51 (r'/admin', AdminHandler),
55 (r'/file', FileHandler), 52 (r'/file', FileHandler),
56 - # (r'/ws', SocketHandler), 53 + # (r'/ws', AdminWebSocketHandler),
57 (r'/', RootHandler), # TODO multiple tests 54 (r'/', RootHandler), # TODO multiple tests
58 ] 55 ]
59 56
@@ -142,7 +139,7 @@ class FileHandler(BaseHandler): @@ -142,7 +139,7 @@ class FileHandler(BaseHandler):
142 uid = self.current_user 139 uid = self.current_user
143 ref = self.get_query_argument('ref', None) 140 ref = self.get_query_argument('ref', None)
144 image = self.get_query_argument('image', None) 141 image = self.get_query_argument('image', None)
145 - content_type, encoding = mimetypes.guess_type(image) 142 + content_type = mimetypes.guess_type(image)[0]
146 143
147 if uid != '0': 144 if uid != '0':
148 t = self.testapp.get_student_test(uid) 145 t = self.testapp.get_student_test(uid)
@@ -163,6 +160,8 @@ class FileHandler(BaseHandler): @@ -163,6 +160,8 @@ class FileHandler(BaseHandler):
163 logging.error(f'File not found: {filepath}') 160 logging.error(f'File not found: {filepath}')
164 except PermissionError: 161 except PermissionError:
165 logging.error(f'No permission: {filepath}') 162 logging.error(f'No permission: {filepath}')
  163 + except OSError:
  164 + logging.error(f'Error opening file: {filepath}')
166 else: 165 else:
167 data = f.read() 166 data = f.read()
168 f.close() 167 f.close()
@@ -266,7 +265,7 @@ class ReviewHandler(BaseHandler): @@ -266,7 +265,7 @@ class ReviewHandler(BaseHandler):
266 265
267 @tornado.web.authenticated 266 @tornado.web.authenticated
268 @admin_only 267 @admin_only
269 - def get(self): 268 + async def get(self):
270 test_id = self.get_query_argument('test_id', None) 269 test_id = self.get_query_argument('test_id', None)
271 logging.info(f'Review test {test_id}.') 270 logging.info(f'Review test {test_id}.')
272 fname = self.testapp.get_json_filename_of_test(test_id) 271 fname = self.testapp.get_json_filename_of_test(test_id)
@@ -290,7 +289,7 @@ class AdminHandler(BaseHandler): @@ -290,7 +289,7 @@ class AdminHandler(BaseHandler):
290 289
291 @tornado.web.authenticated 290 @tornado.web.authenticated
292 @admin_only 291 @admin_only
293 - def get(self): 292 + async def get(self):
294 cmd = self.get_query_argument('cmd', default=None) 293 cmd = self.get_query_argument('cmd', default=None)
295 294
296 if cmd == 'students_table': 295 if cmd == 'students_table':
@@ -312,7 +311,7 @@ class AdminHandler(BaseHandler): @@ -312,7 +311,7 @@ class AdminHandler(BaseHandler):
312 311
313 @tornado.web.authenticated 312 @tornado.web.authenticated
314 @admin_only 313 @admin_only
315 - def post(self): 314 + async def post(self):
316 cmd = self.get_body_argument('cmd', None) 315 cmd = self.get_body_argument('cmd', None)
317 value = self.get_body_argument('value', None) 316 value = self.get_body_argument('value', None)
318 317
@@ -323,14 +322,14 @@ class AdminHandler(BaseHandler): @@ -323,14 +322,14 @@ class AdminHandler(BaseHandler):
323 self.testapp.deny_student(value) 322 self.testapp.deny_student(value)
324 323
325 elif cmd == 'reset_password': 324 elif cmd == 'reset_password':
326 - self.testapp.update_student_password(uid=value, pw='') 325 + await self.testapp.update_student_password(uid=value, pw='')
327 326
328 elif cmd == 'insert_student': 327 elif cmd == 'insert_student':
329 s = json.loads(value) 328 s = json.loads(value)
330 self.testapp.insert_new_student(uid=s['number'], name=s['name']) 329 self.testapp.insert_new_student(uid=s['number'], name=s['name'])
331 330
332 else: 331 else:
333 - logging.error(f'Unknown command in post: "{cmd}"') 332 + logging.error(f'Unknown command: "{cmd}"')
334 333
335 334
336 # ------------------------------------------------------------------------- 335 # -------------------------------------------------------------------------