Skip to content
Snippets Groups Projects
Commit cf8b7c6e authored by Diana Huang's avatar Diana Huang
Browse files

Merge pull request #996 from edx/hotfix-2013-09-16

Hotfix 2013-09-16
parents 6e8c8a64 b7836c94
No related merge requests found
......@@ -82,7 +82,7 @@ $(document).ready(function() {
<dl class="list-faq">
<dt class="faq-question">${_("Why do I have to pay?")}</dt>
<dd class="faq-answer">
<p>${_("As a not-for-profit, edX uses your contribution to support our mission to provide quality education to everyone around the world. While we have established a minimum fee, we ask that you contribute as much as you can.")}</p>
<p>${_("As a not-for-profit, edX uses your contribution to support our mission to provide quality education to everyone around the world, and to improve learning through research. While we have established a minimum fee, we ask that you contribute as much as you can.")}</p>
</dd>
<dt class="faq-question">${_("I'd like to pay more than the minimum. Is my contribution tax deductible?")}</dt>
......@@ -93,7 +93,7 @@ $(document).ready(function() {
% if "honor" in modes:
<dt class="faq-question">${_("What if I can't afford it or don't have the necessary equipment?")}</dt>
<dd class="faq-answer">
<p>${_("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.")}</p>
<p>${_("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.")}</p>
<ul class="list-fields">
<li class="field field-honor-code checkbox">
......@@ -102,7 +102,7 @@ $(document).ready(function() {
</li>
<li class="field field-explain">
<label for="explain"><span class="sr">${_("Explain your situation: ")}</span>${_("Please write a few sentences about why you would like the fee waived for this course")}</label>
<label for="explain"><span class="sr">${_("Explain your situation: ")}</span>${_("Please write a few sentences about why you'd like to opt out of the paid verified certificate to pursue the honor code certificate:")}</label>
<textarea name="explain"></textarea>
</li>
</ul>
......
......@@ -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):
"""
......@@ -500,7 +520,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 +529,8 @@ 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'),
verify=False
)
log.debug("Sent request to Software Secure for {}".format(self.receipt_id))
log.debug("Headers:\n{}\n\n".format(headers))
......
......@@ -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,17 +162,26 @@ 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
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()):
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
......@@ -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!")
......
......@@ -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)
......@@ -372,6 +372,10 @@
.copy {
@extend .copy-detail;
}
strong {
color: $m-gray-d2;
}
}
// ====================
......@@ -695,6 +699,10 @@
@extend .copy-detail;
}
.example {
color: $m-gray-l2;
}
// help - general list
.list-help {
margin-top: ($baseline/2);
......@@ -1406,6 +1414,10 @@
// VIEW: requirements
&.step-requirements {
.help-item-technical {
display: none;
}
// progress nav
.progress .progress-step {
......@@ -1636,6 +1648,11 @@
// VIEW: review photos
&.step-review {
.help-item-technical {
display: none;
}
.modal.edit-name .submit input {
color: #fff;
}
......@@ -1655,22 +1672,38 @@
border: none;
}
}
}
.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;
......@@ -1681,13 +1714,6 @@
}
}
.step-match {
label {
@extend .t-copy-sub1;
}
}
.step-proceed {
}
......@@ -1733,6 +1759,10 @@
// VIEW: confirmation/receipt
&.step-confirmation {
.help-item-technical {
display: none;
}
// progress nav
.progress .progress-step {
......
......@@ -100,7 +100,7 @@
<tr>
<th scope="col" >${_("Course")}</th>
<th scope="col" >${_("Status")}</th>
<th scope="col" >${_("Options")}</th>
<th scope="col" ><span class="sr">${_("Options")}</span></th>
</tr>
</thead>
......@@ -113,9 +113,8 @@
</td>
<td class="options">
%if course_has_started:
${_("Starts: {start_date}").format(start_date=course_start_date_text)}
<a class="action action-course" href="${reverse('course_root', kwargs={'course_id': item.course_id})}">${_("Go to Course")}</a>
%else:
<a class="action action-course" href="${reverse('course_root', kwargs={'course_id': item.course_id})}">${_("Go to Course")}</a>
%endif
</td>
</tr>
......
......@@ -3,19 +3,26 @@
<div class="wrapper-content-supplementary">
<aside class="content-supplementary">
<ul class="list-help">
<li class="help-item">
<li class="help-item help-item-questions">
<h3 class="title">${_("Have questions?")}</h3>
<div class="copy">
<p>${_("Please read {a_start}our FAQs to view common questions about our certificates{a_end}.").format(a_start='<a rel="external" href="'+ marketing_link('WHAT_IS_VERIFIED_CERT') + '">', a_end="</a>")}</p>
</div>
</li>
<li class="help-item">
<li class="help-item help-item-coldfeet">
<h3 class="title">${_("Change your mind?")}</h3>
<div class="copy">
<p>${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</p>
</div>
</li>
<li class="help-item help-item-technical">
<h3 class="title">${_("Having Technical Trouble?")}</h3>
<div class="copy">
<p>${_("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 rel="external" href="http://browsehappy.com/">', a_end="</a>", strong_start="<strong>", strong_end="</strong>")}</p>
</div>
</li>
</ul>
</aside>
</div> <!-- /wrapper-content-supplementary -->
......@@ -155,7 +155,8 @@
<li class="help-item">${_("Make sure your face is well-lit")}</li>
<li class="help-item">${_("Be sure your entire face is inside the frame")}</li>
<li class="help-item">${_("Can we match the photo you took with the one on your ID?")}</li>
<li class="help-item">${_("Click the checkmark once you are happy with the photo")}</li>
<li class="help-item">${_("Once in position, use the camera button")} <span class="example">(<i class="icon-camera"></i>)</span> ${_("to capture your picture")}</li>
<li class="help-item">${_("Use the checkmark button")} <span class="example">(<i class="icon-ok"></i>)</span> ${_("once you are happy with the photo")}</li>
</ul>
</div>
</div>
......@@ -244,7 +245,8 @@
<li class="help-item">${_("Ensure that you can see your photo and read your name")}</li>
<li class="help-item">${_("Try to keep your fingers at the edge to avoid covering important information")}</li>
<li class="help-item">${_("Acceptable IDs include drivers licenses, passports, or other goverment-issued IDs that include your name and photo")}</li>
<li class="help-item">${_("Click the checkmark once you are happy with the photo")}</li>
<li class="help-item">${_("Once in position, use the camera button")} <span class="example">(<i class="icon-camera"></i>)</span> ${_("to capture your ID")}</li>
<li class="help-item">${_("Use the checkmark button")} <span class="example">(<i class="icon-ok"></i>)</span> ${_("once you are happy with the photo")}</li>
</ul>
</div>
</div>
......@@ -367,13 +369,20 @@
</div>
<nav class="nav-wizard">
<span class="help help-inline">${_("Once you verify your details match the requirements, you can move on to step 4, payment on our secure server.")}</span>
<div class="prompt-verify">
<h3 class="title">Before proceeding, please confirm that your details match</h3>
<p class="copy"> ${_("Once you verify your details match the requirements, you can move on to step 4, payment on our secure server.")}</p>
<ul class="list-actions">
<li class="action action-verify">
<input type="checkbox" name="match" id="confirm_pics_good" />
<label for="confirm_pics_good">${_("Yes! My details all match.")}</label>
</li>
</ul>
</div>
<ol class="wizard-steps">
<li class="wizard-step step-match">
<input type="checkbox" name="match" id="confirm_pics_good" />
<label for="confirm_pics_good">${_("Yes! My details all match.")}</label>
</li>
<li class="wizard-step step-proceed">
<form id="pay_form" method="post" action="${purchase_endpoint}">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
......
......@@ -104,7 +104,7 @@
<div class="copy">
<p>
<span class="copy-super">${_("A photo identification document")}</span>
<span class="copy-sub">${_("a drivers license, passport, or other goverment-issued ID with your name and picture on it")}</span>
<span class="copy-sub">${_("a drivers license, passport, or other goverment or school-issued ID with your name and picture on it")}</span>
</p>
</div>
</li>
......@@ -117,8 +117,18 @@
<div class="copy">
<p>
<%
browser_links = {
"ff_a_start": '<a rel="{rel}" href="{url}">'.format(url="https://www.mozilla.org/en-US/firefox/new/", rel="external"),
"chrome_a_start": '<a rel="{rel}" href="{url}">'.format(url="https://www.google.com/intl/en/chrome/browser/", rel="external"),
"safari_a_start": '<a rel="{rel}" href="{url}">'.format(url="http://www.apple.com/safari/", rel="external"),
"ie_a_start": '<a rel="{rel}" href="{url}">'.format(url="http://windows.microsoft.com/en-us/internet-explorer/download-ie", rel="external"),
"a_end": '</a>'
}
%>
<span class="copy-super">${_("A webcam and a modern browser")}</span>
<span class="copy-sub">${_("Firefox, Chrome, Safari, IE9+")}</span>
<span class="copy-sub"><strong>${_("{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)}</strong> - ${_("Please make sure your browser is updated to the most recent version possible")}
</span>
</p>
</div>
</li>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment