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
4ee9ef61
Commit
4ee9ef61
authored
Sep 24, 2013
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Clean up some old pep8/pylint violations
Also, deletes some unused code.
parent
3ca74c01
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
86 additions
and
105 deletions
+86
-105
common/djangoapps/course_modes/views.py
+22
-5
lms/djangoapps/verify_student/models.py
+18
-15
lms/djangoapps/verify_student/ssencrypt.py
+28
-7
lms/djangoapps/verify_student/urls.py
+0
-7
lms/djangoapps/verify_student/views.py
+18
-71
No files found.
common/djangoapps/course_modes/views.py
View file @
4ee9ef61
"""
Views for the course_mode module
"""
import
decimal
from
django.core.urlresolvers
import
reverse
from
django.http
import
(
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseForbidden
,
Http404
HttpResponse
BadRequest
,
Http404
)
from
django.shortcuts
import
redirect
from
django.views.generic.base
import
View
from
django.utils.translation
import
ugettext
as
_
from
django.utils.http
import
urlencode
from
django.contrib.auth.decorators
import
login_required
from
django.utils.decorators
import
method_decorator
...
...
@@ -18,10 +21,19 @@ from student.models import CourseEnrollment
from
student.views
import
course_from_id
from
verify_student.models
import
SoftwareSecurePhotoVerification
class
ChooseModeView
(
View
):
class
ChooseModeView
(
View
):
"""
View used when the user is asked to pick a mode
When a get request is used, shows the selection page.
When a post request is used, assumes that it is a form submission
from the selection page, parses the response, and then sends user
to the next step in the flow
"""
@method_decorator
(
login_required
)
def
get
(
self
,
request
,
course_id
,
error
=
None
):
""" Displays the course mode choice page """
if
CourseEnrollment
.
enrollment_mode_for_user
(
request
.
user
,
course_id
)
==
'verified'
:
return
redirect
(
reverse
(
'dashboard'
))
modes
=
CourseMode
.
modes_for_course_dict
(
course_id
)
...
...
@@ -34,8 +46,8 @@ class ChooseModeView(View):
"course_id"
:
course_id
,
"modes"
:
modes
,
"course_name"
:
course
.
display_name_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"chosen_price"
:
chosen_price
,
"error"
:
error
,
}
...
...
@@ -48,6 +60,7 @@ class ChooseModeView(View):
@method_decorator
(
login_required
)
def
post
(
self
,
request
,
course_id
):
""" Takes the form submission from the page and parses it """
user
=
request
.
user
# This is a bit redundant with logic in student.views.change_enrollement,
...
...
@@ -102,6 +115,10 @@ class ChooseModeView(View):
)
def
get_requested_mode
(
self
,
user_choice
):
"""
Given the text of `user_choice`, return the
corresponding course mode slug
"""
choices
=
{
"Select Audit"
:
"audit"
,
"Select Certificate"
:
"verified"
...
...
lms/djangoapps/verify_student/models.py
View file @
4ee9ef61
...
...
@@ -26,12 +26,11 @@ from django.conf import settings
from
django.core.urlresolvers
import
reverse
from
django.db
import
models
from
django.contrib.auth.models
import
User
from
django.core.urlresolvers
import
reverse
from
model_utils.models
import
StatusModel
from
model_utils
import
Choices
from
verify_student.ssencrypt
import
(
random_aes_key
,
decode_and_decrypt
,
encrypt_and_encode
,
random_aes_key
,
encrypt_and_encode
,
generate_signed_message
,
rsa_encrypt
)
...
...
@@ -57,15 +56,18 @@ def status_before_must_be(*valid_start_statuses):
distracting boilerplate when looking at a Model that needs to go through a
workflow process.
"""
def
decorator_func
(
fn
):
@functools.wraps
(
fn
)
def
decorator_func
(
func
):
"""
Decorator function that gets returned
"""
@functools.wraps
(
func
)
def
with_status_check
(
obj
,
*
args
,
**
kwargs
):
if
obj
.
status
not
in
valid_start_statuses
:
exception_msg
=
(
u"Error calling {} {}: status is '{}', must be one of: {}"
)
.
format
(
f
n
,
obj
,
obj
.
status
,
valid_start_statuses
)
)
.
format
(
f
unc
,
obj
,
obj
.
status
,
valid_start_statuses
)
raise
VerificationException
(
exception_msg
)
return
f
n
(
obj
,
*
args
,
**
kwargs
)
return
f
unc
(
obj
,
*
args
,
**
kwargs
)
return
with_status_check
...
...
@@ -367,7 +369,7 @@ class PhotoVerification(StatusModel):
Status should be moved to `must_retry`.
"""
if
self
.
status
in
[
"approved"
,
"denied"
]:
return
# If we were already approved or denied, just leave it.
return
# If we were already approved or denied, just leave it.
self
.
error_msg
=
error_msg
self
.
error_code
=
error_code
...
...
@@ -408,7 +410,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
# encode that. The result is saved here. Actual expected length is 344.
photo_id_key
=
models
.
TextField
(
max_length
=
1024
)
IMAGE_LINK_DURATION
=
5
*
60
*
60
*
24
# 5 days in seconds
IMAGE_LINK_DURATION
=
5
*
60
*
60
*
24
# 5 days in seconds
@status_before_must_be
(
"created"
)
def
upload_face_image
(
self
,
img_data
):
...
...
@@ -444,8 +446,8 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
self
.
status
=
"must_retry"
self
.
error_msg
=
response
.
text
self
.
save
()
except
Exception
as
e
:
log
.
exception
(
e
)
except
Exception
as
e
rror
:
log
.
exception
(
e
rror
)
def
image_url
(
self
,
name
):
"""
...
...
@@ -466,7 +468,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
bucket
=
conn
.
get_bucket
(
settings
.
VERIFY_STUDENT
[
"SOFTWARE_SECURE"
][
"S3_BUCKET"
])
key
=
Key
(
bucket
)
key
.
key
=
"{}/{}"
.
format
(
prefix
,
self
.
receipt_id
)
;
key
.
key
=
"{}/{}"
.
format
(
prefix
,
self
.
receipt_id
)
return
key
...
...
@@ -507,7 +509,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
"Content-Type"
:
"application/json"
,
"Date"
:
formatdate
(
timeval
=
None
,
localtime
=
False
,
usegmt
=
True
)
}
message
,
_
,
authorization
=
generate_signed_message
(
_message
,
_sig
,
authorization
=
generate_signed_message
(
"POST"
,
headers
,
body
,
access_key
,
secret_key
)
headers
[
'Authorization'
]
=
authorization
...
...
@@ -515,16 +517,18 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
return
headers
,
body
def
request_message_txt
(
self
):
""" This is the body of the request we send across """
headers
,
body
=
self
.
create_request
()
header_txt
=
"
\n
"
.
join
(
"{}: {}"
.
format
(
h
,
v
)
for
h
,
v
in
sorted
(
headers
.
items
())
"{}: {}"
.
format
(
h
,
v
)
for
h
,
v
in
sorted
(
headers
.
items
())
)
body_txt
=
json
.
dumps
(
body
,
indent
=
2
,
sort_keys
=
True
,
ensure_ascii
=
False
)
.
encode
(
'utf-8'
)
return
header_txt
+
"
\n\n
"
+
body_txt
def
send_request
(
self
):
""" sends the request across to the endpoint """
headers
,
body
=
self
.
create_request
()
response
=
requests
.
post
(
settings
.
VERIFY_STUDENT
[
"SOFTWARE_SECURE"
][
"API_URL"
],
...
...
@@ -538,4 +542,4 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
log
.
debug
(
"Return code: {}"
.
format
(
response
.
status_code
))
log
.
debug
(
"Return message:
\n\n
{}
\n\n
"
.
format
(
response
.
text
))
return
response
\ No newline at end of file
return
response
lms/djangoapps/verify_student/ssencrypt.py
View file @
4ee9ef61
...
...
@@ -22,16 +22,11 @@ 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
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
...
...
@@ -39,12 +34,17 @@ from Crypto.PublicKey import RSA
log
=
logging
.
getLogger
(
__name__
)
def
encrypt_and_encode
(
data
,
key
):
""" Encrypts and endcodes `data` using `key' """
return
base64
.
urlsafe_b64encode
(
aes_encrypt
(
data
,
key
))
def
decode_and_decrypt
(
encoded_data
,
key
):
""" Decrypts and decodes `data` using `key' """
return
aes_decrypt
(
base64
.
urlsafe_b64decode
(
encoded_data
),
key
)
def
aes_encrypt
(
data
,
key
):
"""
Return a version of the `data` that has been encrypted to
...
...
@@ -53,11 +53,16 @@ def aes_encrypt(data, key):
padded_data
=
pad
(
data
)
return
cipher
.
encrypt
(
padded_data
)
def
aes_decrypt
(
encrypted_data
,
key
):
"""
Decrypt `encrypted_data` using `key`
"""
cipher
=
aes_cipher_from_key
(
key
)
padded_data
=
cipher
.
decrypt
(
encrypted_data
)
return
unpad
(
padded_data
)
def
aes_cipher_from_key
(
key
):
"""
Given an AES key, return a Cipher object that has `encrypt()` and
...
...
@@ -66,6 +71,7 @@ def aes_cipher_from_key(key):
"""
return
AES
.
new
(
key
,
AES
.
MODE_CBC
,
generate_aes_iv
(
key
))
def
generate_aes_iv
(
key
):
"""
Return the initialization vector Software Secure expects for a given AES
...
...
@@ -73,17 +79,23 @@ def generate_aes_iv(key):
"""
return
md5
(
key
+
md5
(
key
)
.
hexdigest
())
.
hexdigest
()[:
AES
.
block_size
]
def
random_aes_key
():
return
Random
.
new
()
.
read
(
32
)
def
pad
(
data
):
""" Pad the given `data` such that it fits into the proper AES block size """
bytes_to_pad
=
AES
.
block_size
-
len
(
data
)
%
AES
.
block_size
return
data
+
(
bytes_to_pad
*
chr
(
bytes_to_pad
))
def
unpad
(
padded_data
):
""" remove all padding from `padded_data` """
num_padded_bytes
=
ord
(
padded_data
[
-
1
])
return
padded_data
[:
-
num_padded_bytes
]
def
rsa_encrypt
(
data
,
rsa_pub_key_str
):
"""
`rsa_pub_key` is a string with the public key
...
...
@@ -93,11 +105,16 @@ def rsa_encrypt(data, rsa_pub_key_str):
encrypted_data
=
cipher
.
encrypt
(
data
)
return
encrypted_data
def
rsa_decrypt
(
data
,
rsa_priv_key_str
):
"""
When given some `data` and an RSA private key, decrypt the data
"""
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
...
...
@@ -123,6 +140,7 @@ def has_valid_signature(method, headers_dict, body_dict, access_key, secret_key)
return
True
def
generate_signed_message
(
method
,
headers_dict
,
body_dict
,
access_key
,
secret_key
):
"""
Returns a (message, signature) pair.
...
...
@@ -137,6 +155,7 @@ 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,
...
...
@@ -149,6 +168,7 @@ def signing_format_message(method, headers_dict, body_dict):
return
message
def
header_string
(
headers_dict
):
"""Given a dictionary of headers, return a canonical string representation."""
header_list
=
[]
...
...
@@ -160,7 +180,8 @@ def header_string(headers_dict):
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
return
""
.
join
(
header_list
)
# Note that trailing \n's are important
def
body_string
(
body_dict
,
prefix
=
""
):
"""
...
...
@@ -183,5 +204,5 @@ def body_string(body_dict, prefix=""):
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
return
""
.
join
(
body_list
)
# Note that trailing \n's are important
lms/djangoapps/verify_student/urls.py
View file @
4ee9ef61
...
...
@@ -35,11 +35,4 @@ urlpatterns = patterns(
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 @
4ee9ef61
"""
Views for the verification flow
"""
import
json
...
...
@@ -37,6 +37,12 @@ class VerifyView(View):
@method_decorator
(
login_required
)
def
get
(
self
,
request
,
course_id
):
"""
Displays the main verification view, which contains three separate steps:
- Taking the standard face photo
- Taking the id photo
- Confirming that the photos and payment price are correct
before proceeding to payment
"""
# If the user has already been verified within the given time period,
# redirect straight to the payment -- no need to verify again.
...
...
@@ -69,8 +75,8 @@ class VerifyView(View):
"user_full_name"
:
request
.
user
.
profile
.
name
,
"course_id"
:
course_id
,
"course_name"
:
course
.
display_name_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"purchase_endpoint"
:
get_purchase_endpoint
(),
"suggested_prices"
:
[
decimal
.
Decimal
(
price
)
...
...
@@ -106,8 +112,8 @@ class VerifiedView(View):
context
=
{
"course_id"
:
course_id
,
"course_name"
:
course
.
display_name_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"purchase_endpoint"
:
get_purchase_endpoint
(),
"currency"
:
verify_mode
.
currency
.
upper
(),
"chosen_price"
:
chosen_price
,
...
...
@@ -162,8 +168,9 @@ 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
@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
...
...
@@ -194,7 +201,7 @@ def results_callback(request):
settings
.
VERIFY_STUDENT
[
"SOFTWARE_SECURE"
][
"API_SECRET_KEY"
]
)
_
,
access_key_and_sig
=
headers
[
"Authorization"
]
.
split
(
" "
)
_
response
,
access_key_and_sig
=
headers
[
"Authorization"
]
.
split
(
" "
)
access_key
=
access_key_and_sig
.
split
(
":"
)[
0
]
# This is what we should be doing...
...
...
@@ -234,10 +241,11 @@ def results_callback(request):
return
HttpResponse
(
"OK!"
)
@login_required
def
show_requirements
(
request
,
course_id
):
"""
Show the requirements necessary for
Show the requirements necessary for
the verification flow.
"""
if
CourseEnrollment
.
enrollment_mode_for_user
(
request
.
user
,
course_id
)
==
'verified'
:
return
redirect
(
reverse
(
'dashboard'
))
...
...
@@ -246,69 +254,8 @@ def show_requirements(request, course_id):
context
=
{
"course_id"
:
course_id
,
"course_name"
:
course
.
display_name_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"course_org"
:
course
.
display_org_with_default
,
"course_num"
:
course
.
display_number_with_default
,
"is_not_active"
:
not
request
.
user
.
is_active
,
}
return
render_to_response
(
"verify_student/show_requirements.html"
,
context
)
def
show_verification_page
(
request
):
pass
def
enroll
(
user
,
course_id
,
mode_slug
):
"""
Enroll the user in a course for a certain mode.
This is the view you send folks to when they click on the enroll button.
This does NOT cover changing enrollment modes -- it's intended for new
enrollments only, and will just redirect to the dashboard if it detects
that an enrollment already exists.
"""
# If the user is already enrolled, jump to the dashboard. Yeah, we could
# do upgrades here, but this method is complicated enough.
if
CourseEnrollment
.
is_enrolled
(
user
,
course_id
):
return
HttpResponseRedirect
(
reverse
(
'dashboard'
))
available_modes
=
CourseModes
.
modes_for_course
(
course_id
)
# If they haven't chosen a mode...
if
not
mode_slug
:
# Does this course support multiple modes of Enrollment? If so, redirect
# to a page that lets them choose which mode they want.
if
len
(
available_modes
)
>
1
:
return
HttpResponseRedirect
(
reverse
(
'choose_enroll_mode'
,
kwargs
=
{
'course_id'
:
course_id
})
)
# Otherwise, we use the only mode that's supported...
else
:
mode_slug
=
available_modes
[
0
]
.
slug
# If the mode is one of the simple, non-payment ones, do the enrollment and
# send them to their dashboard.
if
mode_slug
in
(
"honor"
,
"audit"
):
CourseEnrollment
.
enroll
(
user
,
course_id
,
mode
=
mode_slug
)
return
HttpResponseRedirect
(
reverse
(
'dashboard'
))
if
mode_slug
==
"verify"
:
if
SoftwareSecurePhotoVerification
.
has_submitted_recent_request
(
user
):
# Capture payment info
# Create an order
# 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
:
mode
=
available_modes
[
0
]
elif
len
(
available_modes
)
==
1
:
if
mode
!=
available_modes
[
0
]:
raise
Exception
()
mode
=
available_modes
[
0
]
if
mode
==
"honor"
:
CourseEnrollment
.
enroll
(
user
,
course_id
)
return
HttpResponseRedirect
(
reverse
(
'dashboard'
))
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