From d0c2fc09b6dc0c51167f15361d5a4a4c2050f205 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Tue, 19 Feb 2019 23:52:49 +0100 Subject: First try for token-based params and results requests --- kpov_judge/add_task.py | 2 +- kpov_judge/test_task.py | 70 +++++++++++++++++++---------- kpov_judge/web/kpov_judge/kpov_judge.py | 79 ++++++++++++++++++++++----------- 3 files changed, 101 insertions(+), 50 deletions(-) diff --git a/kpov_judge/add_task.py b/kpov_judge/add_task.py index aab5e46..066efed 100755 --- a/kpov_judge/add_task.py +++ b/kpov_judge/add_task.py @@ -16,7 +16,7 @@ def task_check(results, params): 'results': json.dumps(results), 'params': json.dumps(params) }).encode() - req = urllib.request.Request('{task_url}/{task_name}/results.json'.format(task_url=task_url, task_name=task_name), data) + req = urllib.request.Request('{task_url}/{task_name}/results-token.json'.format(task_url=task_url, task_name=task_name), data) response = urllib.request.urlopen(req) response_dict = json.loads(response.read().decode()) hints = response_dict.get('hints', []) diff --git a/kpov_judge/test_task.py b/kpov_judge/test_task.py index d3dcec7..28fc513 100755 --- a/kpov_judge/test_task.py +++ b/kpov_judge/test_task.py @@ -91,14 +91,9 @@ def locate_task(params, argparser, quiet=False): params['task_url'] = args.task_url if not quiet: params = get_params(params, url_meta) - # then the student's ID (and password if neccessarry) - fetch_params_meta = collections.OrderedDict({ - 'username': {'descriptions': {'si': 'Uporabniško ime', 'en': 'Username'}}, - }) - if params.get('task_url', '').startswith('http'): - fetch_params_meta['password'] = {'descriptions': {'si': 'Geslo', 'en': 'Password'}, 'masked': True} + # and finally, the name of the task - fetch_params_meta['task_name'] = {'descriptions': {'si': 'Ime naloge', 'en': 'Task name'}, } + fetch_params_meta = collections.OrderedDict({'task_name': {'descriptions': {'si': 'Ime naloge', 'en': 'Task name'}}}) add_meta_to_argparser(argparser, meta=fetch_params_meta, defaults=params) args, unknown_args = argparser.parse_known_args() # update params with the now known args @@ -106,6 +101,7 @@ def locate_task(params, argparser, quiet=False): params[k] = vars(args).get(k, params.get(k, None)) if not quiet: params = get_params(params, fetch_params_meta) + print() return params def load_params(filename): @@ -113,7 +109,8 @@ def load_params(filename): return yaml.load(open(filename)) except: return {} - + + if __name__ == '__main__': # get the parameters needed to get to the task, such as the URLs, the name of the task and optionally an ID from the student # start with the the parameters needed for the dialog gui to work @@ -124,7 +121,7 @@ if __name__ == '__main__': help='disable prompts') argparser.add_argument('-g', '--generate_params', action='store_true', help='generate initial values for the task parameters') - argparser.add_argument('-pf','--params_file', nargs='?', default=PARAMS_FILE, + argparser.add_argument('-pf', '--params_file', nargs='?', default=PARAMS_FILE, help='a local file containing saved param values') basic_args, unknown_args = argparser.parse_known_args() @@ -143,34 +140,59 @@ if __name__ == '__main__': try: task_url = params['task_url'] task_name = params['task_name'] - if task_url.startswith('http'): - http_auth(task_url, params['username'], params['password']) - print("Fetching {task_url}/{task_name}/task.py…".format(**params)) + source = urllib.request.urlopen("{task_url}/{task_name}/task.py".format(**params)) task, task_check, task_params_meta, gen_params = load_task(source) except Exception as e: - print(str(e)) - print() - for k, v in params.items(): - if k not in ('password', 'task_params'): - print('{}: {}'.format(k, v)) + import traceback + traceback.print_exc() with open(basic_args.params_file, 'w') as f: yaml.dump(params, f) exit(1) - # get task parameters + # get stored task parameters params['task_params'] = params.get('task_params', {}) - params['task_params'][params['task_name']] = params['task_params'].get(params['task_name'], {}) - task_params = params['task_params'][params['task_name']] + task_params = params['task_params'].setdefault(task_name, {}) + + # ensure we have a submission token + if task_url.startswith('http'): + # check if existing token is valid + if task_params.get('token'): + response = urllib.request.urlopen( + '{task_url}/{task_name}/params-token.json'.format(**params), + data=urllib.parse.urlencode({'params': json.dumps(task_params)}).encode()) + response = json.load(io.TextIOWrapper(response)) + if response: + # got a good token + task_params.update(response) + else: + # did not get a token, try again with password + del task_params['token'] + + # authenticate to get a new token + if not task_params.get('token'): + # get the student's ID and password + # TODO clunky, should refactor all argument-getting stuff + fetch_params_meta = {'username': {'descriptions': {'si': 'Uporabniško ime', 'en': 'Username'}}} + params = get_params(params, fetch_params_meta, params['language']) + fetch_pass_meta = {'password': {'descriptions': {'si': 'Geslo', 'en': 'Password'}, 'masked': True}} + params_pass = get_params({}, fetch_pass_meta, params['language']) + + try: + http_auth(task_url, params['username'], params_pass['password']) + response = urllib.request.urlopen('{task_url}/{task_name}/params.json'.format(**params)) + response = json.load(io.TextIOWrapper(response)) + if response: + task_params.update(response) + except Exception as ex: + print(ex) + exit(2) + if basic_args.generate_params: #prejema lahko samo stringe in ne številk (potrebno je str(int) # print ("params before: {} {}".format(params, task_params)) task_params.update(gen_params(params['username'], task_params_meta)) # print ("params after: {} {}".format(params, task_params)) - if task_url.startswith('http'): - response = urllib.request.urlopen('{task_url}/{task_name}/params.json'.format(**params)) - web_task_params = json.load(io.TextIOWrapper(response)) - task_params.update(web_task_params) task_argparser = argparse.ArgumentParser(parents=[argparser], conflict_handler='resolve', add_help=True) add_meta_to_argparser(task_argparser, task_params_meta, defaults=task_params) diff --git a/kpov_judge/web/kpov_judge/kpov_judge.py b/kpov_judge/web/kpov_judge/kpov_judge.py index 95d7199..ddef16d 100755 --- a/kpov_judge/web/kpov_judge/kpov_judge.py +++ b/kpov_judge/web/kpov_judge/kpov_judge.py @@ -6,6 +6,7 @@ import json import random import settings import traceback +import uuid from kpov_draw_setup import draw_setup import kpov_util @@ -70,10 +71,15 @@ def class_tasks(class_id): return render_template('class_tasks.html', student_id=student_id, tasks=task_list, clas=clas) -def results_post(class_id, task_id, results): +def results_post(class_id, task_id, token, results): student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') db = g.db - params = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'student_id': student_id})['params'] + #params = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'student_id': student_id})['params'] + print(class_id, task_id, token) + params = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'params.token': token})['params'] + if not params: + raise Exception('Invalid token.') + if params is None: # params = {} #else: @@ -104,10 +110,14 @@ def results_post(class_id, task_id, results): return {'result': res, 'hints': hints, 'status': res_status} -def results_dict(class_id, task_id): - student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') +def results_dict(class_id, task_id, token): db = g.db try: + task_params = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'token': token}) + if not task_params: + raise Exception('Invalid token.') + #student_id = flask.app.request.environ.get('REMOTE_USER') + student_id = task_params['student_id'] entry = db.results.find_one( {'$query': {'class_id': class_id, 'task_id': task_id, 'student_id': student_id}, # vsi uporabniki brez nastavljenega REMOTE_USER (i.e. Apache basic auth) imajo skupne rezultate, napaka? '$orderby': {'time': -1}}, @@ -122,9 +132,11 @@ def results_dict(class_id, task_id): @app.route('/tasks///results.json', methods=['GET', 'POST']) def results_json(class_id, task_id): if flask.app.request.method == 'POST': - return json.dumps(results_post(class_id, task_id, - json.loads(flask.app.request.form['results']))) - return json.dumps(results_dict(class_id, task_id)) + return json.dumps( + results_post(class_id, task_id, + json.loads(flask.app.request.form['params']).get('token'), + json.loads(flask.app.request.form['results']))) + return json.dumps(results_dict(class_id, task_id, request.args.get('token'))) @app.route('/tasks////setup.', methods=['GET']) @@ -156,11 +168,20 @@ def task_html(class_id, task_id): return render_template('task.html', task=task_source(class_id, task_id)) +def make_token(student_id): + # TODO need nginx support, in version 1.11.3, not yet in debian stable + #import jwt + #message = {'student_id': student_id} + #return jwt.encode(message, app.config['JWT_SECRET'], algorithm='HS512').decode('utf-8') + return str(uuid.uuid4()) + def get_params(class_id, task_id, student_id, db): try: meta = db.task_params_meta.find_one({'class_id': class_id, 'task_id': task_id})['params'] + meta['token'] = {'public': True, 'generated': True, 'type': 'password', 'w': False} except Exception: return {'mama': 'ZAKVAJ?'}, {'mama': {'public': True}} + params = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'student_id': student_id}) if params is None: try: @@ -281,29 +302,37 @@ def task_greeting(class_id, task_id, lang): **{p['name']: p['value'] for p in public_params}) -@app.route('/tasks///params.json', methods=['GET', 'POST']) -def params_json(class_id, task_id): - student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') +@app.route('/tasks///params.json') +def params_json(class_id, task_id, student_id=None): + if not student_id: + student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') db = g.db params, meta = get_params(class_id, task_id, student_id, db) shown_params = {} - if flask.app.request.method == 'POST': - try: - new_params = json.loads(flask.app.request.form['params']) - except Exception: - new_params = {} - for name in params.items(): - if meta.get(name, {'w': False}).get('w', False) and k in new_params: - params[name] = new_params[name] - if meta.get(name, {'public': False})['public']: - shown_params[name] = params[name] - db.task_params.update({'class_id': class_id, 'task_id': task_id, 'student_id': student_id}, {'$set': {'params': params}}) - else: - for name, param in params.items(): - if meta.get(name, {'public': False})['public']: - shown_params[name] = param + for name, param in params.items(): + if meta.get(name, {'public': False})['public']: + shown_params[name] = param return json.dumps(shown_params) +@app.route('/tasks///params-token.json', methods=['POST']) +def params_token_json(class_id, task_id): + db = g.db + token = json.loads(flask.app.request.form['params']).get('token', '') + record = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'params.token': token}) + if not record: + return json.dumps({}) + return params_json(record['class_id'], record['task_id'], record['student_id']) + +@app.route('/tasks///results-token.json', methods=['GET', 'POST']) +def results_token_json(class_id, task_id): + db = g.db + token = json.loads(flask.app.request.form['params']).get('token', '') + record = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'params.token': token}) + if not record: + return json.dumps({}) + return results_json(class_id, task_id) + + if __name__ == '__main__': app.run(host='0.0.0.0') -- cgit v1.2.1