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 32 hashed = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password)
33 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 42 # Application
... ... @@ -108,7 +113,7 @@ class App(object):
108 113  
109 114 # first login updates the password
110 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 117 pw_ok = True
113 118 else: # check password
114 119 pw_ok = await check_password(try_pw, password) # async bcrypt
... ... @@ -209,14 +214,14 @@ class App(object):
209 214 # -----------------------------------------------------------------------
210 215  
211 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 220 def get_student_test(self, uid, default=None):
216 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 226 def get_student_grades_from_all_tests(self, uid):
222 227 with self.db_session() as s:
... ... @@ -226,8 +231,8 @@ class App(object):
226 231 with self.db_session() as s:
227 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 237 def get_all_students(self):
233 238 with self.db_session() as s:
... ... @@ -249,16 +254,16 @@ class App(object):
249 254 # 'focus': self.online.get(uid, {}).get('student', {}).get('focus', True), # FIXME
250 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 268 # --- helpers (change state)
264 269 def allow_student(self, uid):
... ... @@ -269,9 +274,9 @@ class App(object):
269 274 self.allowed.discard(uid)
270 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 278 if pw:
274   - pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
  279 + pw = await hash_password(pw)
275 280 with self.db_session() as s:
276 281 student = s.query(Student).filter_by(id=uid).one()
277 282 student.password = pw
... ...
perguntations/factory.py
... ... @@ -31,8 +31,8 @@ from os import path
31 31 import logging
32 32  
33 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 36 QuestionTextRegex, QuestionNumericInterval,
37 37 QuestionTextArea, QuestionInformation)
38 38  
... ...
perguntations/serve.py
... ... @@ -10,7 +10,6 @@ import logging.config
10 10 import argparse
11 11 import mimetypes
12 12 import signal
13   -# import asyncio
14 13 import functools
15 14 import json
16 15 import ssl
... ... @@ -19,8 +18,6 @@ import ssl
19 18 import tornado.ioloop
20 19 import tornado.web
21 20 import tornado.httpserver
22   -# from tornado import template, gen, websocket
23   -# import yaml
24 21  
25 22 # this project
26 23 from perguntations.app import App, AppException
... ... @@ -33,11 +30,11 @@ from perguntations import APP_NAME
33 30 # ----------------------------------------------------------------------------
34 31 def admin_only(func):
35 32 @functools.wraps(func)
36   - def wrapper(self, *args, **kwargs):
  33 + async def wrapper(self, *args, **kwargs):
37 34 if self.current_user != '0':
38 35 raise tornado.web.HTTPError(403) # forbidden
39 36 else:
40   - func(self, *args, **kwargs)
  37 + await func(self, *args, **kwargs)
41 38 return wrapper
42 39  
43 40  
... ... @@ -53,7 +50,7 @@ class WebApplication(tornado.web.Application):
53 50 (r'/review', ReviewHandler),
54 51 (r'/admin', AdminHandler),
55 52 (r'/file', FileHandler),
56   - # (r'/ws', SocketHandler),
  53 + # (r'/ws', AdminWebSocketHandler),
57 54 (r'/', RootHandler), # TODO multiple tests
58 55 ]
59 56  
... ... @@ -142,7 +139,7 @@ class FileHandler(BaseHandler):
142 139 uid = self.current_user
143 140 ref = self.get_query_argument('ref', None)
144 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 144 if uid != '0':
148 145 t = self.testapp.get_student_test(uid)
... ... @@ -163,6 +160,8 @@ class FileHandler(BaseHandler):
163 160 logging.error(f'File not found: {filepath}')
164 161 except PermissionError:
165 162 logging.error(f'No permission: {filepath}')
  163 + except OSError:
  164 + logging.error(f'Error opening file: {filepath}')
166 165 else:
167 166 data = f.read()
168 167 f.close()
... ... @@ -266,7 +265,7 @@ class ReviewHandler(BaseHandler):
266 265  
267 266 @tornado.web.authenticated
268 267 @admin_only
269   - def get(self):
  268 + async def get(self):
270 269 test_id = self.get_query_argument('test_id', None)
271 270 logging.info(f'Review test {test_id}.')
272 271 fname = self.testapp.get_json_filename_of_test(test_id)
... ... @@ -290,7 +289,7 @@ class AdminHandler(BaseHandler):
290 289  
291 290 @tornado.web.authenticated
292 291 @admin_only
293   - def get(self):
  292 + async def get(self):
294 293 cmd = self.get_query_argument('cmd', default=None)
295 294  
296 295 if cmd == 'students_table':
... ... @@ -312,7 +311,7 @@ class AdminHandler(BaseHandler):
312 311  
313 312 @tornado.web.authenticated
314 313 @admin_only
315   - def post(self):
  314 + async def post(self):
316 315 cmd = self.get_body_argument('cmd', None)
317 316 value = self.get_body_argument('value', None)
318 317  
... ... @@ -323,14 +322,14 @@ class AdminHandler(BaseHandler):
323 322 self.testapp.deny_student(value)
324 323  
325 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 327 elif cmd == 'insert_student':
329 328 s = json.loads(value)
330 329 self.testapp.insert_new_student(uid=s['number'], name=s['name'])
331 330  
332 331 else:
333   - logging.error(f'Unknown command in post: "{cmd}"')
  332 + logging.error(f'Unknown command: "{cmd}"')
334 333  
335 334  
336 335 # -------------------------------------------------------------------------
... ...