예제 #1
0
    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)
예제 #2
0
def toggle_failed_banner_off(request):
    """
    Finds all denied midcourse reverifications for a user and permanently toggles
    the "Reverification Failed" banner off for those verifications.
    """
    user_id = request.POST.get('user_id')
    SoftwareSecurePhotoVerification.display_off(user_id)
예제 #3
0
 def test_parse_error_msg_success(self):
     user = UserFactory.create()
     attempt = SoftwareSecurePhotoVerification(user=user)
     attempt.status = 'denied'
     attempt.error_msg = '[{"photoIdReasons": ["Not provided"]}]'
     parsed_error_msg = attempt.parsed_error_msg()
     self.assertEquals("No photo ID was provided.", parsed_error_msg)
예제 #4
0
    def post(self, request, course_id, checkpoint_name):
        """Submits the re-verification attempt to SoftwareSecure

        Args:
            request(HttpRequest): HttpRequest object
            course_id(str): Course Id
            checkpoint_name(str): Checkpoint name

        Returns:
            HttpResponse with status_code 400 if photo is missing or any error
            or redirect to verify_student_verify_later url if initial verification doesn't exist otherwise
            HttpsResponse with status code 200
        """
        # Check the in-course re-verification is enabled or not
        incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
        if not incourse_reverify_enabled:
            raise Http404

        user = request.user
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)
        checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
        if checkpoint is None:
            log.error("Checkpoint is not defined. Could not submit verification attempt for user %s",
                      request.user.id)
            context = {
                'course_key': unicode(course_key),
                'course_name': course.display_name_with_default,
                'checkpoint_name': checkpoint_name,
                'error': True,
                'errorMsg': _("No checkpoint found"),
                'platform_name': settings.PLATFORM_NAME,
            }
            return render_to_response("verify_student/incourse_reverify.html", context)
        init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
        if not init_verification:
            log.error("Could not submit verification attempt for user %s", request.user.id)
            return redirect(reverse('verify_student_verify_later', kwargs={'course_id': unicode(course_key)}))

        try:
            attempt = SoftwareSecurePhotoVerification.submit_faceimage(
                request.user, request.POST['face_image'], init_verification.photo_id_key
            )
            checkpoint.add_verification_attempt(attempt)
            VerificationStatus.add_verification_status(checkpoint, user, "submitted")

            # emit the reverification event
            self._track_reverification_events(
                EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, user.id, course_id, checkpoint_name
            )

            return HttpResponse()
        except IndexError:
            log.exception("Invalid image data during photo verification.")
            return HttpResponseBadRequest(_("Invalid image data during photo verification."))
        except Exception:  # pylint: disable=broad-except
            log.exception("Could not submit verification attempt for user {}.").format(request.user.id)
            msg = _("Could not submit photos")
            return HttpResponseBadRequest(msg)
예제 #5
0
def toggle_failed_banner_off(request):
    """
    Finds all denied midcourse reverifications for a user and permanently toggles
    the "Reverification Failed" banner off for those verifications.
    """
    user_id = request.user.id
    SoftwareSecurePhotoVerification.display_off(user_id)
    return HttpResponse('Success')
예제 #6
0
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")
예제 #7
0
 def test_original_verification(self):
     orig_attempt = SoftwareSecurePhotoVerification(user=self.user)
     orig_attempt.save()
     window = MidcourseReverificationWindowFactory(
         course_id=self.course.id,
         start_date=datetime.now(pytz.UTC) - timedelta(days=15),
         end_date=datetime.now(pytz.UTC) - timedelta(days=13),
     )
     midcourse_attempt = SoftwareSecurePhotoVerification(user=self.user, window=window)
     self.assertEquals(midcourse_attempt.original_verification(user=self.user), orig_attempt)
예제 #8
0
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)
예제 #9
0
 def test_parse_error_msg_failure(self):
     user = UserFactory.create()
     attempt = SoftwareSecurePhotoVerification(user=user)
     attempt.status = 'denied'
     # when we can't parse into json
     bad_messages = {
         'Not Provided',
         '[{"IdReasons": ["Not provided"]}]',
         '{"IdReasons": ["Not provided"]}',
         u'[{"ïḋṚëäṡöṅṡ": ["Ⓝⓞⓣ ⓟⓡⓞⓥⓘⓓⓔⓓ "]}]',
     }
     for msg in bad_messages:
         attempt.error_msg = msg
         parsed_error_msg = attempt.parsed_error_msg()
         self.assertEquals(parsed_error_msg, "There was an error verifying your ID photos.")
예제 #10
0
    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
예제 #11
0
 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
예제 #12
0
    def get(self, request, course_id, checkpoint_name, usage_id):
        """ Display the view for face photo submission"""
        # Check the in-course re-verification is enabled or not

        incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
        if not incourse_reverify_enabled:
            raise Http404

        user = request.user
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)
        if course is None:
            raise Http404

        checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
        if checkpoint is None:
            raise Http404

        init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
        if not init_verification:
            return redirect(reverse('verify_student_verify_later', kwargs={'course_id': unicode(course_key)}))

        # emit the reverification event
        self._track_reverification_events(
            EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id, checkpoint_name
        )

        context = {
            'course_key': unicode(course_key),
            'course_name': course.display_name_with_default,
            'checkpoint_name': checkpoint_name,
            'platform_name': settings.PLATFORM_NAME,
            'usage_id': usage_id
        }
        return render_to_response("verify_student/incourse_reverify.html", context)
예제 #13
0
    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
        """
        upgrade = request.GET.get('upgrade', False)

        course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        # If the user has already been verified within the given time period,
        # redirect straight to the payment -- no need to verify again.
        if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
            return redirect(
                reverse('verify_student_verified',
                        kwargs={'course_id': course_id.to_deprecated_string()}) + "?upgrade={}".format(upgrade)
            )
        elif CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True):
            return redirect(reverse('dashboard'))
        else:
            # If they haven't completed a verification attempt, we have to
            # restart with a new one. We can't reuse an older one because we
            # won't be able to show them their encrypted photo_id -- it's easier
            # bookkeeping-wise just to start over.
            progress_state = "start"

        modes_dict = CourseMode.modes_for_course_dict(course_id)
        verify_mode = modes_dict.get('verified', None)
        # if the course doesn't have a verified mode, we want to kick them
        # from the flow
        if not verify_mode:
            return redirect(reverse('dashboard'))
        if course_id.to_deprecated_string() in request.session.get("donation_for_course", {}):
            chosen_price = request.session["donation_for_course"][course_id.to_deprecated_string()]
        else:
            chosen_price = verify_mode.min_price

        course = modulestore().get_course(course_id)
        context = {
            "progress_state": progress_state,
            "user_full_name": request.user.profile.name,
            "course_id": course_id.to_deprecated_string(),
            "course_modes_choose_url": reverse('course_modes_choose', kwargs={'course_id': course_id.to_deprecated_string()}),
            "course_name": course.display_name_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)
                for price in verify_mode.suggested_prices.split(",")
            ],
            "currency": verify_mode.currency.upper(),
            "chosen_price": chosen_price,
            "min_price": verify_mode.min_price,
            "upgrade": upgrade == u'True',
            "can_audit": "audit" in modes_dict,
        }

        return render_to_response('verify_student/photo_verification.html', context)
예제 #14
0
def show_requirements(request, course_id):
    """
    Show the requirements necessary for the verification flow.
    """
    # TODO: seems borked for professional; we're told we need to take photos even if there's a pending verification
    course_id = CourseKey.from_string(course_id)
    upgrade = request.GET.get('upgrade', False)
    if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True):
        return redirect(reverse('dashboard'))
    if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
        return redirect(
            reverse(
                'verify_student_verified',
                kwargs={'course_id': course_id.to_deprecated_string()}
            ) + "?upgrade={}".format(upgrade)
        )

    upgrade = request.GET.get('upgrade', False)
    course = modulestore().get_course(course_id)
    modes_dict = CourseMode.modes_for_course_dict(course_id)
    context = {
        "course_id": course_id.to_deprecated_string(),
        "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_id.to_deprecated_string()}),
        "verify_student_url": reverse('verify_student_verify', kwargs={'course_id': course_id.to_deprecated_string()}),
        "course_name": course.display_name_with_default,
        "course_org": course.display_org_with_default,
        "course_num": course.display_number_with_default,
        "is_not_active": not request.user.is_active,
        "upgrade": upgrade == u'True',
        "modes_dict": modes_dict,
    }
    return render_to_response("verify_student/show_requirements.html", context)
예제 #15
0
def checkout_receipt(request):
    """ Receipt view. """
    context = {
        'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME),
        'verified': SoftwareSecurePhotoVerification.verification_valid_or_pending(request.user).exists()
    }
    return render_to_response('commerce/checkout_receipt.html', context)
예제 #16
0
    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)
예제 #17
0
    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)
예제 #18
0
    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,
        # but I don't really have the time to refactor it more nicely and test.
        course = course_from_id(course_id)
        if not has_access(user, course, 'enroll'):
            error_msg = _("Enrollment is closed")
            return self.get(request, course_id, error=error_msg)

        upgrade = request.GET.get('upgrade', False)

        requested_mode = self.get_requested_mode(request.POST)

        allowed_modes = CourseMode.modes_for_course_dict(course_id)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode in ("audit", "honor"):
            CourseEnrollment.enroll(user, course_id, requested_mode)
            return redirect('dashboard')

        mode_info = allowed_modes[requested_mode]

        if requested_mode == "verified":
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(
                    decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _(
                    "No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course",
                                                      {})
            donation_for_course[course_id] = amount_value
            request.session["donation_for_course"] = donation_for_course
            if SoftwareSecurePhotoVerification.user_has_valid_or_pending(
                    request.user):
                return redirect(
                    reverse('verify_student_verified',
                            kwargs={'course_id': course_id}) +
                    "?upgrade={}".format(upgrade))

            return redirect(
                reverse('verify_student_show_requirements',
                        kwargs={'course_id': course_id}) +
                "?upgrade={}".format(upgrade))
예제 #19
0
    def test_verification_for_datetime(self):
        user = UserFactory.create()
        now = datetime.now(pytz.UTC)

        # No attempts in the query set, so should return None
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(now, query)
        self.assertIs(result, None)

        # Should also return None if no deadline specified
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(None, query)
        self.assertIs(result, None)

        # Make an attempt
        attempt = SoftwareSecurePhotoVerification.objects.create(user=user)

        # Before the created date, should get no results
        before = attempt.created_at - timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(before, query)
        self.assertIs(result, None)

        # Immediately after the created date, should get the attempt
        after_created = attempt.created_at + timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(after_created, query)
        self.assertEqual(result, attempt)

        # If no deadline specified, should return first available
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(None, query)
        self.assertEqual(result, attempt)

        # Immediately before the expiration date, should get the attempt
        expiration = attempt.created_at + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
        before_expiration = expiration - timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(before_expiration, query)
        self.assertEqual(result, attempt)

        # Immediately after the expiration date, should not get the attempt
        after = expiration + timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(after, query)
        self.assertIs(result, None)

        # Create a second attempt in the same window
        second_attempt = SoftwareSecurePhotoVerification.objects.create(user=user)

        # Now we should get the newer attempt
        deadline = second_attempt.created_at + timedelta(days=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(deadline, query)
        self.assertEqual(result, second_attempt)
예제 #20
0
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")
예제 #21
0
    def test_verification_for_datetime(self):
        user = UserFactory.create()
        now = datetime.now(pytz.UTC)

        # No attempts in the query set, so should return None
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(now, query)
        self.assertIs(result, None)

        # Should also return None if no deadline specified
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(None, query)
        self.assertIs(result, None)

        # Make an attempt
        attempt = SoftwareSecurePhotoVerification.objects.create(user=user)

        # Before the created date, should get no results
        before = attempt.created_at - timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(before, query)
        self.assertIs(result, None)

        # Immediately after the created date, should get the attempt
        after_created = attempt.created_at + timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(after_created, query)
        self.assertEqual(result, attempt)

        # If no deadline specified, should return first available
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(None, query)
        self.assertEqual(result, attempt)

        # Immediately before the expiration date, should get the attempt
        expiration = attempt.created_at + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
        before_expiration = expiration - timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(before_expiration, query)
        self.assertEqual(result, attempt)

        # Immediately after the expiration date, should not get the attempt
        after = expiration + timedelta(seconds=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(after, query)
        self.assertIs(result, None)

        # Create a second attempt in the same window
        second_attempt = SoftwareSecurePhotoVerification.objects.create(user=user)

        # Now we should get the newer attempt
        deadline = second_attempt.created_at + timedelta(days=1)
        query = SoftwareSecurePhotoVerification.objects.filter(user=user)
        result = SoftwareSecurePhotoVerification.verification_for_datetime(deadline, query)
        self.assertEqual(result, second_attempt)
예제 #22
0
    def test_fetch_photo_id_image(self):
        user = UserFactory.create()
        orig_attempt = SoftwareSecurePhotoVerification(user=user)
        orig_attempt.save()

        old_key = orig_attempt.photo_id_key

        new_attempt = SoftwareSecurePhotoVerification(user=user)
        new_attempt.save()
        new_attempt.fetch_photo_id_image()
        assert_equals(new_attempt.photo_id_key, old_key)
예제 #23
0
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'))
예제 #24
0
    def get(self, request, course_id):
        """
        """
        # If the user has already been verified within the given time period,
        # redirect straight to the payment -- no need to verify again.
        if SoftwareSecurePhotoVerification.user_has_valid_or_pending(
                request.user):
            return redirect(
                reverse('verify_student_verified',
                        kwargs={'course_id': course_id}))
        elif CourseEnrollment.enrollment_mode_for_user(
                request.user, course_id) == 'verified':
            return redirect(reverse('dashboard'))
        else:
            # If they haven't completed a verification attempt, we have to
            # restart with a new one. We can't reuse an older one because we
            # won't be able to show them their encrypted photo_id -- it's easier
            # bookkeeping-wise just to start over.
            progress_state = "start"

        verify_mode = CourseMode.mode_for_course(course_id, "verified")
        if course_id in request.session.get("donation_for_course", {}):
            chosen_price = request.session["donation_for_course"][course_id]
        else:
            chosen_price = verify_mode.min_price

        course = course_from_id(course_id)
        context = {
            "progress_state":
            progress_state,
            "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,
            "purchase_endpoint":
            get_purchase_endpoint(),
            "suggested_prices": [
                decimal.Decimal(price)
                for price in verify_mode.suggested_prices.split(",")
            ],
            "currency":
            verify_mode.currency.upper(),
            "chosen_price":
            chosen_price,
            "min_price":
            verify_mode.min_price,
        }

        return render_to_response('verify_student/photo_verification.html',
                                  context)
예제 #25
0
    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
        """
        upgrade = request.GET.get('upgrade', False)

        # If the user has already been verified within the given time period,
        # redirect straight to the payment -- no need to verify again.
        if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
            return redirect(
                reverse('verify_student_verified',
                        kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade)
            )
        elif CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified':
            return redirect(reverse('dashboard'))
        else:
            # If they haven't completed a verification attempt, we have to
            # restart with a new one. We can't reuse an older one because we
            # won't be able to show them their encrypted photo_id -- it's easier
            # bookkeeping-wise just to start over.
            progress_state = "start"

        verify_mode = CourseMode.mode_for_course(course_id, "verified")
        # if the course doesn't have a verified mode, we want to kick them
        # from the flow
        if not verify_mode:
            return redirect(reverse('dashboard'))
        if course_id in request.session.get("donation_for_course", {}):
            chosen_price = request.session["donation_for_course"][course_id]
        else:
            chosen_price = verify_mode.min_price

        course = course_from_id(course_id)
        context = {
            "progress_state": progress_state,
            "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,
            "purchase_endpoint": get_purchase_endpoint(),
            "suggested_prices": [
                decimal.Decimal(price)
                for price in verify_mode.suggested_prices.split(",")
            ],
            "currency": verify_mode.currency.upper(),
            "chosen_price": chosen_price,
            "min_price": verify_mode.min_price,
            "upgrade": upgrade,
        }

        return render_to_response('verify_student/photo_verification.html', context)
예제 #26
0
    def test_user_has_valid_or_pending(self):
        window = MidcourseReverificationWindowFactory(
            course_id=self.course.id,
            start_date=datetime.now(pytz.UTC) - timedelta(days=15),
            end_date=datetime.now(pytz.UTC) - timedelta(days=13),
        )

        attempt = SoftwareSecurePhotoVerification(status="must_retry", user=self.user, window=window)
        attempt.save()

        assert_false(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window))

        attempt.status = "approved"
        attempt.save()
        assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window))
예제 #27
0
    def get(self, request, course_id, usage_id):
        """Display the view for face photo submission.

        Args:
            request(HttpRequest): HttpRequest object
            course_id(str): A string of course id
            usage_id(str): Location of Reverification XBlock in courseware

        Returns:
            HttpResponse
        """
        # Check that in-course re-verification is enabled or not
        incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled

        if not incourse_reverify_enabled:
            log.error(
                u"In-course reverification is not enabled.  "
                u"You can enable it in Django admin by setting "
                u"InCourseReverificationConfiguration to enabled."
            )
            raise Http404

        user = request.user
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)
        if course is None:
            log.error(u"Could not find course '%s' for in-course reverification.", course_key)
            raise Http404

        checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, usage_id)
        if checkpoint is None:
            log.error(
                u"No verification checkpoint exists for the "
                u"course '%s' and checkpoint location '%s'.",
                course_key, usage_id
            )
            raise Http404

        initial_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
        if not initial_verification:
            return self._redirect_no_initial_verification(user, course_key)

        # emit the reverification event
        self._track_reverification_events(
            EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id, checkpoint.checkpoint_name
        )

        context = {
            'course_key': unicode(course_key),
            'course_name': course.display_name_with_default,
            'checkpoint_name': checkpoint.checkpoint_name,
            'platform_name': settings.PLATFORM_NAME,
            'usage_id': usage_id,
            'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"),
        }
        return render_to_response("verify_student/incourse_reverify.html", context)
예제 #28
0
    def test_display(self):
        user = UserFactory.create()
        window = MidcourseReverificationWindowFactory()
        attempt = SoftwareSecurePhotoVerification(user=user, window=window, status="denied")
        attempt.save()

        # We expect the verification to be displayed by default
        self.assertEquals(SoftwareSecurePhotoVerification.display_status(user, window), True)

        # Turn it off
        SoftwareSecurePhotoVerification.display_off(user.id)
        self.assertEquals(SoftwareSecurePhotoVerification.display_status(user, window), False)
예제 #29
0
파일: views.py 프로젝트: LICEF/edx-platform
    def post(self, request, course_id):
        """ Takes the form submission from the page and parses it """
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        user = request.user

        # This is a bit redundant with logic in student.views.change_enrollment,
        # but I don't really have the time to refactor it more nicely and test.
        course = modulestore().get_course(course_key)
        if not has_access(user, 'enroll', course):
            # error_msg = _("Enrollment is closed")
            error_msg = u"La période d'inscription est terminée."
            return self.get(request, course_id, error=error_msg)

        upgrade = request.GET.get('upgrade', False)

        requested_mode = self.get_requested_mode(request.POST)

        allowed_modes = CourseMode.modes_for_course_dict(course_key)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode in ("audit", "honor"):
            CourseEnrollment.enroll(user, course_key, requested_mode)
            return redirect('dashboard')

        mode_info = allowed_modes[requested_mode]

        if requested_mode == "verified":
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _("No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course", {})
            donation_for_course[course_key] = amount_value
            request.session["donation_for_course"] = donation_for_course
            if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
                return redirect(
                    reverse('verify_student_verified',
                            kwargs={'course_id': course_key.to_deprecated_string()}) + "?upgrade={}".format(upgrade)
                )

            return redirect(
                reverse('verify_student_show_requirements',
                        kwargs={'course_id': course_key.to_deprecated_string()}) + "?upgrade={}".format(upgrade))
예제 #30
0
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'))
예제 #31
0
    def test_user_has_valid_or_pending(self):
        """
        Determine whether we have to prompt this user to verify, or if they've
        already at least initiated a verification submission.
        """
        user = UserFactory.create()
        attempt = SoftwareSecurePhotoVerification(user=user)

        # If it's any of these statuses, they don't have anything outstanding
        for status in ["created", "ready", "denied"]:
            attempt.status = status
            attempt.save()
            assert_false(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status)

        # Any of these, and we are. Note the benefit of the doubt we're giving
        # -- must_retry, and submitted both count until we hear otherwise
        for status in ["submitted", "must_retry", "approved"]:
            attempt.status = status
            attempt.save()
            assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status)
예제 #32
0
 def purchased_callback(self):
     """
     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:
         log.exception("Could not submit verification attempt for enrollment {}".format(self.course_enrollment))
     self.course_enrollment.change_mode(self.mode)
     self.course_enrollment.activate()
예제 #33
0
 def setUp(self):
     self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
     self.course_id = self.course.id
     self.user = UserFactory.create()
     self.attempt = SoftwareSecurePhotoVerification(
         status="submitted",
         user=self.user
     )
     self.attempt.save()
     self.receipt_id = self.attempt.receipt_id
     self.client = Client()
예제 #34
0
 def test_original_verification(self):
     orig_attempt = SoftwareSecurePhotoVerification(user=self.user)
     orig_attempt.save()
     window = MidcourseReverificationWindowFactory(
         course_id=self.course.id,
         start_date=datetime.now(pytz.UTC) - timedelta(days=15),
         end_date=datetime.now(pytz.UTC) - timedelta(days=13),
     )
     midcourse_attempt = SoftwareSecurePhotoVerification(user=self.user, window=window)
     self.assertEquals(midcourse_attempt.original_verification(user=self.user), orig_attempt)
예제 #35
0
    def _check_already_verified(self, user):
        """Check whether the user has a valid or pending verification.

        Note that this includes cases in which the user's verification
        has not been accepted (either because it hasn't been processed,
        or there was an error).

        This should return True if the user has done their part:
        submitted photos within the expiration period.

        """
        return SoftwareSecurePhotoVerification.user_has_valid_or_pending(user)
예제 #36
0
    def post(self, request, course_id):
        user = request.user

        # This is a bit redundant with logic in student.views.change_enrollement,
        # but I don't really have the time to refactor it more nicely and test.
        course = course_from_id(course_id)
        if not has_access(user, course, 'enroll'):
            error_msg = _("Enrollment is closed")
            return self.get(request, course_id, error=error_msg)

        requested_mode = self.get_requested_mode(request.POST.get("mode"))
        if requested_mode == "verified" and request.POST.get("honor-code"):
            requested_mode = "honor"

        allowed_modes = CourseMode.modes_for_course_dict(course_id)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode in ("audit", "honor"):
            CourseEnrollment.enroll(user, course_id, requested_mode)
            return redirect('dashboard')

        mode_info = allowed_modes[requested_mode]

        if requested_mode == "verified":
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _("No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course", {})
            donation_for_course[course_id] = amount_value
            request.session["donation_for_course"] = donation_for_course
            if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
                return redirect(
                    reverse('verify_student_verified',
                            kwargs={'course_id': course_id})
                )

            return redirect(
                reverse('verify_student_show_requirements',
                        kwargs={'course_id': course_id}),
            )
예제 #37
0
    def _check_already_verified(self, user):
        """Check whether the user has a valid or pending verification.

        Note that this includes cases in which the user's verification
        has not been accepted (either because it hasn't been processed,
        or there was an error).

        This should return True if the user has done their part:
        submitted photos within the expiration period.

        """
        return SoftwareSecurePhotoVerification.user_has_valid_or_pending(user)
예제 #38
0
    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")
예제 #39
0
    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)
예제 #40
0
 def purchased_callback(self):
     """
     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:
         log.exception(
             "Could not submit verification attempt for enrollment {}".format(self.course_enrollment)
         )
     self.course_enrollment.change_mode(self.mode)
     self.course_enrollment.activate()
예제 #41
0
    def test_verification_status_for_user(self, enrollment_mode, status, output):
        """
        Verify verification_status_for_user returns correct status.
        """
        user = UserFactory.create()
        course = CourseFactory.create()

        with patch('verify_student.models.SoftwareSecurePhotoVerification.user_is_verified') as mock_verification:

            mock_verification.return_value = status

            status = SoftwareSecurePhotoVerification.verification_status_for_user(user, course.id, enrollment_mode)
            self.assertEqual(status, output)
예제 #42
0
    def test_user_has_valid_or_pending(self):
        window = MidcourseReverificationWindowFactory(
            course_id=self.course.id,
            start_date=datetime.now(pytz.UTC) - timedelta(days=15),
            end_date=datetime.now(pytz.UTC) - timedelta(days=13),
        )

        attempt = SoftwareSecurePhotoVerification(status="must_retry", user=self.user, window=window)
        attempt.save()

        assert_false(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window))

        attempt.status = "approved"
        attempt.save()
        assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window))
예제 #43
0
    def test_display(self):
        user = UserFactory.create()
        window = MidcourseReverificationWindowFactory()
        attempt = SoftwareSecurePhotoVerification(user=user, window=window, status="denied")
        attempt.save()

        # We expect the verification to be displayed by default
        self.assertEquals(SoftwareSecurePhotoVerification.display_status(user, window), True)

        # Turn it off
        SoftwareSecurePhotoVerification.display_off(user.id)
        self.assertEquals(SoftwareSecurePhotoVerification.display_status(user, window), False)
예제 #44
0
    def test_fetch_photo_id_image(self):
        user = UserFactory.create()
        orig_attempt = SoftwareSecurePhotoVerification(user=user, window=None)
        orig_attempt.save()

        old_key = orig_attempt.photo_id_key

        window = MidcourseReverificationWindowFactory(
            course_id=SlashSeparatedCourseKey("pony", "rainbow", "dash"),
            start_date=datetime.now(pytz.utc) - timedelta(days=5),
            end_date=datetime.now(pytz.utc) + timedelta(days=5)
        )
        new_attempt = SoftwareSecurePhotoVerification(user=user, window=window)
        new_attempt.save()
        new_attempt.fetch_photo_id_image()
        assert_equals(new_attempt.photo_id_key, old_key)
예제 #45
0
def show_requirements(request, course_id):
    """
    Show the requirements necessary for the verification flow.
    """
    # TODO: seems borked for professional; we're told we need to take photos even if there's a pending verification
    course_id = CourseKey.from_string(course_id)
    upgrade = request.GET.get('upgrade', False)
    if CourseEnrollment.enrollment_mode_for_user(request.user,
                                                 course_id) == ('verified',
                                                                True):
        return redirect(reverse('dashboard'))
    if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
        return redirect(
            reverse('verify_student_verified',
                    kwargs={'course_id': course_id.to_deprecated_string()}) +
            "?upgrade={}".format(upgrade))

    upgrade = request.GET.get('upgrade', False)
    course = modulestore().get_course(course_id)
    modes_dict = CourseMode.modes_for_course_dict(course_id)
    context = {
        "course_id":
        course_id.to_deprecated_string(),
        "course_modes_choose_url":
        reverse("course_modes_choose",
                kwargs={'course_id': course_id.to_deprecated_string()}),
        "verify_student_url":
        reverse('verify_student_verify',
                kwargs={'course_id': course_id.to_deprecated_string()}),
        "course_name":
        course.display_name_with_default,
        "course_org":
        course.display_org_with_default,
        "course_num":
        course.display_number_with_default,
        "is_not_active":
        not request.user.is_active,
        "upgrade":
        upgrade == u'True',
        "modes_dict":
        modes_dict,

        # TODO (ECOM-16): Remove once the AB test completes
        "autoreg":
        request.session.get('auto_register', False),
    }
    return render_to_response("verify_student/show_requirements.html", context)
예제 #46
0
파일: views.py 프로젝트: iivic/BoiseStateX
def checkout_receipt(request):
    """ Receipt view. """

    page_title = _('Receipt')
    is_payment_complete = True
    payment_support_email = microsite.get_value('payment_support_email', settings.PAYMENT_SUPPORT_EMAIL)
    payment_support_link = '<a href=\"mailto:{email}\">{email}</a>'.format(email=payment_support_email)

    is_cybersource = all(k in request.POST for k in ('signed_field_names', 'decision', 'reason_code'))
    if is_cybersource and request.POST['decision'] != 'ACCEPT':
        # Cybersource may redirect users to this view if it couldn't recover
        # from an error while capturing payment info.
        is_payment_complete = False
        page_title = _('Payment Failed')
        reason_code = request.POST['reason_code']
        # if the problem was with the info submitted by the user, we present more detailed messages.
        if is_user_payment_error(reason_code):
            error_summary = _("There was a problem with this transaction. You have not been charged.")
            error_text = _(
                "Make sure your information is correct, or try again with a different card or another form of payment."
            )
        else:
            error_summary = _("A system error occurred while processing your payment. You have not been charged.")
            error_text = _("Please wait a few minutes and then try again.")
        for_help_text = _("For help, contact {payment_support_link}.").format(payment_support_link=payment_support_link)
    else:
        # if anything goes wrong rendering the receipt, it indicates a problem fetching order data.
        error_summary = _("An error occurred while creating your receipt.")
        error_text = None  # nothing particularly helpful to say if this happens.
        for_help_text = _(
            "If your course does not appear on your dashboard, contact {payment_support_link}."
        ).format(payment_support_link=payment_support_link)

    context = {
        'page_title': page_title,
        'is_payment_complete': is_payment_complete,
        'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME),
        'verified': SoftwareSecurePhotoVerification.verification_valid_or_pending(request.user).exists(),
        'error_summary': error_summary,
        'error_text': error_text,
        'for_help_text': for_help_text,
        'payment_support_email': payment_support_email,
        'username': request.user.username,
        'nav_hidden': True,
    }
    return render_to_response('commerce/checkout_receipt.html', context)
예제 #47
0
    def response_post_params(cls, user):
        """
        Calculate the POST params we want to send back to the client.
        """
        access_key = settings.VERIFY_STUDENT["SOFTWARE_SECURE"][
            "API_ACCESS_KEY"]
        context = {
            'receipt_id': None,
            'authorization_code': 'SIS {}:0000'.format(access_key),
            'results_callback': reverse('verify_student_results_callback')
        }

        try:
            most_recent = SoftwareSecurePhotoVerification.original_verification(
                user)
            context["receipt_id"] = most_recent.receipt_id
        except:  # pylint: disable=bare-except
            pass

        return context
예제 #48
0
    def _verification_valid_until(self, user, date_format="%m/%d/%Y"):
        """
        Check whether the user has a valid or pending verification.

        Arguments:
            user:
            date_format: optional parameter for formatting datetime
                object to string in response

        Returns:
            datetime object in string format
        """
        photo_verifications = SoftwareSecurePhotoVerification.verification_valid_or_pending(
            user)
        # return 'expiration_datetime' of latest photo verification if found,
        # otherwise implicitly return ''
        if photo_verifications:
            return photo_verifications[0].expiration_datetime.strftime(
                date_format)

        return ''
예제 #49
0
    def get(self, request, course_id, checkpoint_name):
        """ Display the view for face photo submission"""
        # Check the in-course re-verification is enabled or not
        incourse_reverify_enabled = InCourseReverificationConfiguration.current(
        ).enabled
        if not incourse_reverify_enabled:
            raise Http404

        user = request.user
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)
        if course is None:
            raise Http404

        checkpoint = VerificationCheckpoint.get_verification_checkpoint(
            course_key, checkpoint_name)
        if checkpoint is None:
            raise Http404

        init_verification = SoftwareSecurePhotoVerification.get_initial_verification(
            user)
        if not init_verification:
            return redirect(
                reverse('verify_student_verify_later',
                        kwargs={'course_id': unicode(course_key)}))

        # emit the reverification event
        self._track_reverification_events(
            EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id,
            checkpoint_name)

        context = {
            'course_key': unicode(course_key),
            'course_name': course.display_name_with_default,
            'checkpoint_name': checkpoint_name,
            'platform_name': settings.PLATFORM_NAME,
        }
        return render_to_response("verify_student/incourse_reverify.html",
                                  context)
예제 #50
0
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)
        attempt.status = "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")
예제 #51
0
def check_verify_status_by_course(user, course_enrollment_pairs,
                                  all_course_modes):
    """Determine the per-course verification statuses for a given user.

    The possible statuses are:
        * VERIFY_STATUS_NEED_TO_VERIFY: The student has not yet submitted photos for verification.
        * VERIFY_STATUS_SUBMITTED: The student has submitted photos for verification,
          but has have not yet been approved.
        * VERIFY_STATUS_APPROVED: The student has been successfully verified.
        * VERIFY_STATUS_MISSED_DEADLINE: The student did not submit photos within the course's deadline.

    It is is also possible that a course does NOT have a verification status if:
        * The user is not enrolled in a verified mode, meaning that the user didn't pay.
        * The course does not offer a verified mode.
        * The user submitted photos but an error occurred while verifying them.
        * The user submitted photos but the verification was denied.

    In the last two cases, we rely on messages in the sidebar rather than displaying
    messages for each course.

    Arguments:
        user (User): The currently logged-in user.
        course_enrollment_pairs (list): The courses the user is enrolled in.
            The list should contain tuples of `(Course, CourseEnrollment)`.
        all_course_modes (list): List of all course modes for the student's enrolled courses,
            including modes that have expired.

    Returns:
        dict: Mapping of course keys verification status dictionaries.
            If no verification status is applicable to a course, it will not
            be included in the dictionary.
            The dictionaries have these keys:
                * status (str): One of the enumerated status codes.
                * days_until_deadline (int): Number of days until the verification deadline.
                * verification_good_until (str): Date string for the verification expiration date.
    """

    status_by_course = {}

    # Retrieve all verifications for the user, sorted in descending
    # order by submission datetime
    verifications = SoftwareSecurePhotoVerification.objects.filter(user=user)

    for course, enrollment in course_enrollment_pairs:

        # Get the verified mode (if any) for this course
        # We pass in the course modes we have already loaded to avoid
        # another database hit, as well as to ensure that expired
        # course modes are included in the search.
        verified_mode = CourseMode.verified_mode_for_course(
            course.id, modes=all_course_modes[course.id])

        # If no verified mode has ever been offered, or the user hasn't enrolled
        # as verified, then the course won't display state related to its
        # verification status.
        if verified_mode is not None and enrollment.mode in CourseMode.VERIFIED_MODES:
            deadline = verified_mode.expiration_datetime
            relevant_verification = SoftwareSecurePhotoVerification.verification_for_datetime(
                deadline, verifications)

            # By default, don't show any status related to verification
            status = None

            # Check whether the user was approved or is awaiting approval
            if relevant_verification is not None:
                if relevant_verification.status == "approved":
                    status = VERIFY_STATUS_APPROVED
                elif relevant_verification.status == "submitted":
                    status = VERIFY_STATUS_SUBMITTED

            # If the user didn't submit at all, then tell them they need to verify
            # If the deadline has already passed, then tell them they missed it.
            # If they submitted but something went wrong (error or denied),
            # then don't show any messaging next to the course, since we already
            # show messages related to this on the left sidebar.
            submitted = (relevant_verification is not None
                         and relevant_verification.status
                         not in ["created", "ready"])
            if status is None and not submitted:
                if deadline is None or deadline > datetime.now(UTC):
                    status = VERIFY_STATUS_NEED_TO_VERIFY
                else:
                    status = VERIFY_STATUS_MISSED_DEADLINE

            # Set the status for the course only if we're displaying some kind of message
            # Otherwise, leave the course out of the dictionary.
            if status is not None:
                days_until_deadline = None
                verification_good_until = None

                now = datetime.now(UTC)
                if deadline is not None and deadline > now:
                    days_until_deadline = (deadline - now).days

                if relevant_verification is not None:
                    verification_good_until = relevant_verification.expiration_datetime.strftime(
                        "%m/%d/%Y")

                status_by_course[course.id] = {
                    'status': status,
                    'days_until_deadline': days_until_deadline,
                    'verification_good_until': verification_good_until
                }

    return status_by_course
예제 #52
0
    def add_cert(self, student, course_id, course=None):
        """

        Arguments:
          student - User.object
          course_id - courseenrollment.course_id (string)

        Request a new certificate for a student.
        Will change the certificate status to 'generating'.

        Certificate must be in the 'unavailable', 'error',
        'deleted' or 'generating' state.

        If a student has a passing grade or is in the whitelist
        table for the course a request will made for a new cert.

        If a student has allow_certificate set to False in the
        userprofile table the status will change to 'restricted'

        If a student does not have a passing grade the status
        will change to status.notpassing

        Returns the student's status

        """

        VALID_STATUSES = [status.generating,
                          status.unavailable,
                          status.deleted,
                          status.error,
                          status.notpassing]

        cert_status = certificate_status_for_student(student, course_id)['status']

        new_status = cert_status

        if cert_status in VALID_STATUSES:
            # grade the student

            # re-use the course passed in optionally so we don't have to re-fetch everything
            # for every student
            if course is None:
                course = courses.get_course_by_id(course_id)
            profile = UserProfile.objects.get(user=student)

            # Needed
            self.request.user = student
            self.request.session = {}

            grade = grades.grade(student, self.request, course)
            is_whitelisted = self.whitelist.filter(
                user=student, course_id=course_id, whitelist=True).exists()
            enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_id)
            mode_is_verified = (enrollment_mode == GeneratedCertificate.MODES.verified)
            user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student)
            user_is_reverified = SoftwareSecurePhotoVerification.user_is_reverified_for_all(course_id, student)
            org = course_id.split('/')[0]
            course_num = course_id.split('/')[1]
            cert_mode = enrollment_mode
            if (mode_is_verified and user_is_verified and user_is_reverified):
                template_pdf = "certificate-template-{0}-{1}-verified.pdf".format(
                    org, course_num)
            elif (mode_is_verified and not (user_is_verified and user_is_reverified)):
                template_pdf = "certificate-template-{0}-{1}.pdf".format(
                    org, course_num)
                cert_mode = GeneratedCertificate.MODES.honor
            else:
                # honor code and audit students
                template_pdf = "certificate-template-{0}-{1}.pdf".format(
                    org, course_num)

            cert, created = GeneratedCertificate.objects.get_or_create(
                user=student, course_id=course_id)

            cert.mode = cert_mode
            cert.user = student
            cert.grade = grade['percent']
            cert.course_id = course_id
            cert.name = profile.name

            if is_whitelisted or grade['grade'] is not None:

                # check to see whether the student is on the
                # the embargoed country restricted list
                # otherwise, put a new certificate request
                # on the queue

                if self.restricted.filter(user=student).exists():
                    new_status = status.restricted
                    cert.status = new_status
                    cert.save()
                else:
                    key = make_hashkey(random.random())
                    cert.key = key
                    contents = {
                        'action': 'create',
                        'username': student.username,
                        'course_id': course_id,
                        'name': profile.name,
                        'grade': grade['grade'],
                        'template_pdf': template_pdf,
                    }
                    new_status = status.generating
                    cert.status = new_status
                    cert.save()
                    self._send_to_xqueue(contents, key)
            else:
                new_status = status.notpassing
                cert.status = new_status
                cert.save()

        return new_status
예제 #53
0
    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)
예제 #54
0
    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()
예제 #55
0
    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))
예제 #56
0
    def test_user_is_verified(self):
        """
        Test to make sure we correctly answer whether a user has been verified.
        """
        user = UserFactory.create()
        attempt = SoftwareSecurePhotoVerification(user=user)
        attempt.save()

        # If it's any of these, they're not verified...
        for status in [
                "created", "ready", "denied", "submitted", "must_retry"
        ]:
            attempt.status = status
            attempt.save()
            assert_false(
                SoftwareSecurePhotoVerification.user_is_verified(user), status)

        attempt.status = "approved"
        attempt.save()
        assert_true(SoftwareSecurePhotoVerification.user_is_verified(user),
                    status)
예제 #57
0
    def test_user_status(self):
        # test for correct status when no error returned
        user = UserFactory.create()
        status = SoftwareSecurePhotoVerification.user_status(user)
        self.assertEquals(status, ('none', ''))

        # test for when one has been created
        attempt = SoftwareSecurePhotoVerification(user=user)
        attempt.status = 'approved'
        attempt.save()

        status = SoftwareSecurePhotoVerification.user_status(user)
        self.assertEquals(status, ('approved', ''))

        # create another one for the same user, make sure the right one is
        # returned
        attempt2 = SoftwareSecurePhotoVerification(user=user)
        attempt2.status = 'denied'
        attempt2.error_msg = '[{"photoIdReasons": ["Not provided"]}]'
        attempt2.save()

        status = SoftwareSecurePhotoVerification.user_status(user)
        self.assertEquals(status, ('approved', ''))

        # now delete the first one and verify that the denial is being handled
        # properly
        attempt.delete()
        status = SoftwareSecurePhotoVerification.user_status(user)
        self.assertEquals(status,
                          ('must_reverify', "No photo ID was provided."))

        # test for correct status for reverifications
        window = MidcourseReverificationWindowFactory()
        reverify_status = SoftwareSecurePhotoVerification.user_status(
            user=user, window=window)
        self.assertEquals(reverify_status, ('must_reverify', ''))

        reverify_attempt = SoftwareSecurePhotoVerification(user=user,
                                                           window=window)
        reverify_attempt.status = 'approved'
        reverify_attempt.save()

        reverify_status = SoftwareSecurePhotoVerification.user_status(
            user=user, window=window)
        self.assertEquals(reverify_status, ('approved', ''))

        reverify_attempt.status = 'denied'
        reverify_attempt.save()

        reverify_status = SoftwareSecurePhotoVerification.user_status(
            user=user, window=window)
        self.assertEquals(reverify_status, ('denied', ''))

        reverify_attempt.status = 'approved'
        # pylint: disable=protected-access
        reverify_attempt.created_at = SoftwareSecurePhotoVerification._earliest_allowed_date(
        ) + timedelta(days=-1)
        reverify_attempt.save()
        reverify_status = SoftwareSecurePhotoVerification.user_status(
            user=user, window=window)
        message = 'Your {platform_name} verification has expired.'.format(
            platform_name=settings.PLATFORM_NAME)
        self.assertEquals(reverify_status, ('expired', message))
예제 #58
0
    def test_user_is_reverified_for_all(self):

        # if there are no windows for a course, this should return True
        self.assertTrue(
            SoftwareSecurePhotoVerification.user_is_reverified_for_all(
                self.course.id, self.user))

        # first, make three windows
        window1 = MidcourseReverificationWindowFactory(
            course_id=self.course.id,
            start_date=datetime.now(pytz.UTC) - timedelta(days=15),
            end_date=datetime.now(pytz.UTC) - timedelta(days=13),
        )

        window2 = MidcourseReverificationWindowFactory(
            course_id=self.course.id,
            start_date=datetime.now(pytz.UTC) - timedelta(days=10),
            end_date=datetime.now(pytz.UTC) - timedelta(days=8),
        )

        window3 = MidcourseReverificationWindowFactory(
            course_id=self.course.id,
            start_date=datetime.now(pytz.UTC) - timedelta(days=5),
            end_date=datetime.now(pytz.UTC) - timedelta(days=3),
        )

        # make two SSPMidcourseReverifications for those windows
        attempt1 = SoftwareSecurePhotoVerification(status="approved",
                                                   user=self.user,
                                                   window=window1)
        attempt1.save()

        attempt2 = SoftwareSecurePhotoVerification(status="approved",
                                                   user=self.user,
                                                   window=window2)
        attempt2.save()

        # should return False because only 2 of 3 windows have verifications
        self.assertFalse(
            SoftwareSecurePhotoVerification.user_is_reverified_for_all(
                self.course.id, self.user))

        attempt3 = SoftwareSecurePhotoVerification(status="must_retry",
                                                   user=self.user,
                                                   window=window3)
        attempt3.save()

        # should return False because the last verification exists BUT is not approved
        self.assertFalse(
            SoftwareSecurePhotoVerification.user_is_reverified_for_all(
                self.course.id, self.user))

        attempt3.status = "approved"
        attempt3.save()

        # should now return True because all windows have approved verifications
        self.assertTrue(
            SoftwareSecurePhotoVerification.user_is_reverified_for_all(
                self.course.id, self.user))
예제 #59
0
class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
    """
    Tests for the results_callback view.
    """
    def setUp(self):
        self.course = CourseFactory.create(org='Robot',
                                           number='999',
                                           display_name='Test Course')
        self.course_id = self.course.id
        self.user = UserFactory.create()
        self.attempt = SoftwareSecurePhotoVerification(status="submitted",
                                                       user=self.user)
        self.attempt.save()
        self.receipt_id = self.attempt.receipt_id
        self.client = Client()

    def mocked_has_valid_signature(method, headers_dict, body_dict, access_key,
                                   secret_key):
        return True

    def test_invalid_json(self):
        """
        Test for invalid json being posted by software secure.
        """
        data = {"Testing invalid"}
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB: testing',
            HTTP_DATE='testdate')
        self.assertIn('Invalid JSON', response.content)
        self.assertEqual(response.status_code, 400)

    def test_invalid_dict(self):
        """
        Test for invalid dictionary being posted by software secure.
        """
        data = '"\\"Test\\tTesting"'
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        self.assertIn('JSON should be dict', response.content)
        self.assertEqual(response.status_code, 400)

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_invalid_access_key(self):
        """
        Test for invalid access key.
        """
        data = {
            "EdX-ID": self.receipt_id,
            "Result": "Testing",
            "Reason": "Testing",
            "MessageType": "Testing"
        }
        json_data = json.dumps(data)
        response = self.client.post(reverse('verify_student_results_callback'),
                                    data=json_data,
                                    content_type='application/json',
                                    HTTP_AUTHORIZATION='test testing:testing',
                                    HTTP_DATE='testdate')
        self.assertIn('Access key invalid', response.content)
        self.assertEqual(response.status_code, 400)

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_wrong_edx_id(self):
        """
        Test for wrong id of Software secure verification attempt.
        """
        data = {
            "EdX-ID": "Invalid-Id",
            "Result": "Testing",
            "Reason": "Testing",
            "MessageType": "Testing"
        }
        json_data = json.dumps(data)
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=json_data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        self.assertIn('edX ID Invalid-Id not found', response.content)
        self.assertEqual(response.status_code, 400)

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_pass_result(self):
        """
        Test for verification passed.
        """
        data = {
            "EdX-ID": self.receipt_id,
            "Result": "PASS",
            "Reason": "",
            "MessageType": "You have been verified."
        }
        json_data = json.dumps(data)
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=json_data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        attempt = SoftwareSecurePhotoVerification.objects.get(
            receipt_id=self.receipt_id)
        self.assertEqual(attempt.status, u'approved')
        self.assertEquals(response.content, 'OK!')

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_fail_result(self):
        """
        Test for failed verification.
        """
        data = {
            "EdX-ID": self.receipt_id,
            "Result": 'FAIL',
            "Reason": 'Invalid photo',
            "MessageType": 'Your photo doesn\'t meet standards.'
        }
        json_data = json.dumps(data)
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=json_data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        attempt = SoftwareSecurePhotoVerification.objects.get(
            receipt_id=self.receipt_id)
        self.assertEqual(attempt.status, u'denied')
        self.assertEqual(attempt.error_code,
                         u'Your photo doesn\'t meet standards.')
        self.assertEqual(attempt.error_msg, u'"Invalid photo"')
        self.assertEquals(response.content, 'OK!')

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_system_fail_result(self):
        """
        Test for software secure result system failure.
        """
        data = {
            "EdX-ID": self.receipt_id,
            "Result": 'SYSTEM FAIL',
            "Reason": 'Memory overflow',
            "MessageType": 'You must retry the verification.'
        }
        json_data = json.dumps(data)
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=json_data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        attempt = SoftwareSecurePhotoVerification.objects.get(
            receipt_id=self.receipt_id)
        self.assertEqual(attempt.status, u'must_retry')
        self.assertEqual(attempt.error_code,
                         u'You must retry the verification.')
        self.assertEqual(attempt.error_msg, u'"Memory overflow"')
        self.assertEquals(response.content, 'OK!')

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_unknown_result(self):
        """
        test for unknown software secure result
        """
        data = {
            "EdX-ID": self.receipt_id,
            "Result": 'Unknown',
            "Reason": 'Unknown reason',
            "MessageType": 'Unknown message'
        }
        json_data = json.dumps(data)
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=json_data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        self.assertIn('Result Unknown not understood', response.content)

    @mock.patch('verify_student.ssencrypt.has_valid_signature',
                mock.Mock(side_effect=mocked_has_valid_signature))
    def test_reverification(self):
        """
         Test software secure result for reverification window.
        """
        data = {
            "EdX-ID": self.receipt_id,
            "Result": "PASS",
            "Reason": "",
            "MessageType": "You have been verified."
        }
        window = MidcourseReverificationWindowFactory(course_id=self.course_id)
        self.attempt.window = window
        self.attempt.save()
        json_data = json.dumps(data)
        self.assertEqual(
            CourseEnrollment.objects.filter(course_id=self.course_id).count(),
            0)
        response = self.client.post(
            reverse('verify_student_results_callback'),
            data=json_data,
            content_type='application/json',
            HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
            HTTP_DATE='testdate')
        self.assertEquals(response.content, 'OK!')
        self.assertIsNotNone(
            CourseEnrollment.objects.get(course_id=self.course_id))