Merge pull request #1115 from edx/diana/verify-pep8-pylint
Clean up some old pep8/pylint violations
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
"""
|
||||
Views for the course_mode module
|
||||
"""
|
||||
|
||||
import decimal
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import (
|
||||
HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, Http404
|
||||
HttpResponseBadRequest, 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"
|
||||
|
||||
@@ -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(fn, obj, obj.status, valid_start_statuses)
|
||||
).format(func, obj, obj.status, valid_start_statuses)
|
||||
raise VerificationException(exception_msg)
|
||||
return fn(obj, *args, **kwargs)
|
||||
return func(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 error:
|
||||
log.exception(error)
|
||||
|
||||
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
|
||||
return response
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
|
||||
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
|
||||
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'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user