From 2dd80251f10b11030abd36e56aff0d1f3a86a754 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 20 Feb 2019 03:24:54 +0100 Subject: Fix and clean up token-based access to params and results --- kpov_judge/add_task.py | 15 ++- kpov_judge/test_task.py | 61 ++++++------ kpov_judge/web/kpov_judge/kpov_judge.py | 161 +++++++++++++------------------- 3 files changed, 107 insertions(+), 130 deletions(-) diff --git a/kpov_judge/add_task.py b/kpov_judge/add_task.py index 066efed..5b77668 100755 --- a/kpov_judge/add_task.py +++ b/kpov_judge/add_task.py @@ -12,12 +12,17 @@ import pymongo from bson import Binary def task_check(results, params): - data = urllib.parse.urlencode({ + data = { 'results': json.dumps(results), - 'params': json.dumps(params) - }).encode() - 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) + 'params': json.dumps({k: v for k, v in params.items() if k != 'token'}), + } + # should be an argument to task_check, but probably better not modify the signature… + if 'token' in params: + data['token'] = params['token'] + + response = urllib.request.urlopen( + '{task_url}/{task_name}/results.json'.format(task_url=task_url, task_name=task_name), + data=urllib.parse.urlencode(data).encode()) response_dict = json.loads(response.read().decode()) hints = response_dict.get('hints', []) hints = ['status: ' + response_dict.get('status', '')] + hints diff --git a/kpov_judge/test_task.py b/kpov_judge/test_task.py index 28fc513..a049bee 100755 --- a/kpov_judge/test_task.py +++ b/kpov_judge/test_task.py @@ -153,40 +153,42 @@ if __name__ == '__main__': # get stored task parameters params['task_params'] = params.get('task_params', {}) task_params = params['task_params'].setdefault(task_name, {}) + tokens = params.setdefault('tokens', {}) # 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']) - + n_tries = 3 + while n_tries > 0: 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) + if tokens.get(task_name): + response = urllib.request.urlopen( + '{task_url}/{task_name}/params.json'.format(**params), + data=urllib.parse.urlencode({'token': tokens.get(task_name)}).encode()) + response = json.load(io.TextIOWrapper(response)) + if response: + # got params + task_params.update(response) + break + else: + # did not get a token, try again with password + del tokens[task_name] + n_tries -= 1 + + if not tokens.get(task_name): + # 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']) + + http_auth(task_url, params['username'], params_pass['password']) + response = urllib.request.urlopen('{task_url}/{task_name}/token.json'.format(**params)) + response = json.load(io.TextIOWrapper(response)) + if response: + tokens[task_name] = response['token'] except Exception as ex: print(ex) - exit(2) if basic_args.generate_params: #prejema lahko samo stringe in ne številk (potrebno je str(int) @@ -210,6 +212,7 @@ if __name__ == '__main__': for k in inspect.getargs(task.__code__)[0]: public_params[k] = task_params[k] params['task_params'][params['task_name']] = task_params + # save parameters for the next run with open(basic_args.params_file, 'w') as f: yaml.dump(params, f) @@ -219,7 +222,9 @@ if __name__ == '__main__': print('Running task… ') task_result = task(**public_params) print('Checking task… ') + task_params['token'] = tokens[task_name] # hack to avoid changing task_check signature score, hints = task_check(task_result, task_params) + del task_params['token'] print('Done!') print() print('Score: {}'.format(score)) diff --git a/kpov_judge/web/kpov_judge/kpov_judge.py b/kpov_judge/web/kpov_judge/kpov_judge.py index ddef16d..7415724 100755 --- a/kpov_judge/web/kpov_judge/kpov_judge.py +++ b/kpov_judge/web/kpov_judge/kpov_judge.py @@ -71,74 +71,6 @@ 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, 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'] - 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: - # params = params['params'] - return {'result': 0, 'hints': ['task not found'], status: 'NOT OK'} # no such task - user_params = json.loads(flask.app.request.form['params']) - meta = db.task_params_meta.find_one({'task_id': task_id}) - if meta is None: - meta = {} - else: - meta = meta['params'] - for param_name, param_meta in meta.items(): - if param_meta.get('w', False) and param_name in user_params: - params[param_name] = user_params[param_name] - try: - task_check_source = db.task_checkers.find_one({'class_id': class_id, 'task_id': task_id})['source'] - d = {} - exec(compile(task_check_source, 'checker.py', 'exec'), globals(), d) - res, hints = d['task_check'](collections.defaultdict(str, results), params) - except Exception as e: - hints = ["Checker died: " + str(e)] - res = 0 - if (isinstance(res, int) or isinstance(res, float)) and res > 0: - res_status = 'OK' - else: - res_status = 'NOT OK' - db.results.insert({'class_id': class_id, 'task_id': task_id, 'result': res, 'hints': hints, 'status': res_status,'student_id': student_id, 'response': results, 'time': datetime.datetime.now()}) - return {'result': res, 'hints': hints, 'status': res_status} - - -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}}, - {'result': 1, 'status': 1, 'hints': 1, '_id': 0}) - if entry is None: - return {'result': 'Naloga ni bila nikdar ocenjena', 'status': 'NOT OK'} - return entry - except Exception as e: - return {'Error': str(e)} - - -@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['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']) def setup_svg(class_id, task_id, lang, ending): db = g.db @@ -168,22 +100,14 @@ 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: + if params is None or 'params' not in params: # TODO try with $exists: params or smth. try: gen_params_source = db.gen_params.find_one({'class_id': class_id, 'task_id': task_id})['source'] gen_params_code = compile(gen_params_source, 'generator.py', 'exec') @@ -302,12 +226,24 @@ def task_greeting(class_id, task_id, lang): **{p['name']: p['value'] for p in public_params}) -@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') +@app.route('/tasks///token.json') +def get_token(class_id, task_id): db = g.db - params, meta = get_params(class_id, task_id, student_id, db) + student_id = flask.app.request.environ.get('REMOTE_USER', 'Nobody') + token = str(uuid.uuid4()) + db.task_params.update({'class_id': class_id, 'task_id': task_id, 'student_id': student_id}, + {'$set': {'token': token}}, upsert=True) + return json.dumps({'token': token}) + + +@app.route('/tasks///params.json', methods=['POST']) +def params_json(class_id, task_id): + db = g.db + token = flask.app.request.form['token'] + record = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'token': token}) + if not record: + return json.dumps({}) + params, meta = get_params(record['class_id'], record['task_id'], record['student_id'], db) shown_params = {} for name, param in params.items(): if meta.get(name, {'public': False})['public']: @@ -315,23 +251,54 @@ def params_json(class_id, task_id, student_id=None): return json.dumps(shown_params) -@app.route('/tasks///params-token.json', methods=['POST']) -def params_token_json(class_id, task_id): +@app.route('/tasks///results.json', methods=['POST']) +def results_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']) + token = flask.app.request.form.get('token', '') + task = db.task_params.find_one({'class_id': class_id, 'task_id': task_id, 'token': token}) + if not task: + return json.dumps({'result': 0, 'hints': ['invalid token'], 'status': 'NOT OK'}) -@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) + params = task['params'] + if params is None: + return json.dumps({'result': 0, 'hints': ['no parameters found for task'], 'status': 'NOT OK'}) # no such task + + results = json.loads(flask.app.request.form['results']) + user_params = json.loads(flask.app.request.form['params']) + + meta = db.task_params_meta.find_one({'task_id': task_id}) + if meta is None: + meta = {} + else: + meta = meta['params'] + for param_name, param_meta in meta.items(): + if param_meta.get('w', False) and param_name in user_params: + params[param_name] = user_params[param_name] + + # hack to get token into task_check function + # TODO rethink the API + params['token'] = token + try: + task_check_source = db.task_checkers.find_one({'class_id': class_id, 'task_id': task_id})['source'] + d = {} + exec(compile(task_check_source, 'checker.py', 'exec'), globals(), d) + res, hints = d['task_check'](collections.defaultdict(str, results), params) + except Exception as e: + hints = ["Checker died: " + str(e)] + res = 0 + if (isinstance(res, int) or isinstance(res, float)) and res > 0: + res_status = 'OK' + else: + res_status = 'NOT OK' + + db.results.insert({ + 'class_id': class_id, 'task_id': task_id, + 'result': res, 'hints': hints, 'status': res_status, + 'student_id': task['student_id'], + 'response': results, + 'time': datetime.datetime.now() + }) + return json.dumps({'result': res, 'hints': hints, 'status': res_status}) if __name__ == '__main__': -- cgit v1.2.1