Example #1
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)
Example #2
0
    def test_add_verification_status(self, status):
        """adding verfication status using the class method."""

        # adding verification status
        VerificationStatus.add_verification_status(checkpoint=self.check_point1, user=self.user, status=status)

        # getting the status from db
        result = VerificationStatus.objects.filter(checkpoint=self.check_point1)[0]
        self.assertEqual(result.status, status)
        self.assertEqual(result.user, self.user)
Example #3
0
    def test_get_user_attempts(self):
        """
        Test adding verification status.
        """
        VerificationStatus.add_verification_status(checkpoint=self.first_checkpoint, user=self.user, status="submitted")

        actual_attempts = VerificationStatus.get_user_attempts(
            self.user.id, self.course.id, self.first_checkpoint_location
        )
        self.assertEqual(actual_attempts, 1)
Example #4
0
    def test_add_verification_status(self, status):
        """ Adding verification status using the class method. """

        # adding verification status
        VerificationStatus.add_verification_status(checkpoint=self.first_checkpoint, user=self.user, status=status)

        # test the status from database
        result = VerificationStatus.objects.filter(checkpoint=self.first_checkpoint)[0]
        self.assertEqual(result.status, status)
        self.assertEqual(result.user, self.user)
Example #5
0
    def test_add_verification_status(self, status):
        """adding verfication status using the class method."""

        # adding verification status
        VerificationStatus.add_verification_status(checkpoint=self.check_point1, user=self.user, status=status)

        # getting the status from db
        result = VerificationStatus.objects.filter(checkpoint=self.check_point1)[0]
        self.assertEqual(result.status, status)
        self.assertEqual(result.user, self.user)
Example #6
0
    def test_get_user_attempts(self):
        """
        Test adding verification status.
        """
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint,
            user=self.user,
            status='submitted')

        actual_attempts = VerificationStatus.get_user_attempts(
            self.user.id, self.course.id, self.first_checkpoint_location)
        self.assertEqual(actual_attempts, 1)
Example #7
0
    def test_add_verification_status(self, status):
        """ Adding verification status using the class method. """

        # adding verification status
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint, user=self.user, status=status)

        # test the status from database
        result = VerificationStatus.objects.filter(
            checkpoint=self.first_checkpoint)[0]
        self.assertEqual(result.status, status)
        self.assertEqual(result.user, self.user)
Example #8
0
    def test_add_status_from_checkpoints(self, status):
        """adding verfication status for checkpoints list."""

        # adding verification status with multiple points
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=[self.check_point1, self.check_point2], user=self.user, status=status
        )

        # getting the status from db.
        result = VerificationStatus.objects.filter(user=self.user)
        self.assertEqual(len(result), len([self.check_point1.checkpoint_name, self.check_point2.checkpoint_name]))
        self.assertEqual(result[0].checkpoint.checkpoint_name, self.check_point1.checkpoint_name)
        self.assertEqual(result[1].checkpoint.checkpoint_name, self.check_point2.checkpoint_name)
Example #9
0
    def test_add_status_from_checkpoints(self, status):
        """adding verfication status for checkpoints list."""

        # adding verification status with multiple points
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=[self.check_point1, self.check_point2], user=self.user, status=status
        )

        # getting the status from db.
        result = VerificationStatus.objects.filter(user=self.user)
        self.assertEqual(len(result), len([self.check_point1.checkpoint_name, self.check_point2.checkpoint_name]))
        self.assertEqual(result[0].checkpoint.checkpoint_name, self.check_point1.checkpoint_name)
        self.assertEqual(result[1].checkpoint.checkpoint_name, self.check_point2.checkpoint_name)
Example #10
0
    def test_get_location_id(self):
        """
        Getting location id for a specific checkpoint.
        """

        # creating software secure attempt against checkpoint
        self.first_checkpoint.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))

        # add initial verification status for checkpoint
        VerificationStatus.add_verification_status(checkpoint=self.first_checkpoint, user=self.user, status="submitted")
        attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)

        self.assertIsNotNone(VerificationStatus.get_location_id(attempt))
        self.assertEqual(VerificationStatus.get_location_id(None), "")
Example #11
0
    def test_get_user_attempts(self):
        """
        Test adding verification status.
        """
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint,
            user=self.user,
            status='submitted')

        self.assertEqual(
            VerificationStatus.get_user_attempts(
                user_id=self.user.id,
                course_key=self.course.id,
                related_assessment_location=self.first_checkpoint_location), 1)
Example #12
0
    def test_get_user_attempts(self):

        # adding verification status
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point1,
            user=self.user,
            status='submitted',
            location_id=self.dummy_reverification_item_id_1
        )

        self.assertEqual(VerificationStatus.get_user_attempts(
            course_key=self.course.id,
            user_id=self.user.id,
            related_assessment='midterm', location_id=self.dummy_reverification_item_id_1), 1)
    def test_get_user_attempts(self):

        # adding verification status
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point1,
            user=self.user,
            status='submitted',
            location_id=self.dummy_reverification_item_id_1
        )

        self.assertEqual(VerificationStatus.get_user_attempts(
            course_key=self.course.id,
            user_id=self.user.id,
            related_assessment='midterm', location_id=self.dummy_reverification_item_id_1), 1)
Example #14
0
    def test_get_user_attempts(self):
        """
        Test adding verification status.
        """
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint,
            user=self.user,
            status='submitted'
        )

        self.assertEqual(
            VerificationStatus.get_user_attempts(
                user_id=self.user.id,
                course_key=self.course.id,
                related_assessment_location=self.first_checkpoint_location
            ),
            1
        )
Example #15
0
    def test_get_location_id(self):
        """
        Getting location id for a specific checkpoint.
        """

        # creating software secure attempt against checkpoint
        self.first_checkpoint.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))

        # add initial verification status for checkpoint
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint,
            user=self.user,
            status='submitted',
        )
        attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)

        self.assertIsNotNone(VerificationStatus.get_location_id(attempt))
        self.assertEqual(VerificationStatus.get_location_id(None), '')
Example #16
0
    def test_get_location_id(self):
        """ Getting location id for a specific checkpoint  """

        # creating software secure attempt against checkpoint
        self.check_point1.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))

        # add initial verification status for checkpoint
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point1,
            user=self.user,
            status='submitted',
            location_id=self.dummy_reverification_item_id_1
        )

        attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)

        self.assertIsNotNone(VerificationStatus.get_location_id(attempt))
        self.assertEqual(VerificationStatus.get_location_id(None), '')
Example #17
0
    def test_add_status_from_checkpoints(self, status):
        """Test verification status for reverification checkpoints after
        submitting software secure photo verification.
        """

        # add initial verification status for checkpoints
        initial_status = "submitted"
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint, user=self.user, status=initial_status
        )
        VerificationStatus.add_verification_status(
            checkpoint=self.second_checkpoint, user=self.user, status=initial_status
        )

        # now add verification status for multiple checkpoint points
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=[self.first_checkpoint, self.second_checkpoint], user=self.user, status=status
        )

        # test that verification status entries with new status have been added
        # for both checkpoints
        result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.first_checkpoint)
        self.assertEqual(len(result), len(self.first_checkpoint.checkpoint_status.all()))
        self.assertEqual(
            list(result.values_list("checkpoint__checkpoint_location", flat=True)),
            list(self.first_checkpoint.checkpoint_status.values_list("checkpoint__checkpoint_location", flat=True)),
        )

        result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.second_checkpoint)
        self.assertEqual(len(result), len(self.second_checkpoint.checkpoint_status.all()))
        self.assertEqual(
            list(result.values_list("checkpoint__checkpoint_location", flat=True)),
            list(self.second_checkpoint.checkpoint_status.values_list("checkpoint__checkpoint_location", flat=True)),
        )
    def handle(self, *args, **kwargs):  # pylint: disable=unused-argument
        from verify_student.views import _set_user_requirement_status

        status_to_set = args[0]
        receipt_id = args[1]

        try:
            attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
        except SoftwareSecurePhotoVerification.DoesNotExist:
            self.stderr.write(
                'SoftwareSecurePhotoVerification with id {id} could not be found.\n'.format(id=receipt_id)
            )
            sys.exit(1)

        if status_to_set == 'approved':
            self.stdout.write('Approving verification for {id}.\n'.format(id=receipt_id))
            attempt.approve()
            _set_user_requirement_status(attempt, 'reverification', 'satisfied')

        elif status_to_set == 'denied':
            self.stdout.write('Denying verification for {id}.\n'.format(id=receipt_id))
            if len(args) >= 3:
                reason_for_denial = args[2]
            else:
                reason_for_denial = 'Denied via management command.'
            attempt.deny(reason_for_denial)
            _set_user_requirement_status(attempt, 'reverification', 'failed', reason_for_denial)

        else:
            self.stdout.write('Cannot set id {id} to unrecognized status {status}'.format(
                id=receipt_id, status=status_to_set
            ))
            sys.exit(1)

        checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=checkpoints,
            user=attempt.user,
            status=status_to_set
        )
Example #19
0
    def get_attempts(self, user_id, course_id, related_assessment_location):
        """Get re-verification attempts against a user for a given 'checkpoint'
        and 'course_id'.

        Args:
            user_id(str): User Id string
            course_id(str): A string of course id
            related_assessment_location(str): Location of Reverification XBlock

        Returns:
            Number of re-verification attempts of a user
        """
        course_key = CourseKey.from_string(course_id)
        return VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location)
Example #20
0
    def test_add_status_from_checkpoints(self, status):
        """Test verification status for reverification checkpoints after
        submitting software secure photo verification.
        """

        # add initial verification status for checkpoints
        initial_status = "submitted"
        VerificationStatus.add_verification_status(
            checkpoint=self.first_checkpoint,
            user=self.user,
            status=initial_status)
        VerificationStatus.add_verification_status(
            checkpoint=self.second_checkpoint,
            user=self.user,
            status=initial_status)

        # now add verification status for multiple checkpoint points
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=[self.first_checkpoint, self.second_checkpoint],
            user=self.user,
            status=status)

        # test that verification status entries with new status have been added
        # for both checkpoints
        result = VerificationStatus.objects.filter(
            user=self.user, checkpoint=self.first_checkpoint)
        self.assertEqual(len(result),
                         len(self.first_checkpoint.checkpoint_status.all()))
        self.assertEqual(
            list(
                result.values_list('checkpoint__checkpoint_location',
                                   flat=True)),
            list(
                self.first_checkpoint.checkpoint_status.values_list(
                    'checkpoint__checkpoint_location', flat=True)))

        result = VerificationStatus.objects.filter(
            user=self.user, checkpoint=self.second_checkpoint)
        self.assertEqual(len(result),
                         len(self.second_checkpoint.checkpoint_status.all()))
        self.assertEqual(
            list(
                result.values_list('checkpoint__checkpoint_location',
                                   flat=True)),
            list(
                self.second_checkpoint.checkpoint_status.values_list(
                    'checkpoint__checkpoint_location', flat=True)))
Example #21
0
    def test_add_status_from_checkpoints(self, status):
        """ Adding verification status for checkpoints list after submitting sspv. """

        # add initial verification status for checkpoints
        initial_status = "submitted"
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point1,
            user=self.user,
            status=initial_status,
            location_id=self.dummy_reverification_item_id_1)
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point2,
            user=self.user,
            status=initial_status,
            location_id=self.dummy_reverification_item_id_2)

        # now add verification status for multiple checkpoint points
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=[self.check_point1, self.check_point2],
            user=self.user,
            status=status)

        # test that verification status entries with new status have been added
        # for both checkpoints and all entries have related 'location_id'.
        result = VerificationStatus.objects.filter(
            user=self.user, checkpoint=self.check_point1)
        self.assertEqual(len(result),
                         len(self.check_point1.checkpoint_status.all()))
        self.assertEqual(
            list(result.values_list('location_id', flat=True)),
            list(self.check_point1.checkpoint_status.all().values_list(
                'location_id', flat=True)))
        result = VerificationStatus.objects.filter(
            user=self.user, checkpoint=self.check_point2)
        self.assertEqual(len(result),
                         len(self.check_point2.checkpoint_status.all()))
        self.assertEqual(
            list(result.values_list('location_id', flat=True)),
            list(self.check_point2.checkpoint_status.all().values_list(
                'location_id', flat=True)))
Example #22
0
    def test_add_status_from_checkpoints(self, status):
        """ Adding verification status for checkpoints list after submitting sspv. """

        # add initial verification status for checkpoints
        initial_status = "submitted"
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point1,
            user=self.user,
            status=initial_status,
            location_id=self.dummy_reverification_item_id_1
        )
        VerificationStatus.add_verification_status(
            checkpoint=self.check_point2,
            user=self.user,
            status=initial_status,
            location_id=self.dummy_reverification_item_id_2
        )

        # now add verification status for multiple checkpoint points
        VerificationStatus.add_status_from_checkpoints(
            checkpoints=[self.check_point1, self.check_point2], user=self.user, status=status
        )

        # test that verification status entries with new status have been added
        # for both checkpoints and all entries have related 'location_id'.
        result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.check_point1)
        self.assertEqual(len(result), len(self.check_point1.checkpoint_status.all()))
        self.assertEqual(
            list(result.values_list('location_id', flat=True)),
            list(self.check_point1.checkpoint_status.all().values_list('location_id', flat=True))
        )
        result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.check_point2)
        self.assertEqual(len(result), len(self.check_point2.checkpoint_status.all()))
        self.assertEqual(
            list(result.values_list('location_id', flat=True)),
            list(self.check_point2.checkpoint_status.all().values_list('location_id', flat=True))
        )
Example #23
0
def results_callback(request):
    """
    Software Secure will call this callback to tell us whether a user is
    verified to be who they said they are.
    """
    body = request.body

    try:
        body_dict = json.loads(body)
    except ValueError:
        log.exception("Invalid JSON received from Software Secure:\n\n{}\n".format(body))
        return HttpResponseBadRequest("Invalid JSON. Received:\n\n{}".format(body))

    if not isinstance(body_dict, dict):
        log.error("Reply from Software Secure is not a dict:\n\n{}\n".format(body))
        return HttpResponseBadRequest("JSON should be dict. Received:\n\n{}".format(body))

    headers = {
        "Authorization": request.META.get("HTTP_AUTHORIZATION", ""),
        "Date": request.META.get("HTTP_DATE", "")
    }

    sig_valid = ssencrypt.has_valid_signature(
        "POST",
        headers,
        body_dict,
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"],
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_SECRET_KEY"]
    )

    _response, access_key_and_sig = headers["Authorization"].split(" ")
    access_key = access_key_and_sig.split(":")[0]

    # This is what we should be doing...
    #if not sig_valid:
    #    return HttpResponseBadRequest("Signature is invalid")

    # This is what we're doing until we can figure out why we disagree on sigs
    if access_key != settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"]:
        return HttpResponseBadRequest("Access key invalid")

    receipt_id = body_dict.get("EdX-ID")
    result = body_dict.get("Result")
    reason = body_dict.get("Reason", "")
    error_code = body_dict.get("MessageType", "")

    try:
        attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
    except SoftwareSecurePhotoVerification.DoesNotExist:
        log.error("Software Secure posted back for receipt_id {}, but not found".format(receipt_id))
        return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))

    checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()

    if result == "PASS":
        log.debug("Approving verification for {}".format(receipt_id))
        attempt.approve()
        status = "approved"
    elif result == "FAIL":
        log.debug("Denying verification for {}".format(receipt_id))
        attempt.deny(json.dumps(reason), error_code=error_code)
        status = "denied"
    elif result == "SYSTEM FAIL":
        log.debug("System failure for {} -- resetting to must_retry".format(receipt_id))
        attempt.system_error(json.dumps(reason), error_code=error_code)
        status = "error"
        log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
    else:
        log.error("Software Secure returned unknown result {}".format(result))
        return HttpResponseBadRequest(
            "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
        )

    # If this is a reverification, log an event
    if attempt.window:
        course_id = attempt.window.course_id
        course_enrollment = CourseEnrollment.get_or_create_enrollment(attempt.user, course_id)
        course_enrollment.emit_event(EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE)
    VerificationStatus.add_status_from_checkpoints(checkpoints=checkpoints, user=attempt.user, status=status)
    return HttpResponse("OK!")
Example #24
0
def results_callback(request):
    """
    Software Secure will call this callback to tell us whether a user is
    verified to be who they said they are.
    """
    body = request.body

    try:
        body_dict = json.loads(body)
    except ValueError:
        log.exception("Invalid JSON received from Software Secure:\n\n{}\n".format(body))
        return HttpResponseBadRequest("Invalid JSON. Received:\n\n{}".format(body))

    if not isinstance(body_dict, dict):
        log.error("Reply from Software Secure is not a dict:\n\n{}\n".format(body))
        return HttpResponseBadRequest("JSON should be dict. Received:\n\n{}".format(body))

    headers = {
        "Authorization": request.META.get("HTTP_AUTHORIZATION", ""),
        "Date": request.META.get("HTTP_DATE", "")
    }

    sig_valid = ssencrypt.has_valid_signature(
        "POST",
        headers,
        body_dict,
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"],
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_SECRET_KEY"]
    )

    _response, access_key_and_sig = headers["Authorization"].split(" ")
    access_key = access_key_and_sig.split(":")[0]

    # This is what we should be doing...
    #if not sig_valid:
    #    return HttpResponseBadRequest("Signature is invalid")

    # This is what we're doing until we can figure out why we disagree on sigs
    if access_key != settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"]:
        return HttpResponseBadRequest("Access key invalid")

    receipt_id = body_dict.get("EdX-ID")
    result = body_dict.get("Result")
    reason = body_dict.get("Reason", "")
    error_code = body_dict.get("MessageType", "")

    try:
        attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
    except SoftwareSecurePhotoVerification.DoesNotExist:
        log.error("Software Secure posted back for receipt_id {}, but not found".format(receipt_id))
        return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))

    checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()

    if result == "PASS":
        log.debug("Approving verification for {}".format(receipt_id))
        attempt.approve()
        status = "approved"
    elif result == "FAIL":
        log.debug("Denying verification for {}".format(receipt_id))
        attempt.deny(json.dumps(reason), error_code=error_code)
        status = "denied"
    elif result == "SYSTEM FAIL":
        log.debug("System failure for {} -- resetting to must_retry".format(receipt_id))
        attempt.system_error(json.dumps(reason), error_code=error_code)
        status = "error"
        log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
    else:
        log.error("Software Secure returned unknown result {}".format(result))
        return HttpResponseBadRequest(
            "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
        )

    # If this is a reverification, log an event
    if attempt.window:
        course_id = attempt.window.course_id
        course_enrollment = CourseEnrollment.get_or_create_enrollment(attempt.user, course_id)
        course_enrollment.emit_event(EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE)
    VerificationStatus.add_status_from_checkpoints(checkpoints=checkpoints, user=attempt.user, status=status)
    return HttpResponse("OK!")
Example #25
0
def results_callback(request):
    """
    Software Secure will call this callback to tell us whether a user is
    verified to be who they said they are.
    """
    body = request.body

    try:
        body_dict = json.loads(body)
    except ValueError:
        log.exception("Invalid JSON received from Software Secure:\n\n{}\n".format(body))
        return HttpResponseBadRequest("Invalid JSON. Received:\n\n{}".format(body))

    if not isinstance(body_dict, dict):
        log.error("Reply from Software Secure is not a dict:\n\n{}\n".format(body))
        return HttpResponseBadRequest("JSON should be dict. Received:\n\n{}".format(body))

    headers = {
        "Authorization": request.META.get("HTTP_AUTHORIZATION", ""),
        "Date": request.META.get("HTTP_DATE", "")
    }

    has_valid_signature(
        "POST",
        headers,
        body_dict,
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"],
        settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_SECRET_KEY"]
    )

    _response, access_key_and_sig = headers["Authorization"].split(" ")
    access_key = access_key_and_sig.split(":")[0]

    # This is what we should be doing...
    #if not sig_valid:
    #    return HttpResponseBadRequest("Signature is invalid")

    # This is what we're doing until we can figure out why we disagree on sigs
    if access_key != settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_ACCESS_KEY"]:
        return HttpResponseBadRequest("Access key invalid")

    receipt_id = body_dict.get("EdX-ID")
    result = body_dict.get("Result")
    reason = body_dict.get("Reason", "")
    error_code = body_dict.get("MessageType", "")

    try:
        attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=receipt_id)
    except SoftwareSecurePhotoVerification.DoesNotExist:
        log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id)
        return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
    if result == "PASS":
        log.debug("Approving verification for %s", receipt_id)
        attempt.approve()
        status = "approved"
        _set_user_requirement_status(attempt, 'reverification', 'satisfied')

    elif result == "FAIL":
        log.debug("Denying verification for %s", receipt_id)
        attempt.deny(json.dumps(reason), error_code=error_code)
        status = "denied"
        _set_user_requirement_status(
            attempt, 'reverification', 'failed', json.dumps(reason)
        )
    elif result == "SYSTEM FAIL":
        log.debug("System failure for %s -- resetting to must_retry", receipt_id)
        attempt.system_error(json.dumps(reason), error_code=error_code)
        status = "error"
        log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
    else:
        log.error("Software Secure returned unknown result %s", result)
        return HttpResponseBadRequest(
            "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
        )
    incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
    if incourse_reverify_enabled:
        checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
        VerificationStatus.add_status_from_checkpoints(checkpoints=checkpoints, user=attempt.user, status=status)
        # If this is re-verification then send the update email
        if checkpoints:
            user_id = attempt.user.id
            course_key = checkpoints[0].course_id
            related_assessment_location = checkpoints[0].checkpoint_location

            subject, message = _compose_message_reverification_email(
                course_key, user_id, related_assessment_location, status, request
            )

            _send_email(user_id, subject, message)
    return HttpResponse("OK!")
Example #26
0
def _compose_message_reverification_email(
        course_key, user_id, related_assessment_location, status, request
):  # pylint: disable=invalid-name
    """
    Compose subject and message for photo reverification email.

    Args:
        course_key(CourseKey): CourseKey object
        user_id(str): User Id
        related_assessment_location(str): Location of reverification XBlock
        photo_verification(QuerySet): Queryset of SoftwareSecure objects
        status(str): Approval status
        is_secure(Bool): Is running on secure protocol or not

    Returns:
        None if any error occurred else Tuple of subject and message strings
    """
    try:
        usage_key = UsageKey.from_string(related_assessment_location)
        reverification_block = modulestore().get_item(usage_key)

        course = modulestore().get_course(course_key)
        redirect_url = get_redirect_url(course_key, usage_key.replace(course_key=course_key))

        subject = "Re-verification Status"
        context = {
            "status": status,
            "course_name": course.display_name_with_default,
            "assessment": reverification_block.related_assessment
        }

        # Allowed attempts is 1 if not set on verification block
        allowed_attempts = reverification_block.attempts + 1
        used_attempts = VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location)
        left_attempts = allowed_attempts - used_attempts
        is_attempt_allowed = left_attempts > 0
        verification_open = True
        if reverification_block.due:
            verification_open = timezone.now() <= reverification_block.due

        context["left_attempts"] = left_attempts
        context["is_attempt_allowed"] = is_attempt_allowed
        context["verification_open"] = verification_open
        context["due_date"] = get_default_time_display(reverification_block.due)

        context['platform_name'] = settings.PLATFORM_NAME
        context["used_attempts"] = used_attempts
        context["allowed_attempts"] = allowed_attempts
        context["support_link"] = microsite.get_value('email_from_address', settings.CONTACT_EMAIL)

        re_verification_link = reverse(
            'verify_student_incourse_reverify',
            args=(
                unicode(course_key),
                related_assessment_location
            )
        )

        context["course_link"] = request.build_absolute_uri(redirect_url)
        context["reverify_link"] = request.build_absolute_uri(re_verification_link)

        message = render_to_string('emails/reverification_processed.txt', context)
        log.info(
            "Sending email to User_Id=%s. Attempts left for this user are %s. "
            "Allowed attempts %s. "
            "Due Date %s",
            str(user_id), left_attempts, allowed_attempts, str(reverification_block.due)
        )
        return subject, message
    # Catch all exception to avoid raising back to view
    except:  # pylint: disable=bare-except
        log.exception("The email for re-verification sending failed for user_id %s", user_id)
Example #27
0
    def post(self, request, course_id, usage_id):
        """Submits the re-verification attempt to SoftwareSecure.

        Args:
            request(HttpRequest): HttpRequest object
            course_id(str): Course Id
            usage_id(str): Location of Reverification XBlock in courseware

        Returns:
            HttpResponse with status_code 400 if photo is missing or any error
            or redirect to the verification flow if initial verification
            doesn't exist otherwise HttpResponse 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
        try:
            course_key = CourseKey.from_string(course_id)
            usage_key = UsageKey.from_string(usage_id).replace(course_key=course_key)
        except InvalidKeyError:
            raise Http404(u"Invalid course_key or usage_key")

        course = modulestore().get_course(course_key)
        if course is None:
            log.error(u"Invalid course id '%s'", course_id)
            return HttpResponseBadRequest(_("Invalid course location."))

        checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, usage_id)
        if checkpoint is None:
            log.error(
                u"Checkpoint is not defined. Could not submit verification attempt"
                u" for user '%s', course '%s' and checkpoint location '%s'.",
                request.user.id, course_key, usage_id
            )
            return HttpResponseBadRequest(_("Invalid checkpoint location."))

        init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
        if not init_verification:
            return self._redirect_no_initial_verification(user, 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(
                'edx.bi.reverify.submitted',
                user.id, course_id, checkpoint.checkpoint_name
            )

            redirect_url = get_redirect_url(course_key, usage_key)
            response = JsonResponse({'url': redirect_url})

        except (ItemNotFoundError, NoPathToItem):
            log.warning(u"Could not find redirect URL for location %s in course %s", course_key, usage_key)
            redirect_url = reverse("courseware", args=(unicode(course_key),))
            response = JsonResponse({'url': redirect_url})

        except Http404 as expt:
            log.exception("Invalid location during photo verification.")
            response = HttpResponseBadRequest(expt.message)

        except IndexError:
            log.exception("Invalid image data during photo verification.")
            response = HttpResponseBadRequest(_("Invalid image data during photo verification."))

        except Exception:  # pylint: disable=broad-except
            log.exception("Could not submit verification attempt for user %s.", request.user.id)
            msg = _("Could not submit photos")
            response = HttpResponseBadRequest(msg)

        return response