def post(self, request, course_id): """ submits the reverification to SoftwareSecure """ try: now = datetime.datetime.now(UTC) course_id = CourseKey.from_string(course_id) window = MidcourseReverificationWindow.get_window(course_id, now) if window is None: raise WindowExpiredException attempt = SoftwareSecurePhotoVerification(user=request.user, window=window) b64_face_image = request.POST["face_image"].split(",")[1] attempt.upload_face_image(b64_face_image.decode("base64")) attempt.fetch_photo_id_image() attempt.mark_ready() attempt.save() attempt.submit() course_enrollment = CourseEnrollment.get_or_create_enrollment(request.user, course_id) course_enrollment.update_enrollment(mode="verified") course_enrollment.emit_event(EVENT_NAME_USER_SUBMITTED_MIDCOURSE_REVERIFY) return HttpResponseRedirect(reverse("verify_student_midcourse_reverification_confirmation")) except WindowExpiredException: log.exception( "User {} attempted to re-verify, but the window expired before the attempt".format(request.user.id) ) return HttpResponseRedirect(reverse("verify_student_reverification_window_expired")) except Exception: log.exception("Could not submit verification attempt for user {}".format(request.user.id)) context = {"user_full_name": request.user.profile.name, "error": True} return render_to_response("verify_student/midcourse_photo_reverification.html", context)
def test_state_transitions(self): """Make sure we can't make unexpected status transitions. The status transitions we expect are:: created → ready → submitted → approved ↑ ↓ → denied """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) assert_equals(attempt.status, SoftwareSecurePhotoVerification.STATUS.created) assert_equals(attempt.status, "created") # These should all fail because we're in the wrong starting state. assert_raises(VerificationException, attempt.submit) assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) # Now let's fill in some values so that we can pass the mark_ready() call attempt.face_image_url = "http://fake.edx.org/face.jpg" attempt.photo_id_image_url = "http://fake.edx.org/photo_id.jpg" attempt.mark_ready() assert_equals(attempt.name, user.profile.name) # Move this to another test assert_equals(attempt.status, "ready") # Once again, state transitions should fail here. We can't approve or # deny anything until it's been placed into the submitted state -- i.e. # the user has clicked on whatever agreements, or given payment, or done # whatever the application requires before it agrees to process their # attempt. assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny)
def post(self, request): """ submits the reverification to SoftwareSecure """ try: attempt = SoftwareSecurePhotoVerification(user=request.user) 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() # save this attempt attempt.save() # then submit it across attempt.submit() return HttpResponseRedirect(reverse('verify_student_reverification_confirmation')) except Exception: log.exception( "Could not submit verification attempt for user {}".format(request.user.id) ) context = { "user_full_name": request.user.profile.name, "error": True, } return render_to_response("verify_student/photo_reverification.html", context)
def test_state_transitions(self): """ Make sure we can't make unexpected status transitions. The status transitions we expect are:: → → → must_retry ↑ ↑ ↓ created → ready → submitted → approved ↓ ↑ ↓ ↓ → → denied """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) assert_equals(attempt.status, "created") # These should all fail because we're in the wrong starting state. assert_raises(VerificationException, attempt.submit) assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) # Now let's fill in some values so that we can pass the mark_ready() call attempt.mark_ready() assert_equals(attempt.status, "ready") # ready (can't approve or deny unless it's "submitted") assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) DENY_ERROR_MSG = '[{"photoIdReasons": ["Not provided"]}]' # must_retry attempt.status = "must_retry" attempt.system_error("System error") attempt.approve() attempt.status = "must_retry" attempt.deny(DENY_ERROR_MSG) # submitted attempt.status = "submitted" attempt.deny(DENY_ERROR_MSG) attempt.status = "submitted" attempt.approve() # approved assert_raises(VerificationException, attempt.submit) attempt.approve() # no-op attempt.system_error( "System error") # no-op, something processed it without error attempt.deny(DENY_ERROR_MSG) # denied assert_raises(VerificationException, attempt.submit) attempt.deny(DENY_ERROR_MSG) # no-op attempt.system_error( "System error") # no-op, something processed it without error attempt.approve()
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) 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'] course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(course_id, decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(course_id, 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[course_id] = amount request.session['donation_for_course'] = donation_for_course # prefer professional mode over verified_mode current_mode = CourseMode.verified_mode_for_course(course_id) if current_mode.slug == 'professional': amount = current_mode.min_price # make sure this course has a verified mode if not current_mode: return HttpResponseBadRequest(_("This course doesn't support verified certificates")) if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) params = get_signed_purchase_params( cart, callback_url=callback_url ) return HttpResponse(json.dumps(params), content_type="text/json")
def test_state_transitions(self): """ Make sure we can't make unexpected status transitions. The status transitions we expect are:: → → → must_retry ↑ ↑ ↓ created → ready → submitted → approved ↓ ↑ ↓ ↓ → → denied """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) assert_equals(attempt.status, "created") # These should all fail because we're in the wrong starting state. assert_raises(VerificationException, attempt.submit) assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) # Now let's fill in some values so that we can pass the mark_ready() call attempt.mark_ready() assert_equals(attempt.status, "ready") # ready (can't approve or deny unless it's "submitted") assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) DENY_ERROR_MSG = '[{"photoIdReasons": ["Not provided"]}]' # must_retry attempt.status = "must_retry" attempt.system_error("System error") attempt.approve() attempt.status = "must_retry" attempt.deny(DENY_ERROR_MSG) # submitted attempt.status = "submitted" attempt.deny(DENY_ERROR_MSG) attempt.status = "submitted" attempt.approve() # approved assert_raises(VerificationException, attempt.submit) attempt.approve() # no-op attempt.system_error("System error") # no-op, something processed it without error attempt.deny(DENY_ERROR_MSG) # denied assert_raises(VerificationException, attempt.submit) attempt.deny(DENY_ERROR_MSG) # no-op attempt.system_error("System error") # no-op, something processed it without error attempt.approve()
def create_and_submit(self): """Helper method to create a generic submission and send it.""" user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = u"Rust\u01B4" attempt.upload_face_image("Just pretend this is image data") attempt.upload_photo_id_image("Hey, we're a photo ID") attempt.mark_ready() attempt.submit() return attempt
def create_and_submit(self, username): """ Helper method that lets us create new SoftwareSecurePhotoVerifications """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = username attempt.upload_face_image("Fake Data") attempt.upload_photo_id_image("More Fake Data") attempt.mark_ready() attempt.submit() return attempt
def create_and_submit(self, username): """ Helper method that lets us create new SoftwareSecurePhotoVerifications """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = username attempt.upload_face_image("Fake Data") attempt.upload_photo_id_image("More Fake Data") attempt.mark_ready() attempt.submit() return attempt
def create_and_submit(self): """Helper method to create a generic submission and send it.""" user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = u"Rust\u01B4" attempt.upload_face_image("Just pretend this is image data") attempt.upload_photo_id_image("Hey, we're a photo ID") attempt.mark_ready() attempt.submit() return attempt
def submit_photos_for_verification(request): """Submit a photo verification attempt. Arguments: request (HttpRequest): The request to submit photos. Returns: HttpResponse: 200 on success, 400 if there are errors. """ # Check the required parameters missing_params = set(['face_image', 'photo_id_image']) - set( request.POST.keys()) if len(missing_params) > 0: msg = _("Missing required parameters: {missing}").format( missing=", ".join(missing_params)) return HttpResponseBadRequest(msg) # If the user already has valid or pending request, the UI will hide # the verification steps. For this reason, we reject any requests # for users that already have a valid or pending verification. if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): return HttpResponseBadRequest( _("You already have a valid or pending verification.")) # If the user wants to change his/her full name, # then try to do that before creating the attempt. if request.POST.get('full_name'): try: profile_api.update_profile(request.user.username, full_name=request.POST.get('full_name')) except profile_api.ProfileUserNotFound: return HttpResponseBadRequest(_("No profile found for user")) except profile_api.ProfileInvalidField: msg = _("Name must be at least {min_length} characters long." ).format(min_length=profile_api.FULL_NAME_MIN_LENGTH) return HttpResponseBadRequest(msg) # Create the attempt attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: msg = _("Image data is not valid.") return HttpResponseBadRequest(msg) attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) attempt.mark_ready() attempt.submit() return HttpResponse(200)
def submit_photos_for_verification(request): """Submit a photo verification attempt. Arguments: request (HttpRequest): The request to submit photos. Returns: HttpResponse: 200 on success, 400 if there are errors. """ # Check the required parameters missing_params = set(['face_image', 'photo_id_image']) - set(request.POST.keys()) if len(missing_params) > 0: msg = _("Missing required parameters: {missing}").format(missing=", ".join(missing_params)) return HttpResponseBadRequest(msg) # If the user already has valid or pending request, the UI will hide # the verification steps. For this reason, we reject any requests # for users that already have a valid or pending verification. if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): return HttpResponseBadRequest(_("You already have a valid or pending verification.")) # If the user wants to change his/her full name, # then try to do that before creating the attempt. if request.POST.get('full_name'): try: profile_api.update_profile( request.user.username, full_name=request.POST.get('full_name') ) except profile_api.ProfileUserNotFound: return HttpResponseBadRequest(_("No profile found for user")) except profile_api.ProfileInvalidField: msg = _( "Name must be at least {min_length} characters long." ).format(min_length=profile_api.FULL_NAME_MIN_LENGTH) return HttpResponseBadRequest(msg) # Create the attempt attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: msg = _("Image data is not valid.") return HttpResponseBadRequest(msg) attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) attempt.mark_ready() attempt.submit() return HttpResponse(200)
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending( request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) 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'] course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(course_id, decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(course_id, 0)) try: amount = decimal.Decimal(contribution).quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[course_id] = amount request.session['donation_for_course'] = donation_for_course verified_mode = CourseMode.modes_for_course_dict(course_id).get( 'verified', None) # make sure this course has a verified mode if not verified_mode: return HttpResponseBadRequest( _("This course doesn't support verified certificates")) if amount < verified_mode.min_price: return HttpResponseBadRequest( _("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() CertificateItem.add_to_order(cart, course_id, amount, 'verified') params = get_signed_purchase_params(cart) return HttpResponse(json.dumps(params), content_type="text/json")
def test_submission_while_testing_flag_is_true(self): """ Test that a fake value is set for field 'photo_id_key' of user's initial verification when the feature flag 'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING' is enabled. """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = "test-user" attempt.upload_photo_id_image("Image data") attempt.mark_ready() attempt.submit() self.assertEqual(attempt.photo_id_key, "fake-photo-id-key")
def test_state_transitions(self): """Make sure we can't make unexpected status transitions. The status transitions we expect are:: created → ready → submitted → approved ↑ ↓ → denied """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) 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) assert_raises(VerificationException, attempt.deny) # Now let's fill in some values so that we can pass the mark_ready() call attempt.face_image_url = "http://fake.edx.org/face.jpg" attempt.photo_id_image_url = "http://fake.edx.org/photo_id.jpg" attempt.mark_ready() assert_equals(attempt.name, user.profile.name) # Move this to another test assert_equals(attempt.status, "ready") # Once again, state transitions should fail here. We can't approve or # deny anything until it's been placed into the submitted state -- i.e. # the user has clicked on whatever agreements, or given payment, or done # whatever the application requires before it agrees to process their # attempt. assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) # Now we submit 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.deny("Could not read name on Photo ID") assert_equals(attempt.status, "denied")
def post(self, request, course_id): """ submits the reverification to SoftwareSecure """ try: now = datetime.datetime.now(UTC) course_id = SlashSeparatedCourseKey.from_deprecated_string( course_id) window = MidcourseReverificationWindow.get_window(course_id, now) if window is None: raise WindowExpiredException attempt = SoftwareSecurePhotoVerification(user=request.user, window=window) b64_face_image = request.POST['face_image'].split(",")[1] attempt.upload_face_image(b64_face_image.decode('base64')) attempt.fetch_photo_id_image() attempt.mark_ready() attempt.save() attempt.submit() course_enrollment = CourseEnrollment.get_or_create_enrollment( request.user, course_id) course_enrollment.update_enrollment(mode="verified") course_enrollment.emit_event( EVENT_NAME_USER_SUBMITTED_MIDCOURSE_REVERIFY) return HttpResponseRedirect( reverse( 'verify_student_midcourse_reverification_confirmation')) except WindowExpiredException: log.exception( "User {} attempted to re-verify, but the window expired before the attempt" .format(request.user.id)) return HttpResponseRedirect( reverse('verify_student_reverification_window_expired')) except Exception: log.exception( "Could not submit verification attempt for user {}".format( request.user.id)) context = { "user_full_name": request.user.profile.name, "error": True, } return render_to_response( "verify_student/midcourse_photo_reverification.html", context)
def test_name_freezing(self): """ You can change your name prior to marking a verification attempt ready, but changing your name afterwards should not affect the value in the in the attempt record. Basically, we want to always know what your name was when you submitted it. """ user = UserFactory.create() user.profile.name = u"Jack \u01B4" # gratuious non-ASCII char to test encodings attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = u"Clyde \u01B4" attempt.mark_ready() user.profile.name = u"Rusty \u01B4" assert_equals(u"Clyde \u01B4", attempt.name)
def test_name_freezing(self): """ You can change your name prior to marking a verification attempt ready, but changing your name afterwards should not affect the value in the in the attempt record. Basically, we want to always know what your name was when you submitted it. """ user = UserFactory.create() user.profile.name = u"Jack \u01B4" # gratuious non-ASCII char to test encodings attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = u"Clyde \u01B4" attempt.mark_ready() user.profile.name = u"Rusty \u01B4" assert_equals(u"Clyde \u01B4", attempt.name)
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) 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'] donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(course_id, decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(course_id, 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[course_id] = amount request.session['donation_for_course'] = donation_for_course verified_mode = CourseMode.modes_for_course_dict(course_id).get('verified', None) # make sure this course has a verified mode if not verified_mode: return HttpResponseBadRequest(_("This course doesn't support verified certificates")) if amount < verified_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() CertificateItem.add_to_order(cart, course_id, amount, 'verified') params = get_signed_purchase_params(cart) return HttpResponse(json.dumps(params), content_type="text/json")
def test_active_for_user(self): """ Make sure we can retrive a user's active (in progress) verification attempt. """ user = UserFactory.create() # This user has no active at the moment... assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user)) # Create an attempt and mark it ready... attempt = SoftwareSecurePhotoVerification(user=user) attempt.mark_ready() assert_equals(attempt, SoftwareSecurePhotoVerification.active_for_user(user)) # A new user won't see this... user2 = UserFactory.create() user2.save() assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user2)) # If it's got a different status, it doesn't count for status in ["submitted", "must_retry", "approved", "denied"]: attempt.status = status attempt.save() assert_is_none( SoftwareSecurePhotoVerification.active_for_user(user)) # But if we create yet another one and mark it ready, it passes again. attempt_2 = SoftwareSecurePhotoVerification(user=user) attempt_2.mark_ready() assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user)) # And if we add yet another one with a later created time, we get that # one instead. We always want the most recent attempt marked ready() attempt_3 = SoftwareSecurePhotoVerification( user=user, created_at=attempt_2.created_at + timedelta(days=1)) attempt_3.save() # We haven't marked attempt_3 ready yet, so attempt_2 still wins assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user)) # Now we mark attempt_3 ready and expect it to come back attempt_3.mark_ready() assert_equals(attempt_3, SoftwareSecurePhotoVerification.active_for_user(user))
def test_active_for_user(self): """ Make sure we can retrive a user's active (in progress) verification attempt. """ user = UserFactory.create() # This user has no active at the moment... assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user)) # Create an attempt and mark it ready... attempt = SoftwareSecurePhotoVerification(user=user) attempt.mark_ready() assert_equals(attempt, SoftwareSecurePhotoVerification.active_for_user(user)) # A new user won't see this... user2 = UserFactory.create() user2.save() assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user2)) # If it's got a different status, it doesn't count for status in ["submitted", "must_retry", "approved", "denied"]: attempt.status = status attempt.save() assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user)) # But if we create yet another one and mark it ready, it passes again. attempt_2 = SoftwareSecurePhotoVerification(user=user) attempt_2.mark_ready() assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user)) # And if we add yet another one with a later created time, we get that # one instead. We always want the most recent attempt marked ready() attempt_3 = SoftwareSecurePhotoVerification( user=user, created_at=attempt_2.created_at + timedelta(days=1) ) attempt_3.save() # We haven't marked attempt_3 ready yet, so attempt_2 still wins assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user)) # Now we mark attempt_3 ready and expect it to come back attempt_3.mark_ready() assert_equals(attempt_3, SoftwareSecurePhotoVerification.active_for_user(user))
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ # Only submit photos if photo data is provided by the client. # TODO (ECOM-188): Once the A/B test of decoupling verified / payment # completes, we may be able to remove photo submission from this step # entirely. submit_photo = "face_image" in request.POST and "photo_id_image" in request.POST if submit_photo and not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST["face_image"].split(",")[1] b64_photo_id_image = request.POST["photo_id_image"].split(",")[1] except IndexError: log.error(u"Invalid image data during photo verification.") context = {"success": False} return JsonResponse(context) 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"] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get("donation_for_course", {}) current_donation = donation_for_course.get(unicode(course_id), decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(unicode(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal(".01"), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[unicode(course_id)] = amount request.session["donation_for_course"] = donation_for_course # prefer professional mode over verified_mode current_mode = CourseMode.verified_mode_for_course(course_id) # make sure this course has a verified mode if not current_mode: log.warn(u"Verification requested for course {course_id} without a verified mode.".format(course_id=course_id)) return HttpResponseBadRequest(_("This course doesn't support verified certificates")) if current_mode.slug == "professional": amount = current_mode.min_price if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode, currency=current_mode.currency) # Change the order's status so that we don't accidentally modify it later. # We need to do this to ensure that the parameters we send to the payment system # match what we store in the database. # (Ordinarily we would do this client-side when the user submits the form, but since # the JavaScript on this page does that immediately, we make the change here instead. # This avoids a second AJAX call and some additional complication of the JavaScript.) # If a user later re-enters the verification / payment flow, she will create a new order. cart.start_purchase() callback_url = request.build_absolute_uri(reverse("shoppingcart.views.postpay_callback")) params = get_signed_purchase_params( cart, callback_url=callback_url, extra_data=[unicode(course_id), current_mode.slug] ) # params['success'] = True # return HttpResponse(json.dumps(params), content_type="text/json") processor = settings.CC_PROCESSOR.get(settings.CC_PROCESSOR_NAME, {}) callback_data = { "description": "Верифікований сертифікат", "order_id": cart.id, "server_url": processor.get("SERVER_URL", ""), "currency": processor.get("CURRENCY", ""), "result_url": processor.get("RESULT_URL", ""), "public_key": processor.get("PUBLIC_KEY", ""), "language": processor.get("LANGUAGE", "en"), "pay_way": processor.get("PAY_WAY", ""), "amount": current_mode.min_price, "sandbox": processor.get("SANDBOX", 0), "version": processor.get("VERSION", 3), "type": "buy", } log.warn(json.dumps(callback_data)) # callback_data = { # "description": "Верифікований сертифікат", # "order_id": cart.id, # "server_url": "http://courses.prometheus.org.ua/shoppingcart/postpay_callback/", # "currency": "UAH", # "result_url": "http://courses.prometheus.org.ua/shoppingcart/postpay_callback/", # "public_key": "", # "language": "en", # "pay_way": "card,liqpay,privat24", # "amount": "5", # "sandbox": "1", # "version": 3, # "type": "buy" # } callback_data = base64.b64encode(json.dumps(callback_data)) private_key = processor.get("PRIVATE_KEY", "") callback_signature = base64.b64encode(sha.new(private_key + callback_data + private_key).digest()) params["data"] = callback_data params["signature"] = callback_signature return HttpResponse(json.dumps(params), content_type="text/json")
def submit_photos_for_verification(request): """Submit a photo verification attempt. Arguments: request (HttpRequest): The request to submit photos. Returns: HttpResponse: 200 on success, 400 if there are errors. """ # Check the required parameters missing_params = set(['face_image', 'photo_id_image']) - set(request.POST.keys()) if len(missing_params) > 0: msg = _("Missing required parameters: {missing}").format(missing=", ".join(missing_params)) return HttpResponseBadRequest(msg) # If the user already has valid or pending request, the UI will hide # the verification steps. For this reason, we reject any requests # for users that already have a valid or pending verification. if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): return HttpResponseBadRequest(_("You already have a valid or pending verification.")) # If the user wants to change his/her full name, # then try to do that before creating the attempt. if request.POST.get('full_name'): try: update_account_settings(request.user, {"name": request.POST.get('full_name')}) except UserNotFound: return HttpResponseBadRequest(_("No profile found for user")) except AccountValidationError: msg = _( "Name must be at least {min_length} characters long." ).format(min_length=NAME_MIN_LENGTH) return HttpResponseBadRequest(msg) # Create the attempt attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: msg = _("Image data is not valid.") return HttpResponseBadRequest(msg) attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) attempt.mark_ready() attempt.submit() account_settings = get_account_settings(request.user) # Send a confirmation email to the user context = { 'full_name': account_settings['name'], 'platform_name': settings.PLATFORM_NAME } subject = _("Verification photos received") message = render_to_string('emails/photo_submission_confirmation.txt', context) from_address = microsite.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL) to_address = account_settings['email'] send_mail(subject, message, from_address, [to_address], fail_silently=False) return HttpResponse(200)
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ # Only submit photos if photo data is provided by the client. # TODO (ECOM-188): Once the A/B test of decoupling verified / payment # completes, we may be able to remove photo submission from this step # entirely. submit_photo = ( 'face_image' in request.POST and 'photo_id_image' in request.POST ) if ( submit_photo and not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user) ): attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: log.error(u"Invalid image data during photo verification.") context = { 'success': False, } return JsonResponse(context) 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'] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) contribution = request.POST.get("contribution", donation_for_course.get(unicode(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) current_mode = None paid_modes = CourseMode.paid_modes_for_course(course_id) # Check if there are more than 1 paid(mode with min_price>0 e.g verified/professional/no-id-professional) modes # for course exist then choose the first one if paid_modes: if len(paid_modes) > 1: log.warn(u"Multiple paid course modes found for course '%s' for create order request", course_id) current_mode = paid_modes[0] # Make sure this course has a paid mode if not current_mode: log.warn(u"Create order requested for course '%s' without a paid mode.", course_id) return HttpResponseBadRequest(_("This course doesn't support paid certificates")) if CourseMode.is_professional_mode(current_mode): amount = current_mode.min_price if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) if current_mode.sku: return create_order_with_ecommerce_service(request.user, course_id, current_mode) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) # Change the order's status so that we don't accidentally modify it later. # We need to do this to ensure that the parameters we send to the payment system # match what we store in the database. # (Ordinarily we would do this client-side when the user submits the form, but since # the JavaScript on this page does that immediately, we make the change here instead. # This avoids a second AJAX call and some additional complication of the JavaScript.) # If a user later re-enters the verification / payment flow, she will create a new order. cart.start_purchase() callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) params = get_signed_purchase_params( cart, callback_url=callback_url, extra_data=[unicode(course_id), current_mode.slug] ) params['success'] = True return HttpResponse(json.dumps(params), content_type="text/json")
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ # Only submit photos if photo data is provided by the client. # TODO (ECOM-188): Once the A/B test of decoupling verified / payment # completes, we may be able to remove photo submission from this step # entirely. submit_photo = ( 'face_image' in request.POST and 'photo_id_image' in request.POST ) if ( submit_photo and not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user) ): attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: log.error(u"Invalid image data during photo verification.") context = { 'success': False, } return JsonResponse(context) 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'] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) contribution = request.POST.get("contribution", donation_for_course.get(unicode(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) current_mode = None paid_modes = CourseMode.paid_modes_for_course(course_id) # Check if there are more than 1 paid(mode with min_price>0 e.g verified/professional/no-id-professional) modes # for course exist then choose the first one if paid_modes: if len(paid_modes) > 1: log.warn(u"Multiple paid course modes found for course '%s' for create order request", course_id) current_mode = paid_modes[0] # Make sure this course has a paid mode if not current_mode: log.warn(u"Create order requested for course '%s' without a paid mode.", course_id) return HttpResponseBadRequest(_("This course doesn't support paid certificates")) if CourseMode.is_professional_mode(current_mode): amount = current_mode.min_price if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) if current_mode.sku: return create_order_with_ecommerce_service(request.user, course_id, current_mode) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) # Change the order's status so that we don't accidentally modify it later. # We need to do this to ensure that the parameters we send to the payment system # match what we store in the database. # (Ordinarily we would do this client-side when the user submits the form, but since # the JavaScript on this page does that immediately, we make the change here instead. # This avoids a second AJAX call and some additional complication of the JavaScript.) # If a user later re-enters the verification / payment flow, she will create a new order. cart.start_purchase() callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) params = get_signed_purchase_params( cart, callback_url=callback_url, extra_data=[unicode(course_id), current_mode.slug] ) params['success'] = True return HttpResponse(json.dumps(params), content_type="text/json")
def submit_photos_for_verification(request): """Submit a photo verification attempt. Arguments: request (HttpRequest): The request to submit photos. Returns: HttpResponse: 200 on success, 400 if there are errors. """ # Check the required parameters missing_params = set(['face_image', 'photo_id_image']) - set( request.POST.keys()) if len(missing_params) > 0: msg = _("Missing required parameters: {missing}").format( missing=", ".join(missing_params)) return HttpResponseBadRequest(msg) # If the user already has valid or pending request, the UI will hide # the verification steps. For this reason, we reject any requests # for users that already have a valid or pending verification. if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): return HttpResponseBadRequest( _("You already have a valid or pending verification.")) username = request.user.username # If the user wants to change his/her full name, # then try to do that before creating the attempt. if request.POST.get('full_name'): try: profile_api.update_profile(username, full_name=request.POST.get('full_name')) except profile_api.ProfileUserNotFound: return HttpResponseBadRequest(_("No profile found for user")) except profile_api.ProfileInvalidField: msg = _("Name must be at least {min_length} characters long." ).format(min_length=profile_api.FULL_NAME_MIN_LENGTH) return HttpResponseBadRequest(msg) # Create the attempt attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: msg = _("Image data is not valid.") return HttpResponseBadRequest(msg) attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) attempt.mark_ready() attempt.submit() profile_dict = profile_api.profile_info(username) if profile_dict: # Send a confirmation email to the user context = { 'full_name': profile_dict.get('full_name'), 'platform_name': settings.PLATFORM_NAME } subject = _("Verification photos received") message = render_to_string('emails/photo_submission_confirmation.txt', context) from_address = microsite.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL) to_address = profile_dict.get('email') send_mail(subject, message, from_address, [to_address], fail_silently=False) return HttpResponse(200)
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: context = { 'success': False, } return JsonResponse(context) 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'] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(unicode(course_id), decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(unicode(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[unicode(course_id)] = amount request.session['donation_for_course'] = donation_for_course # prefer professional mode over verified_mode current_mode = CourseMode.verified_mode_for_course(course_id) # make sure this course has a verified mode if not current_mode: return HttpResponseBadRequest(_("This course doesn't support verified certificates")) if current_mode.slug == 'professional': amount = current_mode.min_price if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) # Change the order's status so that we don't accidentally modify it later. # We need to do this to ensure that the parameters we send to the payment system # match what we store in the database. # (Ordinarily we would do this client-side when the user submits the form, but since # the JavaScript on this page does that immediately, we make the change here instead. # This avoids a second AJAX call and some additional complication of the JavaScript.) # If a user later re-enters the verification / payment flow, she will create a new order. cart.start_purchase() callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) params = get_signed_purchase_params( cart, callback_url=callback_url, extra_data=[unicode(course_id), current_mode.slug] ) params['success'] = True return HttpResponse(json.dumps(params), content_type="text/json")
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending( request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) try: b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] except IndexError: context = { 'success': False, } return JsonResponse(context) 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'] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(unicode(course_id), decimal.Decimal(0)) contribution = request.POST.get( "contribution", donation_for_course.get(unicode(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[unicode(course_id)] = amount request.session['donation_for_course'] = donation_for_course # prefer professional mode over verified_mode current_mode = CourseMode.verified_mode_for_course(course_id) # make sure this course has a verified mode if not current_mode: return HttpResponseBadRequest( _("This course doesn't support verified certificates")) if current_mode.slug == 'professional': amount = current_mode.min_price if amount < current_mode.min_price: return HttpResponseBadRequest( _("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) # Change the order's status so that we don't accidentally modify it later. # We need to do this to ensure that the parameters we send to the payment system # match what we store in the database. # (Ordinarily we would do this client-side when the user submits the form, but since # the JavaScript on this page does that immediately, we make the change here instead. # This avoids a second AJAX call and some additional complication of the JavaScript.) # If a user later re-enters the verification / payment flow, she will create a new order. cart.start_purchase() callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback")) params = get_signed_purchase_params(cart, callback_url=callback_url, extra_data=[unicode(course_id)]) params['success'] = True return HttpResponse(json.dumps(params), content_type="text/json")