Exemplo n.º 1
0
def generate_certificate(self, **kwargs):
    """
    Generates a certificate for a single user.

    kwargs:
        - student: The student for whom to generate a certificate.
        - course_key: The course key for the course that the student is
            receiving a certificate in.
        - expected_verification_status: The expected verification status
            for the user.  When the status has changed, we double check
            that the actual verification status is as expected before
            generating a certificate, in the off chance that the database
            has not yet updated with the user's new verification status.
    """
    original_kwargs = kwargs.copy()
    student = User.objects.get(id=kwargs.pop('student'))
    course_key = CourseKey.from_string(kwargs.pop('course_key'))
    expected_verification_status = kwargs.pop('expected_verification_status', None)
    if expected_verification_status:
        actual_verification_status = IDVerificationService.user_status(student)
        actual_verification_status = actual_verification_status['status']
        if expected_verification_status != actual_verification_status:
            logger.warn('Expected verification status {expected} '
                        'differs from actual verification status {actual} '
                        'for user {user} in course {course}'.format(
                            expected=expected_verification_status,
                            actual=actual_verification_status,
                            user=student.id,
                            course=course_key
                        ))
            raise self.retry(kwargs=original_kwargs)
    generate_user_certificates(student=student, course_key=course_key, **kwargs)
Exemplo n.º 2
0
def _listen_for_id_verification_status_changed(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Catches a track change signal, determines user status,
    calls fire_ungenerated_certificate_task for passing grades
    """
    if not auto_certificate_generation_enabled():
        return

    user_enrollments = CourseEnrollment.enrollments_for_user(user=user)

    grade_factory = CourseGradeFactory()
    expected_verification_status = IDVerificationService.user_status(user)
    expected_verification_status = expected_verification_status['status']
    for enrollment in user_enrollments:
        if grade_factory.read(user=user, course=enrollment.course_overview).passed:
            if fire_ungenerated_certificate_task(user, enrollment.course_id, expected_verification_status):
                message = (
                    u'Certificate generation task initiated for {user} : {course} via track change ' +
                    u'with verification status of {status}'
                )
                log.info(message.format(
                    user=user.id,
                    course=enrollment.course_id,
                    status=expected_verification_status
                ))
Exemplo n.º 3
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(IDVerificationService.user_is_verified(user), status)

        attempt.status = "approved"
        attempt.save()
        assert_true(IDVerificationService.user_is_verified(user), attempt.status)
Exemplo n.º 4
0
    def test_search_username_user_no_enrollment(self, mocked_render):
        created_user, expected_user_info = self._construct_user(
            'user_connected', self.org_key_list[0], self.external_user_key)
        self.client.get(self.url, data={'edx_user': created_user.email})
        expected_info = {
            'user': expected_user_info,
            'id_verification': IDVerificationService.user_status(created_user),
        }

        render_call_dict = mocked_render.call_args[0][1]
        assert expected_info == render_call_dict['learner_program_enrollments']
Exemplo n.º 5
0
 def test_get_verify_location_from_string(self):
     """
     Test for the path to the IDV flow with a course key string
     """
     path = IDVerificationService.get_verify_location(
         'course-v1:edX+DemoX+Demo_Course')
     expected_path = '{}/id-verification'.format(
         settings.ACCOUNT_MICROFRONTEND_URL)
     self.assertEqual(
         path,
         expected_path + '?course_id=course-v1%3AedX%2BDemoX%2BDemo_Course')
Exemplo n.º 6
0
 def test_no_verification(self):
     with freeze_time('2014-12-12'):
         status = IDVerificationService.user_status(self.user)
         expected_status = {
             'status': 'none',
             'error': '',
             'should_display': True,
             'verification_expiry': '',
             'status_date': ''
         }
         self.assertDictEqual(status, expected_status)
Exemplo n.º 7
0
 def test_get_verify_location_from_course_id(self):
     """
     Test for the path to the IDV flow with a course ID
     """
     course = CourseFactory.create(org='Robot',
                                   number='999',
                                   display_name='Test Course')
     path = IDVerificationService.get_verify_location(course.id)
     expected_path = '{}/id-verification'.format(
         settings.ACCOUNT_MICROFRONTEND_URL)
     assert path == (expected_path + '?course_id=Robot/999/Test_Course')
    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()
            self.assertFalse(IDVerificationService.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()
            self.assertTrue(IDVerificationService.user_has_valid_or_pending(user), status)
Exemplo n.º 9
0
def checkout_receipt(request):
    """ Receipt view. """

    page_title = _('Receipt')
    is_payment_complete = True
    payment_support_email = configuration_helpers.get_value('payment_support_email', settings.PAYMENT_SUPPORT_EMAIL)
    payment_support_link = HTML(u'<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 = _(u"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 = _(
            u"If your course does not appear on your dashboard, contact {payment_support_link}."
        ).format(payment_support_link=payment_support_link)

    commerce_configuration = CommerceConfiguration.current()
    # user order cache should be cleared when a new order is placed
    # so user can see new order in their order history.
    if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled:
        cache_key = commerce_configuration.CACHE_KEY + '.' + str(request.user.id)
        cache.delete(cache_key)

    context = {
        'page_title': page_title,
        'is_payment_complete': is_payment_complete,
        'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
        'verified': IDVerificationService.user_has_valid_or_pending(request.user),
        '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,
        'is_request_in_themed_site': is_request_in_themed_site()
    }
    return render_to_response('commerce/checkout_receipt.html', context)
Exemplo n.º 10
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(IDVerificationService.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(IDVerificationService.user_has_valid_or_pending(user), status)
Exemplo n.º 11
0
 def get(self, request, **kwargs):
     """
     Get IDV attempt details by attempt_id. Only accessible by global staff.
     """
     attempt_id = kwargs.get('attempt_id')
     verification_detail = IDVerificationService.get_verification_details_by_id(attempt_id)
     if not verification_detail:
         raise Http404
     return Response(
         IDVerificationDetailsSerializer(verification_detail).data
     )
Exemplo n.º 12
0
 def test_denied_sso_verification(self):
     with freeze_time('2015-04-02'):
         # create denied sso verification for the user, make sure the denial
         # is handled properly
         SSOVerification.objects.create(user=self.user, status='denied')
         status = IDVerificationService.user_status(self.user)
         expected_status = {
             'status': 'must_reverify', 'error': '', 'should_display': False,
             'verification_expiry': '', 'status_date': ''
         }
         self.assertDictEqual(status, expected_status)
Exemplo n.º 13
0
def checkout_receipt(request):
    """ Receipt view. """

    page_title = _('Receipt')
    is_payment_complete = True
    payment_support_email = configuration_helpers.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)

    commerce_configuration = CommerceConfiguration.current()
    # user order cache should be cleared when a new order is placed
    # so user can see new order in their order history.
    if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled:
        cache_key = commerce_configuration.CACHE_KEY + '.' + str(request.user.id)
        cache.delete(cache_key)

    context = {
        'page_title': page_title,
        'is_payment_complete': is_payment_complete,
        'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
        'verified': IDVerificationService.user_has_valid_or_pending(request.user),
        '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,
        'is_request_in_themed_site': is_request_in_themed_site()
    }
    return render_to_response('commerce/checkout_receipt.html', context)
Exemplo n.º 14
0
    def extract_student(student, features):
        """ convert student to dictionary """
        student_features = [x for x in STUDENT_FEATURES if x in features]
        profile_features = [x for x in PROFILE_FEATURES if x in features]

        # For data extractions on the 'meta' field
        # the feature name should be in the format of 'meta.foo' where
        # 'foo' is the keyname in the meta dictionary
        meta_features = []
        for feature in features:
            if 'meta.' in feature:
                meta_key = feature.split('.')[1]
                meta_features.append((feature, meta_key))

        student_dict = dict((feature, extract_attr(student, feature))
                            for feature in student_features)
        profile = student.profile
        if profile is not None:
            profile_dict = dict((feature, extract_attr(profile, feature))
                                for feature in profile_features)
            student_dict.update(profile_dict)

            # now fetch the requested meta fields
            meta_dict = json.loads(profile.meta) if profile.meta else {}
            for meta_feature, meta_key in meta_features:
                student_dict[meta_feature] = meta_dict.get(meta_key)

        if include_cohort_column:
            # Note that we use student.course_groups.all() here instead of
            # student.course_groups.filter(). The latter creates a fresh query,
            # therefore negating the performance gain from prefetch_related().
            student_dict['cohort'] = next(
                (cohort.name for cohort in student.course_groups.all()
                 if cohort.course_id == course_key), "[unassigned]")

        if include_team_column:
            student_dict['team'] = next(
                (team.name for team in student.teams.all()
                 if team.course_id == course_key), UNAVAILABLE)

        if include_enrollment_mode or include_verification_status:
            enrollment_mode = CourseEnrollment.enrollment_mode_for_user(
                student, course_key)[0]
            if include_verification_status:
                student_dict[
                    'verification_status'] = IDVerificationService.verification_status_for_user(
                        student, enrollment_mode)
            if include_enrollment_mode:
                student_dict['enrollment_mode'] = enrollment_mode

        student_dict['enrollment'] = CourseEnrollment.get_enrollment(
            student, course_key).created

        return student_dict
Exemplo n.º 15
0
    def get(self, request):
        """
        Render the reverification flow.

        Most of the work is done client-side by composing the same
        Backbone views used in the initial verification flow.
        """
        status, __ = IDVerificationService.user_status(request.user)

        expiration_datetime = IDVerificationService.get_expiration_datetime(
            request.user)
        can_reverify = False
        if expiration_datetime:
            if is_verification_expiring_soon(expiration_datetime):
                # The user has an active verification, but the verification
                # is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
                # In this case user can resubmit photos for reverification.
                can_reverify = True

        # If the user has no initial verification or if the verification
        # process is still ongoing 'pending' or expired then allow the user to
        # submit the photo verification.
        # A photo verification is marked as 'pending' if its status is either
        # 'submitted' or 'must_retry'.

        if status in ["none", "must_reverify", "expired", "pending"
                      ] or can_reverify:
            context = {
                "user_full_name":
                request.user.profile.name,
                "platform_name":
                configuration_helpers.get_value('PLATFORM_NAME',
                                                settings.PLATFORM_NAME),
                "capture_sound":
                staticfiles_storage.url("audio/camera_capture.wav"),
            }
            return render_to_response("verify_student/reverify.html", context)
        else:
            context = {"status": status}
            return render_to_response(
                "verify_student/reverify_not_allowed.html", context)
Exemplo n.º 16
0
    def get_queryset(self):
        username = self.kwargs['username']
        User = get_user_model()
        try:
            user = User.objects.get(username=username)
            verifications = IDVerificationService.verifications_for_user(user)
            if not verifications:
                raise Http404

            return sorted(verifications, key=lambda x: x.updated_at, reverse=True)
        except User.DoesNotExist:
            raise Http404  # lint-amnesty, pylint: disable=raise-missing-from
Exemplo n.º 17
0
 def _user_verification_mode(self, user, context, bulk_enrollments):
     """
     Returns a list of enrollment-mode and verification-status for the
     given user.
     """
     enrollment_mode = CourseEnrollment.enrollment_mode_for_user(user, context.course_id)[0]
     verification_status = IDVerificationService.verification_status_for_user(
         user,
         enrollment_mode,
         user_is_verified=user.id in bulk_enrollments.verified_users,
     )
     return [enrollment_mode, verification_status]
Exemplo n.º 18
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 IDVerificationService.user_has_valid_or_pending(user)
    def send_verification_expiry_email(self, batch_verifications,
                                       email_config):
        """
        Sends verification expiry email to the learners in the batch using edx_ace
        If the email is successfully sent change the expiry_email_date to reflect when the
        email was sent

        :param batch_verifications: verification objects for which email will be sent
        :param email_config: Contains configuration like dry-run flag value, which determines whether actual email will
                             be sent or not
        """
        if email_config['dry_run']:
            logger.info(
                u"This was a dry run, no email was sent. For the actual run email would have been sent "
                u"to {} learner(s)".format(len(batch_verifications)))
            return True

        site = Site.objects.get_current()
        message_context = get_base_template_context(site)
        message_context.update({
            'platform_name':
            settings.PLATFORM_NAME,
            'lms_verification_link':
            IDVerificationService.email_reverify_url(),
            'help_center_link':
            settings.ID_VERIFICATION_SUPPORT_LINK
        })

        expiry_email = VerificationExpiry(context=message_context)
        users = User.objects.filter(pk__in=[
            verification.user_id for verification in batch_verifications
        ])

        success = True
        for verification in batch_verifications:
            try:
                user = users.get(pk=verification.user_id)
                with emulate_http_request(site=site, user=user):
                    msg = expiry_email.personalize(
                        recipient=Recipient(user.username, user.email),
                        language=get_user_preference(user, LANGUAGE_KEY),
                        user_context={
                            'full_name': user.profile.name,
                        })
                    ace.send(msg)
                    self._set_email_expiry_date(verification, user,
                                                email_config)
            except Exception:  # pylint: disable=broad-except
                logger.exception('Could not send email for verification id %d',
                                 verification.id)
                success = False

        return success
Exemplo n.º 20
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()
            self.assertFalse(IDVerificationService.user_is_verified(user),
                             status)

        attempt.status = "approved"
        attempt.save()
        self.assertTrue(IDVerificationService.user_is_verified(user),
                        attempt.status)
Exemplo n.º 21
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 IDVerificationService.user_has_valid_or_pending(user)
Exemplo n.º 22
0
    def get(self, request):
        """
        Render the reverification flow.

        Most of the work is done client-side by composing the same
        Backbone views used in the initial verification flow.
        """
        verification_status = IDVerificationService.user_status(request.user)
        expiration_datetime = IDVerificationService.get_expiration_datetime(request.user, ['approved'])
        if can_verify_now(verification_status, expiration_datetime):
            context = {
                "user_full_name": request.user.profile.name,
                "platform_name": configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
                "capture_sound": staticfiles_storage.url("audio/camera_capture.wav"),
            }
            return render_to_response("verify_student/reverify.html", context)
        else:
            context = {
                "status": verification_status['status']
            }
            return render_to_response("verify_student/reverify_not_allowed.html", context)
Exemplo n.º 23
0
    def extract_student(student, features):
        """ convert student to dictionary """
        student_features = [x for x in STUDENT_FEATURES if x in features]
        profile_features = [x for x in PROFILE_FEATURES if x in features]

        # For data extractions on the 'meta' field
        # the feature name should be in the format of 'meta.foo' where
        # 'foo' is the keyname in the meta dictionary
        meta_features = []
        for feature in features:
            if 'meta.' in feature:
                meta_key = feature.split('.')[1]
                meta_features.append((feature, meta_key))

        student_dict = dict((feature, extract_attr(student, feature))
                            for feature in student_features)
        profile = student.profile
        if profile is not None:
            profile_dict = dict((feature, extract_attr(profile, feature))
                                for feature in profile_features)
            student_dict.update(profile_dict)

            # now fetch the requested meta fields
            meta_dict = json.loads(profile.meta) if profile.meta else {}
            for meta_feature, meta_key in meta_features:
                student_dict[meta_feature] = meta_dict.get(meta_key)

        if include_cohort_column:
            # Note that we use student.course_groups.all() here instead of
            # student.course_groups.filter(). The latter creates a fresh query,
            # therefore negating the performance gain from prefetch_related().
            student_dict['cohort'] = next(
                (cohort.name for cohort in student.course_groups.all() if cohort.course_id == course_key),
                "[unassigned]"
            )

        if include_team_column:
            student_dict['team'] = next(
                (team.name for team in student.teams.all() if team.course_id == course_key),
                UNAVAILABLE
            )

        if include_enrollment_mode or include_verification_status:
            enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_key)[0]
            if include_verification_status:
                student_dict['verification_status'] = IDVerificationService.verification_status_for_user(
                    student,
                    enrollment_mode
                )
            if include_enrollment_mode:
                student_dict['enrollment_mode'] = enrollment_mode

        return student_dict
    def test_user_status(self):
        # test for correct status when no error returned
        user = UserFactory.create()
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'none', 'error': '', 'should_display': True})

        # test for when photo verification has been created
        SoftwareSecurePhotoVerification.objects.create(user=user, status='approved')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'approved', 'error': '', 'should_display': True})

        # create another photo verification for the same user, make sure the denial
        # is handled properly
        SoftwareSecurePhotoVerification.objects.create(
            user=user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
        )
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'must_reverify', 'error': ['id_image_missing'], 'should_display': True})

        # test for when sso verification has been created
        SSOVerification.objects.create(user=user, status='approved')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'approved', 'error': '', 'should_display': False})

        # create another sso verification for the same user, make sure the denial
        # is handled properly
        SSOVerification.objects.create(user=user, status='denied')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'must_reverify', 'error': '', 'should_display': False})

        # test for when manual verification has been created
        ManualVerification.objects.create(user=user, status='approved')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'approved', 'error': '', 'should_display': False})
Exemplo n.º 25
0
    def test_user_status(self):
        # test for correct status when no error returned
        user = UserFactory.create()
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'none', 'error': '', 'should_display': True})

        # test for when photo verification has been created
        SoftwareSecurePhotoVerification.objects.create(user=user, status='approved')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'approved', 'error': '', 'should_display': True})

        # create another photo verification for the same user, make sure the denial
        # is handled properly
        SoftwareSecurePhotoVerification.objects.create(
            user=user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
        )
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'must_reverify', 'error': ['id_image_missing'], 'should_display': True})

        # test for when sso verification has been created
        SSOVerification.objects.create(user=user, status='approved')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'approved', 'error': '', 'should_display': False})

        # create another sso verification for the same user, make sure the denial
        # is handled properly
        SSOVerification.objects.create(user=user, status='denied')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'must_reverify', 'error': '', 'should_display': False})

        # test for when manual verification has been created
        ManualVerification.objects.create(user=user, status='approved')
        status = IDVerificationService.user_status(user)
        self.assertDictEqual(status, {'status': 'approved', 'error': '', 'should_display': False})
Exemplo n.º 26
0
def can_generate_allowlist_certificate(user, course_key):
    """
    Check if an allowlist certificate can be generated (created if it doesn't already exist, or updated if it does
    exist) for this user, in this course run.
    """
    if not is_using_certificate_allowlist(course_key):
        # This course run is not using the allowlist feature
        log.info(
            '{course} is not using the certificate allowlist. Certificate cannot be generated.'.format(
                course=course_key
            ))
        return False

    if not auto_certificate_generation_enabled():
        # Automatic certificate generation is globally disabled
        log.info('Automatic certificate generation is globally disabled. Certificate cannot be generated.')
        return False

    if CertificateInvalidation.has_certificate_invalidation(user, course_key):
        # The invalidation list overrides the allowlist
        log.info(
            '{user} : {course} is on the certificate invalidation list. Certificate cannot be generated.'.format(
                user=user.id,
                course=course_key
            ))
        return False

    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
    if enrollment_mode is None:
        log.info('{user} : {course} does not have an enrollment. Certificate cannot be generated.'.format(
            user=user.id,
            course=course_key
        ))
        return False

    if not IDVerificationService.user_has_ever_been_verified(user):
        log.info(f'{user.id} has not ever had a verified id. Certificate cannot be generated.')
        return False

    if not _is_on_certificate_allowlist(user, course_key):
        log.info('{user} : {course} is not on the certificate allowlist. Certificate cannot be generated.'.format(
            user=user.id,
            course=course_key
        ))
        return False

    log.info('{user} : {course} is on the certificate allowlist'.format(
        user=user.id,
        course=course_key
    ))
    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    return _can_generate_allowlist_certificate_for_status(cert)
Exemplo n.º 27
0
 def test_approved_sso_verification(self):
     with freeze_time('2015-03-02'):
         # test for when sso verification has been created
         SSOVerification.objects.create(user=self.user, status='approved')
         status = IDVerificationService.user_status(self.user)
         expected_status = {
             'status': 'approved',
             'error': '',
             'should_display': False,
             'verification_expiry': '',
             'status_date': datetime.now(utc)
         }
         self.assertDictEqual(status, expected_status)
Exemplo n.º 28
0
 def test_denied_software_secure_verification(self):
     with freeze_time('2015-2-02'):
         # create denied photo verification for the user, make sure the denial
         # is handled properly
         SoftwareSecurePhotoVerification.objects.create(
             user=self.user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
         )
         status = IDVerificationService.user_status(self.user)
         expected_status = {
             'status': 'must_reverify', 'error': ['id_image_missing'],
             'should_display': True, 'verification_expiry': '', 'status_date': '',
         }
         self.assertDictEqual(status, expected_status)
Exemplo n.º 29
0
def _can_generate_certificate_common(user, course_key):
    """
    Check if a course certificate can be generated (created if it doesn't already exist, or updated if it does
    exist) for this user, in this course run.

    This method contains checks that are common to both allowlist and V2 regular course certificates.
    """
    if CertificateInvalidation.has_certificate_invalidation(user, course_key):
        # The invalidation list prevents certificate generation
        log.info(
            f'{user.id} : {course_key} is on the certificate invalidation list. Certificate cannot be generated.'
        )
        return False

    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)
    if enrollment_mode is None:
        log.info(
            f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.'
        )
        return False

    if not modes_api.is_eligible_for_certificate(enrollment_mode):
        log.info(
            f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for a '
            f'certificate. Certificate cannot be generated.')
        return False

    if not IDVerificationService.user_is_verified(user):
        log.info(
            f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.'
        )
        return False

    if not _can_generate_certificate_for_status(user, course_key):
        return False

    course_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        log.info(
            f'{course_key} does not a course overview. Certificate cannot be generated for {user.id}.'
        )
        return False

    if not has_html_certificates_enabled(course_overview):
        log.info(
            f'{course_key} does not have HTML certificates enabled. Certificate cannot be generated for '
            f'{user.id}.')
        return False

    return True
Exemplo n.º 30
0
    def test_verification_deadline_date_retry(self):
        with freeze_time('2015-01-02'):
            course = create_course_run(days_till_start=-1)
            user = create_user(verification_status='denied')
            CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED)

            block = VerificationDeadlineDate(course, user)
            assert block.css_class == 'verification-deadline-retry'
            assert block.title == 'Verification Deadline'
            assert block.date == (datetime.now(utc) + timedelta(days=14))
            assert block.description ==\
                   'You must successfully complete verification before this date to qualify for a Verified Certificate.'
            assert block.link_text == 'Retry Verification'
            assert block.link == IDVerificationService.get_verify_location()
Exemplo n.º 31
0
    def test_verification_status_for_user(self, enrollment_mode, status, output):
        """
        Verify verification_status_for_user returns correct status.
        """
        user = UserFactory.create()
        CourseFactory.create()

        with patch(
            'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
        ) as mock_verification:
            mock_verification.return_value = status

            status = IDVerificationService.verification_status_for_user(user, enrollment_mode)
            assert status == output
Exemplo n.º 32
0
    def test_get_expiration_datetime(self):
        """
        Test that the latest expiration datetime is returned if there are multiple records
        """
        user_a = UserFactory.create()

        SSOVerification.objects.create(
            user=user_a, status='approved', expiration_date=datetime(2021, 11, 12, 0, 0, tzinfo=timezone.utc)
        )
        newer_record = SSOVerification.objects.create(
            user=user_a, status='approved', expiration_date=datetime(2022, 1, 12, 0, 0, tzinfo=timezone.utc)
        )

        expiration_datetime = IDVerificationService.get_expiration_datetime(user_a, ['approved'])
        assert expiration_datetime == newer_record.expiration_datetime
Exemplo n.º 33
0
 def test_expiring_software_secure_verification(self, new_status):
     with freeze_time('2015-07-11') as frozen_datetime:
         # create approved photo verification for the user
         SoftwareSecurePhotoVerification.objects.create(user=self.user, status='approved')
         expiring_datetime = datetime.now(utc)
         frozen_datetime.move_to('2015-07-14')
         # create another according to status passed in.
         SoftwareSecurePhotoVerification.objects.create(user=self.user, status=new_status)
         status_date = expiring_datetime
         if new_status == 'approved':
             status_date = datetime.now(utc)
         expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '',
                            'status_date': status_date}
         status = IDVerificationService.user_status(self.user)
         self.assertDictEqual(status, expected_status)
Exemplo n.º 34
0
    def get(self, request):
        """
        Render the reverification flow.

        Most of the work is done client-side by composing the same
        Backbone views used in the initial verification flow.
        """
        verification_status = IDVerificationService.user_status(request.user)

        expiration_datetime = IDVerificationService.get_expiration_datetime(request.user, ['approved'])
        can_reverify = False
        if expiration_datetime:
            if is_verification_expiring_soon(expiration_datetime):
                # The user has an active verification, but the verification
                # is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
                # In this case user can resubmit photos for reverification.
                can_reverify = True

        # If the user has no initial verification or if the verification
        # process is still ongoing 'pending' or expired then allow the user to
        # submit the photo verification.
        # A photo verification is marked as 'pending' if its status is either
        # 'submitted' or 'must_retry'.

        if verification_status['status'] in ["none", "must_reverify", "expired", "pending"] or can_reverify:
            context = {
                "user_full_name": request.user.profile.name,
                "platform_name": configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
                "capture_sound": staticfiles_storage.url("audio/camera_capture.wav"),
            }
            return render_to_response("verify_student/reverify.html", context)
        else:
            context = {
                "status": verification_status['status']
            }
            return render_to_response("verify_student/reverify_not_allowed.html", context)
Exemplo n.º 35
0
    def test_verification_status_for_user(self, enrollment_mode, status, output):
        """
        Verify verification_status_for_user returns correct status.
        """
        user = UserFactory.create()
        CourseFactory.create()

        with patch(
            'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
        ) as mock_verification:

            mock_verification.return_value = status

            status = IDVerificationService.verification_status_for_user(user, enrollment_mode)
            self.assertEqual(status, output)
Exemplo n.º 36
0
def generate_certificate(self, **kwargs):
    """
    Generates a certificate for a single user.

    kwargs:
        - student: The student for whom to generate a certificate.
        - course_key: The course key for the course that the student is
            receiving a certificate in.
        - expected_verification_status: The expected verification status
            for the user.  When the status has changed, we double check
            that the actual verification status is as expected before
            generating a certificate, in the off chance that the database
            has not yet updated with the user's new verification status.
        - allowlist_certificate: A flag indicating whether to generate an allowlist certificate (which is V2 of
            whitelisted certificates)
        - v2_certificate: A flag indicating whether to generate a v2 course certificate
    """
    original_kwargs = kwargs.copy()
    student = User.objects.get(id=kwargs.pop('student'))
    course_key = CourseKey.from_string(kwargs.pop('course_key'))
    expected_verification_status = kwargs.pop('expected_verification_status', None)
    allowlist_certificate = kwargs.pop('allowlist_certificate', False)
    v2_certificate = kwargs.pop('v2_certificate', False)

    if allowlist_certificate:
        generate_allowlist_certificate(user=student, course_key=course_key)
        return

    if v2_certificate:
        # TODO: will be implemented in MICROBA-923
        log.warning(f'Ignoring v2 certificate task request for {student.id}: {course_key}')
        return

    if expected_verification_status:
        actual_verification_status = IDVerificationService.user_status(student)
        actual_verification_status = actual_verification_status['status']
        if expected_verification_status != actual_verification_status:
            log.warning(
                'Expected verification status {expected} '
                'differs from actual verification status {actual} '
                'for user {user} in course {course}'.format(
                    expected=expected_verification_status,
                    actual=actual_verification_status,
                    user=student.id,
                    course=course_key
                ))
            raise self.retry(kwargs=original_kwargs)
    generate_user_certificates(student=student, course_key=course_key, **kwargs)
Exemplo n.º 37
0
def generate_certificate(self, **kwargs):
    """
    Generates a certificate for a single user.

    kwargs:
        - student: The student for whom to generate a certificate.
        - course_key: The course key for the course that the student is
            receiving a certificate in.
        - expected_verification_status: The expected verification status
            for the user.  When the status has changed, we double check
            that the actual verification status is as expected before
            generating a certificate, in the off chance that the database
            has not yet updated with the user's new verification status.
        - v2_certificate: A flag indicating whether to generate a v2 course certificate
        - generation_mode: Only used when emitting an event for V2 certificates. Options are "self" (implying the user
            generated the cert themself) and "batch" for everything else.
    """
    original_kwargs = kwargs.copy()
    student = User.objects.get(id=kwargs.pop('student'))
    course_key = CourseKey.from_string(kwargs.pop('course_key'))
    expected_verification_status = kwargs.pop('expected_verification_status',
                                              None)
    v2_certificate = kwargs.pop('v2_certificate', False)
    status = kwargs.pop('status', CertificateStatuses.downloadable)
    generation_mode = kwargs.pop('generation_mode', 'batch')

    if v2_certificate:
        generate_course_certificate(user=student,
                                    course_key=course_key,
                                    status=status,
                                    generation_mode=generation_mode)
        return

    if expected_verification_status:
        actual_verification_status = IDVerificationService.user_status(student)
        actual_verification_status = actual_verification_status['status']
        if expected_verification_status != actual_verification_status:
            log.warning('Expected verification status {expected} '
                        'differs from actual verification status {actual} '
                        'for user {user} in course {course}'.format(
                            expected=expected_verification_status,
                            actual=actual_verification_status,
                            user=student.id,
                            course=course_key))
            raise self.retry(kwargs=original_kwargs)
    generate_user_certificates(student=student,
                               course_key=course_key,
                               **kwargs)
Exemplo n.º 38
0
 def test_override_verification(self, verification_type):
     with freeze_time('2015-07-11') as frozen_datetime:
         # create approved photo verification for the user
         SoftwareSecurePhotoVerification.objects.create(user=self.user,
                                                        status='approved')
         frozen_datetime.move_to('2015-07-14')
         verification_type.objects.create(user=self.user, status='approved')
         expected_status = {
             'status': 'approved',
             'error': '',
             'should_display': False,
             'verification_expiry': '',
             'status_date': datetime.now(utc)
         }
         status = IDVerificationService.user_status(self.user)
         self.assertDictEqual(status, expected_status)
def _listen_for_id_verification_status_changed(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user's id verification status has changed.
    """
    if not auto_certificate_generation_enabled():
        return

    user_enrollments = CourseEnrollment.enrollments_for_user(user=user)
    expected_verification_status = IDVerificationService.user_status(user)
    expected_verification_status = expected_verification_status['status']

    for enrollment in user_enrollments:
        log.info(
            f'Attempt will be made to generate a course certificate for {user.id} : {enrollment.course_id}. Id '
            f'verification status is {expected_verification_status}')
        generate_certificate_task(user, enrollment.course_id)
Exemplo n.º 40
0
def generate_proctoring_requirements_email_context(user, course_id):
    """
    Constructs a dictionary for use in proctoring requirements email context

    Arguments:
        user: Currently logged-in user
        course_id: ID of the proctoring-enabled course the user is enrolled in
    """
    course_module = modulestore().get_course(course_id)
    return {
        'user': user,
        'course_name': course_module.display_name,
        'proctoring_provider': capwords(course_module.proctoring_provider.replace('_', ' ')),
        'proctoring_requirements_url': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''),
        'id_verification_url': IDVerificationService.get_verify_location(),
    }
Exemplo n.º 41
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
        """
        expiration_datetime = IDVerificationService.get_expiration_datetime(
            user, ['submitted', 'approved', 'must_retry']
        )
        # return 'expiration_datetime' of latest photo verification if found,
        # otherwise implicitly return ''
        if expiration_datetime:
            return expiration_datetime.strftime(date_format)

        return ''
Exemplo n.º 42
0
    def test_get_verified_user_ids(self):
        """
        Tests for getting users that are verified.
        """
        user_a = UserFactory.create()
        user_b = UserFactory.create()
        user_c = UserFactory.create()
        user_unverified = UserFactory.create()
        user_denied = UserFactory.create()

        SoftwareSecurePhotoVerification.objects.create(user=user_a, status='approved')
        ManualVerification.objects.create(user=user_b, status='approved')
        SSOVerification.objects.create(user=user_c, status='approved')
        SSOVerification.objects.create(user=user_denied, status='denied')

        verified_user_ids = set(IDVerificationService.get_verified_user_ids([
            user_a, user_b, user_c, user_unverified, user_denied
        ]))
        expected_user_ids = {user_a.id, user_b.id, user_c.id}

        self.assertEqual(expected_user_ids, verified_user_ids)
Exemplo n.º 43
0
 def __init__(self, context, users):
     CourseEnrollment.bulk_fetch_enrollment_states(users, context.course_id)
     self.verified_users = [
         verified.user.id for verified in IDVerificationService.get_verified_users(users)
     ]
Exemplo n.º 44
0
 def __init__(self, context, users):
     CourseEnrollment.bulk_fetch_enrollment_states(users, context.course_id)
     self.verified_users = set(IDVerificationService.get_verified_user_ids(users))
Exemplo n.º 45
0
 def verification_status(self):
     """Return the verification status for this user."""
     verification_status = IDVerificationService.user_status(self.user)
     return verification_status['status']
Exemplo n.º 46
0
def student_dashboard(request):
    """
    Provides the LMS dashboard view

    TODO: This is lms specific and does not belong in common code.

    Arguments:
        request: The request object.

    Returns:
        The dashboard response.

    """
    user = request.user
    if not UserProfile.objects.filter(user=user).exists():
        return redirect(reverse('account_settings'))

    platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME)

    enable_verified_certificates = configuration_helpers.get_value(
        'ENABLE_VERIFIED_CERTIFICATES',
        settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES')
    )
    display_course_modes_on_dashboard = configuration_helpers.get_value(
        'DISPLAY_COURSE_MODES_ON_DASHBOARD',
        settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True)
    )
    activation_email_support_link = configuration_helpers.get_value(
        'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
    ) or settings.SUPPORT_SITE_LINK

    # Get the org whitelist or the org blacklist for the current site
    site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site()
    course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist))

    # Get the entitlements for the user and a mapping to all available sessions for that entitlement
    # If an entitlement has no available sessions, pass through a mock course overview object
    (course_entitlements,
     course_entitlement_available_sessions,
     unfulfilled_entitlement_pseudo_sessions) = get_filtered_course_entitlements(
        user,
        site_org_whitelist,
        site_org_blacklist
    )

    # Record how many courses there are so that we can get a better
    # understanding of usage patterns on prod.
    monitoring_utils.accumulate('num_courses', len(course_enrollments))

    # Sort the enrollment pairs by the enrollment date
    course_enrollments.sort(key=lambda x: x.created, reverse=True)

    # Retrieve the course modes for each course
    enrolled_course_ids = [enrollment.course_id for enrollment in course_enrollments]
    __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses(enrolled_course_ids)
    course_modes_by_course = {
        course_id: {
            mode.slug: mode
            for mode in modes
        }
        for course_id, modes in iteritems(unexpired_course_modes)
    }

    # Check to see if the student has recently enrolled in a course.
    # If so, display a notification message confirming the enrollment.
    enrollment_message = _create_recent_enrollment_message(
        course_enrollments, course_modes_by_course
    )
    course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True)

    sidebar_account_activation_message = ''
    banner_account_activation_message = ''
    display_account_activation_message_on_sidebar = configuration_helpers.get_value(
        'DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR',
        settings.FEATURES.get('DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR', False)
    )

    # Display activation message in sidebar if DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR
    # flag is active. Otherwise display existing message at the top.
    if display_account_activation_message_on_sidebar and not user.is_active:
        sidebar_account_activation_message = render_to_string(
            'registration/account_activation_sidebar_notice.html',
            {
                'email': user.email,
                'platform_name': platform_name,
                'activation_email_support_link': activation_email_support_link
            }
        )
    elif not user.is_active:
        banner_account_activation_message = render_to_string(
            'registration/activate_account_notice.html',
            {'email': user.email}
        )

    enterprise_message = get_dashboard_consent_notification(request, user, course_enrollments)

    # Disable lookup of Enterprise consent_required_course due to ENT-727
    # Will re-enable after fixing WL-1315
    consent_required_courses = set()
    enterprise_customer_name = None

    # Account activation message
    account_activation_messages = [
        message for message in messages.get_messages(request) if 'account-activation' in message.tags
    ]

    # Global staff can see what courses encountered an error on their dashboard
    staff_access = False
    errored_courses = {}
    if has_access(user, 'staff', 'global'):
        # Show any courses that encountered an error on load
        staff_access = True
        errored_courses = modulestore().get_errored_courses()

    show_courseware_links_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if has_access(request.user, 'load', enrollment.course_overview)
    )

    # Find programs associated with course runs being displayed. This information
    # is passed in the template context to allow rendering of program-related
    # information on the dashboard.
    meter = ProgramProgressMeter(request.site, user, enrollments=course_enrollments)
    ecommerce_service = EcommerceService()
    inverted_programs = meter.invert_programs()

    urls, programs_data = {}, {}
    bundles_on_dashboard_flag = WaffleFlag(WaffleFlagNamespace(name=u'student.experiments'), u'bundles_on_dashboard')

    # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete
    if bundles_on_dashboard_flag.is_enabled() and inverted_programs and inverted_programs.items():
        if len(course_enrollments) < 4:
            for program in inverted_programs.values():
                try:
                    program_uuid = program[0]['uuid']
                    program_data = get_programs(request.site, uuid=program_uuid)
                    program_data = ProgramDataExtender(program_data, request.user).extend()
                    skus = program_data.get('skus')
                    checkout_page_url = ecommerce_service.get_checkout_page_url(*skus)
                    program_data['completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get('uuid')
                    programs_data[program_uuid] = program_data
                except:  # pylint: disable=bare-except
                    pass

    # Construct a dictionary of course mode information
    # used to render the course list.  We re-use the course modes dict
    # we loaded earlier to avoid hitting the database.
    course_mode_info = {
        enrollment.course_id: complete_course_mode_info(
            enrollment.course_id, enrollment,
            modes=course_modes_by_course[enrollment.course_id]
        )
        for enrollment in course_enrollments
    }

    # Determine the per-course verification status
    # This is a dictionary in which the keys are course locators
    # and the values are one of:
    #
    # VERIFY_STATUS_NEED_TO_VERIFY
    # VERIFY_STATUS_SUBMITTED
    # VERIFY_STATUS_APPROVED
    # VERIFY_STATUS_MISSED_DEADLINE
    #
    # Each of which correspond to a particular message to display
    # next to the course on the dashboard.
    #
    # If a course is not included in this dictionary,
    # there is no verification messaging to display.
    verify_status_by_course = check_verify_status_by_course(user, course_enrollments)
    cert_statuses = {
        enrollment.course_id: cert_info(request.user, enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # only show email settings for Mongo course and when bulk email is turned on
    show_email_settings_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments if (
            BulkEmailFlag.feature_enabled(enrollment.course_id)
        )
    )

    # Verification Attempts
    # Used to generate the "you must reverify for course x" banner
    verification_status = IDVerificationService.user_status(user)
    verification_errors = get_verification_error_reasons_for_display(verification_status['error'])

    # Gets data for midcourse reverifications, if any are necessary or have failed
    statuses = ["approved", "denied", "pending", "must_reverify"]
    reverifications = reverification_info(statuses)

    block_courses = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if is_course_blocked(
            request,
            CourseRegistrationCode.objects.filter(
                course_id=enrollment.course_id,
                registrationcoderedemption__redeemed_by=request.user
            ),
            enrollment.course_id
        )
    )

    enrolled_courses_either_paid = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.is_paid_course()
    )

    # If there are *any* denied reverifications that have not been toggled off,
    # we'll display the banner
    denied_banner = any(item.display for item in reverifications["denied"])

    # Populate the Order History for the side-bar.
    order_history_list = order_history(
        user,
        course_org_filter=site_org_whitelist,
        org_filter_out_set=site_org_blacklist
    )

    # get list of courses having pre-requisites yet to be completed
    courses_having_prerequisites = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.course_overview.pre_requisite_courses
    )
    courses_requirements_not_met = get_pre_requisite_courses_not_completed(user, courses_having_prerequisites)

    if 'notlive' in request.GET:
        redirect_message = _("The course you are looking for does not start until {date}.").format(
            date=request.GET['notlive']
        )
    elif 'course_closed' in request.GET:
        redirect_message = _("The course you are looking for is closed for enrollment as of {date}.").format(
            date=request.GET['course_closed']
        )
    else:
        redirect_message = ''

    valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired']
    display_sidebar_on_dashboard = (len(order_history_list) or
                                    (verification_status['status'] in valid_verification_statuses and
                                    verification_status['should_display']))

    # Filter out any course enrollment course cards that are associated with fulfilled entitlements
    for entitlement in [e for e in course_entitlements if e.enrollment_course_run is not None]:
        course_enrollments = [
            enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id
        ]

    context = {
        'urls': urls,
        'programs_data': programs_data,
        'enterprise_message': enterprise_message,
        'consent_required_courses': consent_required_courses,
        'enterprise_customer_name': enterprise_customer_name,
        'enrollment_message': enrollment_message,
        'redirect_message': redirect_message,
        'account_activation_messages': account_activation_messages,
        'course_enrollments': course_enrollments,
        'course_entitlements': course_entitlements,
        'course_entitlement_available_sessions': course_entitlement_available_sessions,
        'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions,
        'course_optouts': course_optouts,
        'banner_account_activation_message': banner_account_activation_message,
        'sidebar_account_activation_message': sidebar_account_activation_message,
        'staff_access': staff_access,
        'errored_courses': errored_courses,
        'show_courseware_links_for': show_courseware_links_for,
        'all_course_modes': course_mode_info,
        'cert_statuses': cert_statuses,
        'credit_statuses': _credit_statuses(user, course_enrollments),
        'show_email_settings_for': show_email_settings_for,
        'reverifications': reverifications,
        'verification_display': verification_status['should_display'],
        'verification_status': verification_status['status'],
        'verification_status_by_course': verify_status_by_course,
        'verification_errors': verification_errors,
        'block_courses': block_courses,
        'denied_banner': denied_banner,
        'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
        'user': user,
        'logout_url': reverse('logout'),
        'platform_name': platform_name,
        'enrolled_courses_either_paid': enrolled_courses_either_paid,
        'provider_states': [],
        'order_history_list': order_history_list,
        'courses_requirements_not_met': courses_requirements_not_met,
        'nav_hidden': True,
        'inverted_programs': inverted_programs,
        'show_program_listing': ProgramsApiConfig.is_enabled(),
        'show_dashboard_tabs': True,
        'disable_courseware_js': True,
        'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard,
        'display_sidebar_on_dashboard': display_sidebar_on_dashboard,
    }

    if ecommerce_service.is_enabled(request.user):
        context.update({
            'use_ecommerce_payment_flow': True,
            'ecommerce_payment_page': ecommerce_service.payment_page_url(),
        })

    # Gather urls for course card resume buttons.
    resume_button_urls = _get_urls_for_resume_buttons(user, course_enrollments)
    # There must be enough urls for dashboard.html. Template creates course
    # cards for "enrollments + entitlements".
    resume_button_urls += ['' for entitlement in course_entitlements]
    context.update({
        'resume_button_urls': resume_button_urls
    })

    response = render_to_response('dashboard.html', context)
    set_user_info_cookie(response, request)
    return response
Exemplo n.º 47
0
    def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None, generate_pdf=True):
        """
        Request a new certificate for a student.

        Arguments:
          student   - User.object
          course_id - courseenrollment.course_id (CourseKey)
          forced_grade - a string indicating a grade parameter to pass with
                         the certificate request. If this is given, grading
                         will be skipped.
          generate_pdf - Boolean should a message be sent in queue to generate certificate PDF

        Will change the certificate status to 'generating' or
        `downloadable` in case of web view certificates.

        The course must not be a CCX.

        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 be 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 newly created certificate instance
        """

        if hasattr(course_id, 'ccx'):
            LOGGER.warning(
                (
                    u"Cannot create certificate generation task for user %s "
                    u"in the course '%s'; "
                    u"certificates are not allowed for CCX courses."
                ),
                student.id,
                unicode(course_id)
            )
            return None

        valid_statuses = [
            status.generating,
            status.unavailable,
            status.deleted,
            status.error,
            status.notpassing,
            status.downloadable,
            status.auditing,
            status.audit_passing,
            status.audit_notpassing,
            status.unverified,
        ]

        cert_status_dict = certificate_status_for_student(student, course_id)
        cert_status = cert_status_dict.get('status')
        download_url = cert_status_dict.get('download_url')
        cert = None
        if download_url:
            self._log_pdf_cert_generation_discontinued_warning(
                student.id, course_id, cert_status, download_url
            )
            return None

        if cert_status not in valid_statuses:
            LOGGER.warning(
                (
                    u"Cannot create certificate generation task for user %s "
                    u"in the course '%s'; "
                    u"the certificate status '%s' is not one of %s."
                ),
                student.id,
                unicode(course_id),
                cert_status,
                unicode(valid_statuses)
            )
            return None

        # The caller can optionally pass a course in to avoid
        # re-fetching it from Mongo. If they have not provided one,
        # get it from the modulestore.
        if course is None:
            course = modulestore().get_course(course_id, depth=0)

        profile = UserProfile.objects.get(user=student)
        profile_name = profile.name

        # Needed for access control in grading.
        self.request.user = student
        self.request.session = {}

        is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists()
        course_grade = CourseGradeFactory().read(student, course)
        enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id)
        mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
        user_is_verified = IDVerificationService.user_is_verified(student)
        cert_mode = enrollment_mode
        is_eligible_for_certificate = is_whitelisted or CourseMode.is_eligible_for_certificate(enrollment_mode)
        unverified = False
        # For credit mode generate verified certificate
        if cert_mode in (CourseMode.CREDIT_MODE, CourseMode.MASTERS):
            cert_mode = CourseMode.VERIFIED

        if template_file is not None:
            template_pdf = template_file
        elif mode_is_verified and user_is_verified:
            template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(id=course_id)
        elif mode_is_verified and not user_is_verified:
            template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)
            if CourseMode.mode_for_course(course_id, CourseMode.HONOR):
                cert_mode = GeneratedCertificate.MODES.honor
            else:
                unverified = True
        else:
            # honor code and audit students
            template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)

        LOGGER.info(
            (
                u"Certificate generated for student %s in the course: %s with template: %s. "
                u"given template: %s, "
                u"user is verified: %s, "
                u"mode is verified: %s,"
                u"generate_pdf is: %s"
            ),
            student.username,
            unicode(course_id),
            template_pdf,
            template_file,
            user_is_verified,
            mode_is_verified,
            generate_pdf
        )

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

        cert.mode = cert_mode
        cert.user = student
        cert.grade = course_grade.percent
        cert.course_id = course_id
        cert.name = profile_name
        cert.download_url = ''

        # Strip HTML from grade range label
        grade_contents = forced_grade or course_grade.letter_grade
        try:
            grade_contents = lxml.html.fromstring(grade_contents).text_content()
            passing = True
        except (TypeError, XMLSyntaxError, ParserError) as exc:
            LOGGER.info(
                (
                    u"Could not retrieve grade for student %s "
                    u"in the course '%s' "
                    u"because an exception occurred while parsing the "
                    u"grade contents '%s' as HTML. "
                    u"The exception was: '%s'"
                ),
                student.id,
                unicode(course_id),
                grade_contents,
                unicode(exc)
            )

            # Log if the student is whitelisted
            if is_whitelisted:
                LOGGER.info(
                    u"Student %s is whitelisted in '%s'",
                    student.id,
                    unicode(course_id)
                )
                passing = True
            else:
                passing = False

        # If this user's enrollment is not eligible to receive a
        # certificate, mark it as such for reporting and
        # analytics. Only do this if the certificate is new, or
        # already marked as ineligible -- we don't want to mark
        # existing audit certs as ineligible.
        cutoff = settings.AUDIT_CERT_CUTOFF_DATE
        if (cutoff and cert.created_date >= cutoff) and not is_eligible_for_certificate:
            cert.status = status.audit_passing if passing else status.audit_notpassing
            cert.save()
            LOGGER.info(
                u"Student %s with enrollment mode %s is not eligible for a certificate.",
                student.id,
                enrollment_mode
            )
            return cert
        # If they are not passing, short-circuit and don't generate cert
        elif not passing:
            cert.status = status.notpassing
            cert.save()

            LOGGER.info(
                (
                    u"Student %s does not have a grade for '%s', "
                    u"so their certificate status has been set to '%s'. "
                    u"No certificate generation task was sent to the XQueue."
                ),
                student.id,
                unicode(course_id),
                cert.status
            )
            return cert

        # Check to see whether the student is on the the embargoed
        # country restricted list. If so, they should not receive a
        # certificate -- set their status to restricted and log it.
        if self.restricted.filter(user=student).exists():
            cert.status = status.restricted
            cert.save()

            LOGGER.info(
                (
                    u"Student %s is in the embargoed country restricted "
                    u"list, so their certificate status has been set to '%s' "
                    u"for the course '%s'. "
                    u"No certificate generation task was sent to the XQueue."
                ),
                student.id,
                cert.status,
                unicode(course_id)
            )
            return cert

        if unverified:
            cert.status = status.unverified
            cert.save()
            LOGGER.info(
                (
                    u"User %s has a verified enrollment in course %s "
                    u"but is missing ID verification. "
                    u"Certificate status has been set to unverified"
                ),
                student.id,
                unicode(course_id),
            )
            return cert

        # Finally, generate the certificate and send it off.
        return self._generate_cert(cert, course, student, grade_contents, template_pdf, generate_pdf)
Exemplo n.º 48
0
def check_verify_status_by_course(user, course_enrollments):
    """
    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_RESUBMITTED: The student has re-submitted photos for re-verification while
          they still have an active but expiring ID verification
        * VERIFY_STATUS_APPROVED: The student has been successfully verified.
        * VERIFY_STATUS_MISSED_DEADLINE: The student did not submit photos within the course's deadline.
        * VERIFY_STATUS_NEED_TO_REVERIFY: The student has an active verification, but it is
            set to expire before the verification deadline for the course.

    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_enrollments (list[CourseEnrollment]): The courses the user is enrolled in.

    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 = IDVerificationService.verifications_for_user(user)

    # Check whether the user has an active or pending verification attempt
    has_active_or_pending = IDVerificationService.user_has_valid_or_pending(user)

    # Retrieve expiration_datetime of most recent approved verification
    expiration_datetime = IDVerificationService.get_expiration_datetime(user, ['approved'])
    verification_expiring_soon = is_verification_expiring_soon(expiration_datetime)

    # Retrieve verification deadlines for the enrolled courses
    enrolled_course_keys = [enrollment.course_id for enrollment in course_enrollments]
    course_deadlines = VerificationDeadline.deadlines_for_courses(enrolled_course_keys)

    recent_verification_datetime = None

    for enrollment in course_enrollments:

        # If the user hasn't enrolled as verified, then the course
        # won't display state related to its verification status.
        if enrollment.mode in CourseMode.VERIFIED_MODES:

            # Retrieve the verification deadline associated with the course.
            # This could be None if the course doesn't have a deadline.
            deadline = course_deadlines.get(enrollment.course_id)

            relevant_verification = verification_for_datetime(deadline, verifications)

            # Picking the max verification datetime on each iteration only with approved status
            if relevant_verification is not None and relevant_verification.status == "approved":
                recent_verification_datetime = max(
                    recent_verification_datetime if recent_verification_datetime is not None
                    else relevant_verification.expiration_datetime,
                    relevant_verification.expiration_datetime
                )

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

            # Check whether the user was approved or is awaiting approval
            if relevant_verification is not None:
                should_display = relevant_verification.should_display_status_to_user()

                if relevant_verification.status == "approved":
                    if verification_expiring_soon:
                        status = VERIFY_STATUS_NEED_TO_REVERIFY
                    else:
                        status = VERIFY_STATUS_APPROVED
                elif relevant_verification.status == "submitted":
                    if verification_expiring_soon:
                        status = VERIFY_STATUS_RESUBMITTED
                    else:
                        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):
                    if IDVerificationService.user_is_verified(user) and verification_expiring_soon:
                        # The user has an active verification, but the verification
                        # is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
                        # Tell the student to reverify.
                        status = VERIFY_STATUS_NEED_TO_REVERIFY
                    elif not IDVerificationService.user_is_verified(user):
                        status = VERIFY_STATUS_NEED_TO_VERIFY
                else:
                    # If a user currently has an active or pending verification,
                    # then they may have submitted an additional attempt after
                    # the verification deadline passed.  This can occur,
                    # for example, when the support team asks a student
                    # to reverify after the deadline so they can receive
                    # a verified certificate.
                    # In this case, we still want to show them as "verified"
                    # on the dashboard.
                    if has_active_or_pending:
                        status = VERIFY_STATUS_APPROVED

                    # Otherwise, the student missed the deadline, so show
                    # them as "honor" (the kind of certificate they will receive).
                    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

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

                status_by_course[enrollment.course_id] = {
                    'status': status,
                    'days_until_deadline': days_until_deadline,
                    'should_display': should_display,
                }

    if recent_verification_datetime:
        for key, value in iteritems(status_by_course):  # pylint: disable=unused-variable
            status_by_course[key]['verification_good_until'] = recent_verification_datetime.strftime("%m/%d/%Y")

    return status_by_course