Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
8278357c
Commit
8278357c
authored
Sep 03, 2013
by
David Ormsbee
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Hook up interface to Software Secure for identity validation.
parent
af9193af
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
297 additions
and
43 deletions
+297
-43
lms/djangoapps/shoppingcart/models.py
+9
-0
lms/djangoapps/verify_student/admin.py
+4
-0
lms/djangoapps/verify_student/models.py
+141
-29
lms/djangoapps/verify_student/ssencrypt.py
+78
-1
lms/djangoapps/verify_student/tests/test_models.py
+6
-9
lms/djangoapps/verify_student/urls.py
+7
-0
lms/djangoapps/verify_student/views.py
+49
-3
lms/static/js/verify_student/photocapture.js
+3
-1
No files found.
lms/djangoapps/shoppingcart/models.py
View file @
8278357c
...
...
@@ -19,6 +19,7 @@ from mitxmako.shortcuts import render_to_string
from
student.views
import
course_from_id
from
student.models
import
CourseEnrollment
from
statsd
import
statsd
from
verify_student.models
import
SoftwareSecurePhotoVerification
from
xmodule.modulestore.django
import
modulestore
from
xmodule.course_module
import
CourseDescriptor
...
...
@@ -369,6 +370,14 @@ class CertificateItem(OrderItem):
"""
When purchase goes through, activate and update the course enrollment for the correct mode
"""
try
:
verification_attempt
=
SoftwareSecurePhotoVerification
.
active_for_user
(
self
.
course_enrollment
.
user
)
verification_attempt
.
submit
()
except
Exception
as
e
:
log
.
exception
(
"Could not submit verification attempt for enrollment {}"
.
format
(
self
.
course_enrollment
)
)
self
.
course_enrollment
.
mode
=
self
.
mode
self
.
course_enrollment
.
save
()
self
.
course_enrollment
.
activate
()
...
...
lms/djangoapps/verify_student/admin.py
0 → 100644
View file @
8278357c
from
ratelimitbackend
import
admin
from
verify_student.models
import
SoftwareSecurePhotoVerification
admin
.
site
.
register
(
SoftwareSecurePhotoVerification
)
lms/djangoapps/verify_student/models.py
View file @
8278357c
This diff is collapsed.
Click to expand it.
lms/djangoapps/verify_student/ssencrypt.py
View file @
8278357c
...
...
@@ -22,13 +22,22 @@ In case of PEM encoding, the private key can be encrypted with DES or 3TDES
according to a certain pass phrase. Only OpenSSL-compatible pass phrases are
supported.
"""
from
hashlib
import
md5
from
collections
import
OrderedDict
from
email.utils
import
formatdate
from
hashlib
import
md5
,
sha256
from
uuid
import
uuid4
import
base64
import
binascii
import
json
import
hmac
import
logging
import
sys
from
Crypto
import
Random
from
Crypto.Cipher
import
AES
,
PKCS1_OAEP
from
Crypto.PublicKey
import
RSA
log
=
logging
.
getLogger
(
__name__
)
def
encrypt_and_encode
(
data
,
key
):
return
base64
.
urlsafe_b64encode
(
aes_encrypt
(
data
,
key
))
...
...
@@ -88,3 +97,71 @@ def rsa_decrypt(data, rsa_priv_key_str):
key
=
RSA
.
importKey
(
rsa_priv_key_str
)
cipher
=
PKCS1_OAEP
.
new
(
key
)
return
cipher
.
decrypt
(
data
)
def
has_valid_signature
(
method
,
headers_dict
,
body_dict
,
access_key
,
secret_key
):
"""
Given a message (either request or response), say whether it has a valid
signature or not.
"""
_
,
expected_signature
,
_
=
generate_signed_message
(
method
,
headers_dict
,
body_dict
,
access_key
,
secret_key
)
authorization
=
headers_dict
[
"Authorization"
]
auth_token
,
post_signature
=
authorization
.
split
(
":"
)
_
,
post_access_key
=
auth_token
.
split
()
if
post_access_key
!=
access_key
:
log
.
error
(
"Posted access key does not match ours"
)
log
.
debug
(
"Their access:
%
s; Our access:
%
s"
,
post_access_key
,
access_key
)
return
False
if
post_signature
!=
expected_signature
:
log
.
error
(
"Posted signature does not match expected"
)
log
.
debug
(
"Their sig:
%
s; Expected:
%
s"
,
post_signature
,
expected_signature
)
return
False
return
True
def
generate_signed_message
(
method
,
headers_dict
,
body_dict
,
access_key
,
secret_key
):
"""
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
hashed
=
hmac
.
new
(
secret_key
,
message
,
sha256
)
signature
=
binascii
.
b2a_base64
(
hashed
.
digest
())
.
rstrip
(
'
\n
'
)
authorization_header
=
"SSI {}:{}"
.
format
(
access_key
,
signature
)
message
+=
'
\n
'
return
message
,
signature
,
authorization_header
def
header_string
(
headers_dict
):
"""Given a dictionary of headers, return a canonical string representation."""
header_list
=
[]
if
'Content-Type'
in
headers_dict
:
header_list
.
append
(
headers_dict
[
'Content-Type'
]
+
"
\n
"
)
if
'Date'
in
headers_dict
:
header_list
.
append
(
headers_dict
[
'Date'
]
+
"
\n
"
)
if
'Content-MD5'
in
headers_dict
:
header_list
.
append
(
headers_dict
[
'Content-MD5'
]
+
"
\n
"
)
return
""
.
join
(
header_list
)
# Note that trailing \n's are important
def
body_string
(
body_dict
):
"""
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.
"""
body_list
=
[]
for
key
,
value
in
sorted
(
body_dict
.
items
()):
if
value
is
None
:
value
=
"null"
body_list
.
append
(
u"{}:{}
\n
"
.
format
(
key
,
value
))
return
""
.
join
(
body_list
)
# Note that trailing \n's are important
lms/djangoapps/verify_student/tests/test_models.py
View file @
8278357c
...
...
@@ -23,9 +23,6 @@ class TestPhotoVerification(TestCase):
assert_equals
(
attempt
.
status
,
SoftwareSecurePhotoVerification
.
STATUS
.
created
)
assert_equals
(
attempt
.
status
,
"created"
)
# This should fail because we don't have the necessary fields filled out
assert_raises
(
VerificationException
,
attempt
.
mark_ready
)
# These should all fail because we're in the wrong starting state.
assert_raises
(
VerificationException
,
attempt
.
submit
)
assert_raises
(
VerificationException
,
attempt
.
approve
)
...
...
@@ -47,14 +44,14 @@ class TestPhotoVerification(TestCase):
assert_raises
(
VerificationException
,
attempt
.
deny
)
# Now we submit
attempt
.
submit
()
assert_equals
(
attempt
.
status
,
"submitted"
)
#
attempt.submit()
#
assert_equals(attempt.status, "submitted")
# So we should be able to both approve and deny
attempt
.
approve
()
assert_equals
(
attempt
.
status
,
"approved"
)
#
attempt.approve()
#
assert_equals(attempt.status, "approved")
attempt
.
deny
(
"Could not read name on Photo ID"
)
assert_equals
(
attempt
.
status
,
"denied"
)
#
attempt.deny("Could not read name on Photo ID")
#
assert_equals(attempt.status, "denied")
lms/djangoapps/verify_student/urls.py
View file @
8278357c
...
...
@@ -30,9 +30,16 @@ urlpatterns = patterns(
),
url
(
r'^results_callback$'
,
views
.
results_callback
,
name
=
"verify_student_results_callback"
,
),
url
(
r'^show_verification_page/(?P<course_id>[^/]+/[^/]+/[^/]+)$'
,
views
.
show_verification_page
,
name
=
"verify_student/show_verification_page"
),
)
lms/djangoapps/verify_student/views.py
View file @
8278357c
...
...
@@ -12,6 +12,8 @@ from django.conf import settings
from
django.core.urlresolvers
import
reverse
from
django.http
import
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseRedirect
from
django.shortcuts
import
redirect
from
django.views.decorators.csrf
import
csrf_exempt
from
django.views.decorators.http
import
require_POST
from
django.views.generic.base
import
View
from
django.utils.decorators
import
method_decorator
from
django.utils.translation
import
ugettext
as
_
...
...
@@ -26,6 +28,7 @@ from shoppingcart.processors.CyberSource import (
get_signed_purchase_params
,
get_purchase_endpoint
)
from
verify_student.models
import
SoftwareSecurePhotoVerification
import
ssencrypt
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -115,7 +118,13 @@ def create_order(request):
"""
if
not
SoftwareSecurePhotoVerification
.
user_has_valid_or_pending
(
request
.
user
):
attempt
=
SoftwareSecurePhotoVerification
(
user
=
request
.
user
)
attempt
.
status
=
"ready"
b64_face_image
=
request
.
POST
[
'face_image'
]
.
split
(
","
)[
1
]
b64_photo_id_image
=
request
.
POST
[
'photo_id_image'
]
.
split
(
","
)[
1
]
attempt
.
upload_face_image
(
b64_face_image
.
decode
(
'base64'
))
attempt
.
upload_photo_id_image
(
b64_photo_id_image
.
decode
(
'base64'
))
attempt
.
mark_ready
()
attempt
.
save
()
course_id
=
request
.
POST
[
'course_id'
]
...
...
@@ -149,6 +158,45 @@ def create_order(request):
return
HttpResponse
(
json
.
dumps
(
params
),
content_type
=
"text/json"
)
@require_POST
@csrf_exempt
# SS does its own message signing, and their API won't have a cookie value
def
results_callback
(
request
):
"""
Software Secure will call this callback to tell us whether a user is
verified to be who they said they are.
"""
body
=
request
.
body
body_dict
=
json
.
loads
(
body
)
headers
=
{
"Authorization"
:
request
.
META
.
get
(
"HTTP_AUTHORIZATION"
,
""
),
"Date"
:
request
.
META
.
get
(
"HTTP_DATE"
,
""
)
}
sig_valid
=
ssencrypt
.
has_valid_signature
(
"POST"
,
headers
,
body_dict
,
settings
.
VERIFY_STUDENT
[
"SOFTWARE_SECURE"
][
"API_ACCESS_KEY"
],
settings
.
VERIFY_STUDENT
[
"SOFTWARE_SECURE"
][
"API_SECRET_KEY"
]
)
# if not sig_valid:
# return HttpResponseBadRequest(_("Signature is 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"
:
attempt
.
approve
()
elif
result
==
"FAILED"
:
attempt
.
deny
(
reason
,
error_code
=
error_code
)
elif
result
==
"SYSTEM FAIL"
:
log
.
error
(
"Software Secure callback attempt for
%
s failed:
%
s"
,
receipt_id
,
reason
)
return
HttpResponse
(
"OK!"
)
@login_required
def
show_requirements
(
request
,
course_id
):
...
...
@@ -172,7 +220,6 @@ def show_requirements(request, course_id):
def
show_verification_page
(
request
):
pass
def
enroll
(
user
,
course_id
,
mode_slug
):
"""
Enroll the user in a course for a certain mode.
...
...
@@ -214,7 +261,6 @@ def enroll(user, course_id, mode_slug):
# Create a VerifiedCertificate order item
return
HttpResponse
.
Redirect
(
reverse
(
'verified'
))
# There's always at least one mode available (default is "honor"). If they
# haven't specified a mode, we just assume it's
if
not
mode
:
...
...
lms/static/js/verify_student/photocapture.js
View file @
8278357c
...
...
@@ -32,7 +32,9 @@ var submitToPaymentProcessing = function() {
"/verify_student/create_order"
,
{
"course_id"
:
course_id
,
"contribution"
:
contribution
"contribution"
:
contribution
,
"face_image"
:
$
(
"#face_image"
)[
0
].
src
,
"photo_id_image"
:
$
(
"#photo_id_image"
)[
0
].
src
},
function
(
data
)
{
for
(
prop
in
data
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment