From 0306dd29e220a34980aa332da61de34cd3dbbece Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 11 Sep 2017 17:29:15 -0500 Subject: [PATCH] Towards HTTP API call rules --- course/utils.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ course/validation.py | 16 ++++++++++++ requirements.txt | 5 ++-- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/course/utils.py b/course/utils.py index 8133a79f..af903128 100644 --- a/course/utils.py +++ b/course/utils.py @@ -147,6 +147,59 @@ class FlowSessionGradingRule(FlowSessionRuleBase): self.bonus_points = bonus_points +def _eval_http_rule(rule, participation, flow_id): + get_parameters = {} + + from relate.utils import struct_to_dict + for key, value in six.iteritems(struct_to_dict(rule.get_parameters)): + if value[0] == "literal": + _, get_value = value + elif value[0] == "field": + _, field_name = value + if field_name == "email": + get_value = participation.user.email + elif field_name == "institutional_id": + get_value = participation.user.institutional_id + else: + raise ValueError("invalid field name '%s'" % field_name) + else: + raise ValueError("invalid GET parameter type '%s'" % value[0]) + + get_parameters[key] = str(get_value) + + # TODO: caching + # TODO: documentation + + import requests + try: + r = requests.get(rule.url, params=get_parameters) + r.raise_for_status() + except requests.exceptions.RequestException as e: + return False + + try: + response_json = r.json() + except ValueError: + return False + + for key, value in six.iteritems(struct_to_dict(rule.response)): + json_value = response_json.get(key) + if value[0] == "required_value": + _, req_value = value + + if json_value != req_value: + return False + + elif value[0] == "cache_until": + pass + # TODO + + else: + raise ValueError("invalid response processing directive '%s'" % value[0]) + + return True + + def _eval_generic_conditions( rule, # type: Any course, # type: Course @@ -182,6 +235,11 @@ def _eval_generic_conditions( if login_exam_ticket.exam.flow_id != flow_id: return False + if hasattr(rule, "if_http_api_call"): + if not _eval_http_rule( + rule.if_external_http, participation, flow_id): + return False + return True diff --git a/course/validation.py b/course/validation.py index 76c1739a..5496d2c6 100644 --- a/course/validation.py +++ b/course/validation.py @@ -554,6 +554,17 @@ def validate_flow_group(vctx, location, grp): # {{{ flow rules +def validate_http_api_call_rule(vctx, location, http_rule): + validate_struct( + vctx, location, http_rule, + required_attrs=[ + ("url", str), + ("get_parameters", Struct), + ("cache_for_max_seconds", int), + ("response", Struct), + ]) + + def validate_session_start_rule(vctx, location, nrule, tags): validate_struct( vctx, location, nrule, @@ -569,6 +580,8 @@ def validate_session_start_rule(vctx, location, nrule, tags): ("if_has_session_tagged", (six.string_types, type(None))), ("if_has_fewer_sessions_than", int), ("if_has_fewer_tagged_sessions_than", int), + ("if_external_http", Struct), + ("if_signed_in_with_matching_exam_ticket", bool), ("tag_session", (six.string_types, type(None))), ("may_start_new_session", bool), @@ -605,6 +618,9 @@ def validate_session_start_rule(vctx, location, nrule, tags): validate_identifier(vctx, "%s: if_has_session_tagged" % location, nrule.if_has_session_tagged) + if hasattr(nrule, "if_http_api_call"): + validate_http_api_call_rule(vctx, location, nrule.if_external_http) + if not hasattr(nrule, "may_start_new_session"): vctx.add_warning( location+", rules", diff --git a/requirements.txt b/requirements.txt index 3a5dbd32..7a598fc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,8 +40,7 @@ paramiko git+https://github.com/inducer/django-bootstrap3-datetimepicker.git # For in-class instant messaging -# dnspython # Py2 -dnspython3 # Py3 +dnspython # Py2 broken was broken in 1.3.1 git+https://github.com/fritzy/SleekXMPP.git@6e27f28c854ce4ae1d9f0cc8ee407bda8de97d3b @@ -112,4 +111,6 @@ typing # For unittest by mock (only required for Py2) # mock +requests + # vim: foldmethod=marker -- GitLab