From 89a4737da8bb3f52b6d1abbc13abe3302c4993cd Mon Sep 17 00:00:00 2001 From: Jason Bau Date: Thu, 12 Sep 2013 12:16:47 -0700 Subject: [PATCH 01/13] fix default for VERIFY_STUDENT in lms/envs/aws.py --- lms/envs/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 0f5de94c76..30e7183317 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -256,4 +256,4 @@ BROKER_URL = "{0}://{1}:{2}@{3}/{4}".format(CELERY_BROKER_TRANSPORT, CELERY_BROKER_VHOST) # Student identity verification settings -VERIFY_STUDENT = AUTH_TOKENS.get("VERIFY_STUDENT", "") +VERIFY_STUDENT = AUTH_TOKENS.get("VERIFY_STUDENT", VERIFY_STUDENT) From 3b44ec11e0823faa26f12e5a76ae139fb9a0d554 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Sun, 15 Sep 2013 21:16:17 -0400 Subject: [PATCH 02/13] Convert JSON dumps output to UTF-8 before sending to Software Secure --- lms/djangoapps/verify_student/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index a7f1caaca7..485214d88b 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -500,7 +500,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): header_txt = "\n".join( "{}: {}".format(h, v) for h,v in sorted(headers.items()) ) - body_txt = json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False) + body_txt = json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False).encode('utf-8') return header_txt + "\n\n" + body_txt @@ -509,7 +509,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): response = requests.post( settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_URL"], headers=headers, - data=json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False) + data=json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False).encode('utf-8') ) log.debug("Sent request to Software Secure for {}".format(self.receipt_id)) log.debug("Headers:\n{}\n\n".format(headers)) From b1be80b8485116e0d3dca85e2ccd655e374d7702 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 16 Sep 2013 01:59:26 -0400 Subject: [PATCH 03/13] Extend message signing code to work with dicts, lists. --- lms/djangoapps/verify_student/ssencrypt.py | 33 +++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/verify_student/ssencrypt.py b/lms/djangoapps/verify_student/ssencrypt.py index 862c5aa021..3a110b8d04 100644 --- a/lms/djangoapps/verify_student/ssencrypt.py +++ b/lms/djangoapps/verify_student/ssencrypt.py @@ -127,9 +127,7 @@ def generate_signed_message(method, headers_dict, body_dict, access_key, secret_ """ Returns a (message, signature) pair. """ - headers_str = "{}\n\n{}".format(method, header_string(headers_dict)) - body_str = body_string(body_dict) - message = headers_str + body_str + message = signing_format_message(method, headers_dict, body_dict) # hmac needs a byte string for it's starting key, can't be unicode. hashed = hmac.new(secret_key.encode('utf-8'), message, sha256) @@ -139,6 +137,18 @@ def generate_signed_message(method, headers_dict, body_dict, access_key, secret_ message += '\n' return message, signature, authorization_header +def signing_format_message(method, headers_dict, body_dict): + """ + Given a dictionary of headers and a dictionary of the JSON for the body, + will return a str that represents the normalized version of this messsage + that will be used to generate a signature. + """ + headers_str = "{}\n\n{}".format(method, header_string(headers_dict)) + body_str = body_string(body_dict) + message = headers_str + body_str + + return message + def header_string(headers_dict): """Given a dictionary of headers, return a canonical string representation.""" header_list = [] @@ -152,7 +162,7 @@ def header_string(headers_dict): return "".join(header_list) # Note that trailing \n's are important -def body_string(body_dict): +def body_string(body_dict, prefix=""): """ This version actually doesn't support nested lists and dicts. The code for that was a little gnarly and we don't use that functionality, so there's no @@ -160,9 +170,18 @@ def body_string(body_dict): """ body_list = [] for key, value in sorted(body_dict.items()): - if value is None: - value = "null" - body_list.append(u"{}:{}\n".format(key, value).encode('utf-8')) + if isinstance(value, (list, tuple)): + for i, arr in enumerate(value): + if isinstance(arr, dict): + body_list.append(body_string(arr, u"{}.{}.".format(key, i))) + else: + body_list.append(u"{}.{}:{}\n".format(key, i, arr).encode('utf-8')) + elif isinstance(value, dict): + body_list.append(body_string(value, key + ":")) + else: + if value is None: + value = "null" + body_list.append(u"{}{}:{}\n".format(prefix, key, value).encode('utf-8')) return "".join(body_list) # Note that trailing \n's are important From ab6018a0e58bec1125fee7ac1f319cca96e312fa Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 16 Sep 2013 02:00:30 -0400 Subject: [PATCH 04/13] Replace signature validation with access-key and add logging around Software Secure callbacks. --- lms/djangoapps/verify_student/models.py | 20 +++++++++++++++ lms/djangoapps/verify_student/views.py | 34 ++++++++++++++++++++----- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 485214d88b..5905a9288f 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -356,6 +356,26 @@ class PhotoVerification(StatusModel): self.status = "denied" self.save() + @status_before_must_be("must_retry", "submitted", "approved", "denied") + def system_error(self, + error_msg, + error_code="", + reviewing_user=None, + reviewing_service=""): + """ + Mark that this attempt could not be completed because of a system error. + Status should be moved to `must_retry`. + """ + if self.status in ["approved", "denied"]: + return # If we were already approved or denied, just leave it. + + self.error_msg = error_msg + self.error_code = error_code + self.reviewing_user = reviewing_user + self.reviewing_service = reviewing_service + self.status = "must_retry" + self.save() + class SoftwareSecurePhotoVerification(PhotoVerification): """ diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index f315c2136f..e1ec69f724 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -180,21 +180,43 @@ def results_callback(request): settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_SECRET_KEY"] ) - if not sig_valid: - return HttpResponseBadRequest(_("Signature is invalid")) + _, access_key_and_sig = headers["Authorization"].split(" ") + access_key = access_key_and_sig.split(":")[0] + + # This is what we should be doing... + #if not sig_valid: + # return HttpResponseBadRequest("Signature is invalid") + + # This is what we're doing until we can figure out why we disagree on sigs + if access_key != settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"]: + return HttpResponseBadRequest("Access key invalid") receipt_id = body_dict.get("EdX-ID") result = body_dict.get("Result") reason = body_dict.get("Reason", "") error_code = body_dict.get("MessageType", "") - attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id) - if result == "PASSED": + try: + attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id) + except SoftwareSecurePhotoVerification.DoesNotExist: + log.error("Software Secure posted back for receipt_id {}, but not found".format(receipt_id)) + return HttpResponseBadRequest("edX ID {} not found".format(receipt_id)) + + if result == "PASS": + log.debug("Approving verification for {}".format(receipt_id)) attempt.approve() - elif result == "FAILED": - attempt.deny(reason, error_code=error_code) + elif result == "FAIL": + log.debug("Denying verification for {}".format(receipt_id)) + attempt.deny(json.dumps(reason), error_code=error_code) elif result == "SYSTEM FAIL": + log.debug("System failure for {} -- resetting to must_retry".format(receipt_id)) + attempt.system_error(json.dumps(reason), error_code=error_code) log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason) + else: + log.error("Software Secure returned unknown result {}".format(result)) + return HttpResponseBadRequest( + "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result) + ) return HttpResponse("OK!") From 99841be6044a9d6bad7c7de4be6d55811df60a2e Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 16 Sep 2013 10:23:31 -0400 Subject: [PATCH 05/13] Update docstring on ssencrypt.body_string to be more useful. --- lms/djangoapps/verify_student/ssencrypt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/verify_student/ssencrypt.py b/lms/djangoapps/verify_student/ssencrypt.py index 3a110b8d04..aefb4292a0 100644 --- a/lms/djangoapps/verify_student/ssencrypt.py +++ b/lms/djangoapps/verify_student/ssencrypt.py @@ -164,9 +164,9 @@ def header_string(headers_dict): def body_string(body_dict, prefix=""): """ - This version actually doesn't support nested lists and dicts. The code for - that was a little gnarly and we don't use that functionality, so there's no - real test for correctness. + Return a canonical string representation of the body of a JSON request or + response. This canonical representation will be used as an input to the + hashing used to generate a signature. """ body_list = [] for key, value in sorted(body_dict.items()): From 0c61f194de48f82278363c025c5e8a5de2d67ab7 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 16 Sep 2013 11:00:40 -0400 Subject: [PATCH 06/13] Verification: revises fee/honor code option-based copy --- common/templates/course_modes/choose.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/templates/course_modes/choose.html b/common/templates/course_modes/choose.html index 88f45f024d..c365c2bbcb 100644 --- a/common/templates/course_modes/choose.html +++ b/common/templates/course_modes/choose.html @@ -93,7 +93,7 @@ $(document).ready(function() { % if "honor" in modes:
${_("What if I can't afford it or don't have the necessary equipment?")}
-

${_("If you can't afford the minimum fee or don't meet the requirements, you can audit the course for free. You may also elect to pursue an Honor Code certificate, but you will need to tell us why you would like the fee waived below. Then click the 'Select Certificate' button to complete your registration.")}

+

${_("If you can't afford the minimum fee or don't meet the requirements, you can audit the course or elect to pursue an honor code certificate at no cost. If you would like to pursue the honor code certificate, please check the honor code certificate box, tell us why you can't pursue the verified certificate below, and then click the 'Select Certificate' button to complete your registration.")}

  • @@ -102,7 +102,7 @@ $(document).ready(function() {
  • - +
From 882df8d31910e410a19b68e7686188bfae55307c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 16 Sep 2013 12:20:16 -0400 Subject: [PATCH 07/13] Verification: adds in browser support links, icon-based button help tips, and technical support help info --- lms/static/sass/views/_verification.scss | 18 +++++++++++++++++- .../verify_student/_verification_support.html | 11 +++++++++-- .../verify_student/photo_verification.html | 6 ++++-- .../verify_student/show_requirements.html | 14 ++++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index a78d723b01..aba4353a39 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -372,6 +372,10 @@ .copy { @extend .copy-detail; } + + strong { + color: $m-gray-d4; + } } // ==================== @@ -695,6 +699,10 @@ @extend .copy-detail; } + .example { + color: $m-gray-l2; + } + // help - general list .list-help { margin-top: ($baseline/2); @@ -1636,6 +1644,11 @@ // VIEW: review photos &.step-review { + + .help-item-technical { + display: none; + } + .modal.edit-name .submit input { color: #fff; } @@ -1655,7 +1668,6 @@ border: none; } } - } .nav-wizard { @@ -1733,6 +1745,10 @@ // VIEW: confirmation/receipt &.step-confirmation { + .help-item-technical { + display: none; + } + // progress nav .progress .progress-step { diff --git a/lms/templates/verify_student/_verification_support.html b/lms/templates/verify_student/_verification_support.html index e2f598674f..541ca791d6 100644 --- a/lms/templates/verify_student/_verification_support.html +++ b/lms/templates/verify_student/_verification_support.html @@ -3,19 +3,26 @@
diff --git a/lms/templates/verify_student/photo_verification.html b/lms/templates/verify_student/photo_verification.html index b64a3c3dd3..eb04e04507 100644 --- a/lms/templates/verify_student/photo_verification.html +++ b/lms/templates/verify_student/photo_verification.html @@ -155,7 +155,8 @@
  • ${_("Make sure your face is well-lit")}
  • ${_("Be sure your entire face is inside the frame")}
  • ${_("Can we match the photo you took with the one on your ID?")}
  • -
  • ${_("Click the checkmark once you are happy with the photo")}
  • +
  • ${_("Once in position, use the camera button")} () ${_("to capture your picture")}
  • +
  • ${_("Use the checkmark button")} () ${_("once you are happy with the photo")}
  • @@ -244,7 +245,8 @@
  • ${_("Ensure that you can see your photo and read your name")}
  • ${_("Try to keep your fingers at the edge to avoid covering important information")}
  • ${_("Acceptable IDs include drivers licenses, passports, or other goverment-issued IDs that include your name and photo")}
  • -
  • ${_("Click the checkmark once you are happy with the photo")}
  • +
  • ${_("Once in position, use the camera button")} () ${_("to capture your ID")}
  • +
  • ${_("Use the checkmark button")} () ${_("once you are happy with the photo")}
  • diff --git a/lms/templates/verify_student/show_requirements.html b/lms/templates/verify_student/show_requirements.html index a3510d7f9e..432a13fc62 100644 --- a/lms/templates/verify_student/show_requirements.html +++ b/lms/templates/verify_student/show_requirements.html @@ -104,7 +104,7 @@

    ${_("A photo identification document")} - ${_("a drivers license, passport, or other goverment-issued ID with your name and picture on it")} + ${_("a drivers license, passport, or other goverment or school-issued ID with your name and picture on it")}

    @@ -117,8 +117,18 @@

    + <% + browser_links = { + "ff_a_start": ''.format(url="https://www.mozilla.org/en-US/firefox/new/", rel="external"), + "chrome_a_start": ''.format(url="https://www.google.com/intl/en/chrome/browser/", rel="external"), + "safari_a_start": ''.format(url="http://www.apple.com/safari/", rel="external"), + "ie_a_start": ''.format(url="http://windows.microsoft.com/en-us/internet-explorer/download-ie", rel="external"), + "a_end": '' + } + %> ${_("A webcam and a modern browser")} - ${_("Firefox, Chrome, Safari, IE9+")} + ${_("{ff_a_start}Firefox{a_end}, {chrome_a_start}Chrome{a_end}, {safari_a_start}Safari{a_end}, {ie_a_start}IE9+{a_end}").format(**browser_links)} - ${_("Please make sure your browser is updated to the most recent version possible")} +

    From 9e88be9af161874359aa1d705e5f4641b3b161cd Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 16 Sep 2013 12:42:40 -0400 Subject: [PATCH 08/13] Verification: removes duplicate course start on receipt view --- lms/templates/shoppingcart/verified_cert_receipt.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lms/templates/shoppingcart/verified_cert_receipt.html b/lms/templates/shoppingcart/verified_cert_receipt.html index b341ad3edd..3d999570fc 100644 --- a/lms/templates/shoppingcart/verified_cert_receipt.html +++ b/lms/templates/shoppingcart/verified_cert_receipt.html @@ -100,7 +100,7 @@ ${_("Course")} ${_("Status")} - ${_("Options")} + ${_("Options")} @@ -113,9 +113,8 @@ %if course_has_started: - ${_("Starts: {start_date}").format(start_date=course_start_date_text)} + ${_("Go to Course")} %else: - ${_("Go to Course")} %endif From 2680f8dfcc7b83b9e5ada61a4d5270d5a7d332a5 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 16 Sep 2013 12:52:09 -0400 Subject: [PATCH 09/13] Verification: further revises webcam support w/in browser settings copy --- lms/static/sass/views/_verification.scss | 2 +- lms/templates/verify_student/_verification_support.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index aba4353a39..8bdcdb3e71 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -374,7 +374,7 @@ } strong { - color: $m-gray-d4; + color: $m-gray-d2; } } diff --git a/lms/templates/verify_student/_verification_support.html b/lms/templates/verify_student/_verification_support.html index 541ca791d6..63c05b3280 100644 --- a/lms/templates/verify_student/_verification_support.html +++ b/lms/templates/verify_student/_verification_support.html @@ -20,7 +20,7 @@
  • ${_("Having Technical Trouble?")}

    -

    ${_("Please make sure your browser is updated to the {strong_start}{a_start}most recent version possible{a_end}{strong_end}. Also, please make sure your {strong_start}web cam is plugged in, turned on, and functional{strong_end}").format(a_start='', a_end="", strong_start="", strong_end="")}.

    +

    ${_("Please make sure your browser is updated to the {strong_start}{a_start}most recent version possible{a_end}{strong_end}. Also, please make sure your {strong_start}web cam is plugged in, turned on, and allowed to function in your web browser (commonly adjustable in your browser settings).{strong_end}").format(a_start='', a_end="", strong_start="", strong_end="")}.

  • From c1134acf05cf224de6a9316585b8b0aa5309f7cb Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 16 Sep 2013 13:17:58 -0400 Subject: [PATCH 10/13] Verification: revises copy and layout of the review step's confirm/next actions UI --- lms/static/sass/views/_verification.scss | 36 ++++++++++++------- .../verify_student/photo_verification.html | 17 ++++++--- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index 8bdcdb3e71..a1b44ce37c 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -1672,17 +1672,34 @@ .nav-wizard { - .help-inline { - width: flex-grid(4,12); - margin-top: 0 + .prompt-verify { + float: left; + width: flex-grid(6,12); + margin: 0 flex-gutter() 0 0; + + .title { + @extend .hd-lv4; + margin-bottom: ($baseline/4); + } + + .copy { + @extend .t-copy-sub1; + @extend .t-weight3; + } + + .list-actions { + margin-top: ($baseline/2); + } + + .action-verify label { + @extend .t-copy-sub1; + } } .wizard-steps { - float: right; - width: flex-grid(8,12); + margin-top: ($baseline/2); .wizard-step { - width: flex-grid(4,8); margin-right: flex-gutter(); display: inline-block; vertical-align: middle; @@ -1693,13 +1710,6 @@ } } - .step-match { - - label { - @extend .t-copy-sub1; - } - } - .step-proceed { } diff --git a/lms/templates/verify_student/photo_verification.html b/lms/templates/verify_student/photo_verification.html index eb04e04507..537c26ff84 100644 --- a/lms/templates/verify_student/photo_verification.html +++ b/lms/templates/verify_student/photo_verification.html @@ -369,13 +369,20 @@