Пример #1
0
 def setUp(self):
     """
     Setup common conditions for every test case.
     """
     super().setUp()
     self.certificate = CertificateData(
         user=UserData(
             pii=UserPersonalData(
                 username="******",
                 email="*****@*****.**",
                 name="Test Example",
             ),
             id=39,
             is_active=True,
         ),
         course=CourseData(
             course_key=CourseKey.from_string(
                 "course-v1:edX+DemoX+Demo_Course"),
             display_name="Demonstration Course",
         ),
         mode="audit",
         current_status="notpassing",
         grade=100,
         download_url="https://downdloadurl.com",
         name="Certs",
     )
Пример #2
0
 def setUp(self):
     """
     Setup common conditions for test cases.
     """
     enrollment = CourseEnrollmentData(
         user=UserData(
             pii=UserPersonalData(
                 username="******",
                 email="*****@*****.**",
                 name="Test Example",
             ),
             id=39,
             is_active=True,
         ),
         course=CourseData(
             course_key=CourseKey.from_string("course-v1:edX+DemoX+Demo_Course"),
             display_name="Demonstration Course",
         ),
         mode="audit",
         is_active=True,
         creation_date=datetime.datetime.now(),
     )
     self.kwargs = {
         "enrollment": enrollment,
     }
Пример #3
0
def _handle_successful_authentication_and_login(user, request):
    """
    Handles clearing the failed login counter, login tracking, and setting session timeout.
    """
    if LoginFailures.is_feature_enabled():
        LoginFailures.clear_lockout_counter(user)

    _track_user_login(user, request)

    try:
        django_login(request, user)
        request.session.set_expiry(604800 * 4)
        log.debug("Setting user session expiry to 4 weeks")

        # .. event_implemented_name: SESSION_LOGIN_COMPLETED
        SESSION_LOGIN_COMPLETED.send_event(user=UserData(
            pii=UserPersonalData(
                username=user.username,
                email=user.email,
                name=user.profile.name,
            ),
            id=user.id,
            is_active=user.is_active,
        ), )
    except Exception as exc:
        AUDIT_LOG.critical(
            "Login failed - Could not create session. Is memcached running?")
        log.critical(
            "Login failed - Could not create session. Is memcached running?")
        log.exception(exc)
        raise
Пример #4
0
    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.full_clean(validate_unique=False)

        # .. event_implemented_name: COHORT_MEMBERSHIP_CHANGED
        COHORT_MEMBERSHIP_CHANGED.send_event(
            cohort=CohortData(
                user=UserData(
                    pii=UserPersonalData(
                        username=self.user.username,
                        email=self.user.email,
                        name=self.user.profile.name,
                    ),
                    id=self.user.id,
                    is_active=self.user.is_active,
                ),
                course=CourseData(
                    course_key=self.course_id,
                ),
                name=self.course_user_group.name,
            )
        )

        log.info("Saving CohortMembership for user '%s' in '%s'", self.user.id, self.course_id)
        return super().save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields
        )
Пример #5
0
    def test_send_registration_event(self):
        """
        Test whether the student registration event is sent during the user's
        registration process.

        Expected result:
            - STUDENT_REGISTRATION_COMPLETED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        event_receiver = Mock(side_effect=self._event_receiver_side_effect)
        STUDENT_REGISTRATION_COMPLETED.connect(event_receiver)

        self.client.post(self.url, self.user_info)

        user = User.objects.get(username=self.user_info.get("username"))
        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal":
                STUDENT_REGISTRATION_COMPLETED,
                "sender":
                None,
                "user":
                UserData(
                    pii=UserPersonalData(
                        username=user.username,
                        email=user.email,
                        name=user.profile.name,
                    ),
                    id=user.id,
                    is_active=user.is_active,
                ),
            }, event_receiver.call_args.kwargs)
Пример #6
0
 def setUp(self):
     """
     Setup common conditions for every test case.
     """
     super().setUp()
     self.user = UserData(
         pii=UserPersonalData(
             username="******",
             email="*****@*****.**",
             name="Test Example",
         ),
         id=39,
         is_active=True,
     )
Пример #7
0
    def test_send_certificate_changed_event(self):
        """
        Test whether the certificate changed event is sent at the end of the
        certificate update process.

        Expected result:
            - CERTIFICATE_CHANGED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        event_receiver = Mock(side_effect=self._event_receiver_side_effect)
        CERTIFICATE_CHANGED.connect(event_receiver)
        certificate = GeneratedCertificateFactory.create(
            status=CertificateStatuses.downloadable,
            user=self.user,
            course_id=self.course.id,
            mode=GeneratedCertificate.MODES.honor,
            name="Certificate",
            grade="100",
            download_url="https://certificate.pdf")

        certificate.grade = "50"
        certificate.save()

        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal":
                CERTIFICATE_CHANGED,
                "sender":
                None,
                "certificate":
                CertificateData(
                    user=UserData(
                        pii=UserPersonalData(
                            username=certificate.user.username,
                            email=certificate.user.email,
                            name=certificate.user.profile.name,
                        ),
                        id=certificate.user.id,
                        is_active=certificate.user.is_active,
                    ),
                    course=CourseData(course_key=certificate.course_id, ),
                    mode=certificate.mode,
                    grade=certificate.grade,
                    current_status=certificate.status,
                    download_url=certificate.download_url,
                    name=certificate.name,
                ),
            }, event_receiver.call_args.kwargs)
Пример #8
0
 def setUp(self):
     """Set up class for post_to_webhook_url testing."""
     self.user = UserData(
         pii=UserPersonalData(
             username="******",
             email="*****@*****.**",
             name="Tania Chernova",
         ),
         id=1,
         is_active=True,
     )
     self.kwargs = {
         'user': self.user,
     }
Пример #9
0
    def test_unenrollment_completed_event_emitted(self):
        """
        Test whether the student un-enrollment completed event is sent after the
        user's unenrollment process.

        Expected result:
            - COURSE_UNENROLLMENT_COMPLETED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        enrollment = CourseEnrollment.enroll(self.user, self.course.id)
        event_receiver = mock.Mock(
            side_effect=self._event_receiver_side_effect)
        COURSE_UNENROLLMENT_COMPLETED.connect(event_receiver)

        CourseEnrollment.unenroll(self.user, self.course.id)

        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal":
                COURSE_UNENROLLMENT_COMPLETED,
                "sender":
                None,
                "enrollment":
                CourseEnrollmentData(
                    user=UserData(
                        pii=UserPersonalData(
                            username=self.user.username,
                            email=self.user.email,
                            name=self.user.profile.name,
                        ),
                        id=self.user.id,
                        is_active=self.user.is_active,
                    ),
                    course=CourseData(
                        course_key=self.course.id,
                        display_name=self.course.display_name,
                    ),
                    mode=enrollment.mode,
                    is_active=False,
                    creation_date=enrollment.created,
                ),
            }, event_receiver.call_args.kwargs)
Пример #10
0
    def test_send_cohort_membership_changed_event(self):
        """
        Test whether the COHORT_MEMBERSHIP_CHANGED event is sent when a cohort
        membership update ends.

        Expected result:
            - COHORT_MEMBERSHIP_CHANGED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        event_receiver = Mock(side_effect=self._event_receiver_side_effect)
        COHORT_MEMBERSHIP_CHANGED.connect(event_receiver)

        cohort_membership, _ = CohortMembership.assign(
            cohort=self.cohort,
            user=self.user,
        )

        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal": COHORT_MEMBERSHIP_CHANGED,
                "sender": None,
                "cohort": CohortData(
                    user=UserData(
                        pii=UserPersonalData(
                            username=cohort_membership.user.username,
                            email=cohort_membership.user.email,
                            name=cohort_membership.user.profile.name,
                        ),
                        id=cohort_membership.user.id,
                        is_active=cohort_membership.user.is_active,
                    ),
                    course=CourseData(
                        course_key=cohort_membership.course_id,
                    ),
                    name=cohort_membership.course_user_group.name,
                ),
            },
            event_receiver.call_args.kwargs
        )
Пример #11
0
 def setUp(self):
     """
     Setup common conditions for every test case.
     """
     super().setUp()
     self.cohort = CohortData(
         user=UserData(
             pii=UserPersonalData(
                 username="******",
                 email="*****@*****.**",
                 name="Test Example",
             ),
             id=39,
             is_active=True,
         ),
         course=CourseData(
             course_key=CourseKey.from_string(
                 "course-v1:edX+DemoX+Demo_Course"),
             display_name="Demonstration Course",
         ),
         name="Uchiha",
     )
Пример #12
0
    def test_send_login_event(self):
        """
        Test whether the student login event is sent after the user's
        login process.

        Expected result:
            - SESSION_LOGIN_COMPLETED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        event_receiver = Mock(side_effect=self._event_receiver_side_effect)
        SESSION_LOGIN_COMPLETED.connect(event_receiver)
        data = {
            "email": "*****@*****.**",
            "password": "******",
        }

        self.client.post(self.url, data)

        user = User.objects.get(username=self.user.username)
        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal":
                SESSION_LOGIN_COMPLETED,
                "sender":
                None,
                "user":
                UserData(
                    pii=UserPersonalData(
                        username=user.username,
                        email=user.email,
                        name=user.profile.name,
                    ),
                    id=user.id,
                    is_active=user.is_active,
                ),
            }, event_receiver.call_args.kwargs)
Пример #13
0
 def setUp(self):
     """
     Setup common conditions for test cases.
     """
     self.certificate = CertificateData(
         user=UserData(
             pii=UserPersonalData(
                 username="******",
                 email="*****@*****.**",
                 name="Test Example",
             ),
             id=39,
             is_active=True,
         ),
         course=CourseData(
             course_key=CourseKey.from_string("course-v1:edX+DemoX+Demo_Course"),
             display_name="Demonstration Course",
         ),
         mode="audit",
         current_status="notpassing",
         grade=0.5,
         download_url="https://downdloadurl.com",
         name="Certs",
     )
     self.kwargs = {
         "certificate": self.certificate,
     }
     self.course_key = CourseKey.from_string("course-v1:edx+DemoX+Demo_Course")
     self.grading_config = {
         "block_id": "467f8ab131634e52bb6c22b60940d857",
         "program_id": "course-v1:edx+DemoX+Demo_Course",
     }
     self.usage_key = self.course_key.make_usage_key(
         "staffgradedxblock",
         self.grading_config.get("block_id")
     )
Пример #14
0
    def save(self, *args, **kwargs):  # pylint: disable=signature-differs
        """
        After the base save() method finishes, fire the COURSE_CERT_CHANGED signal. If the learner is currently passing
        the course we also fire the COURSE_CERT_AWARDED signal.

        The COURSE_CERT_CHANGED signal helps determine if a Course Certificate can be awarded to a learner in the
        Credentials IDA.

        The COURSE_CERT_AWARDED signal helps determine if a Program Certificate can be awarded to a learner in the
        Credentials IDA.
        """
        super().save(*args, **kwargs)
        COURSE_CERT_CHANGED.send_robust(
            sender=self.__class__,
            user=self.user,
            course_key=self.course_id,
            mode=self.mode,
            status=self.status,
        )

        # .. event_implemented_name: CERTIFICATE_CHANGED
        CERTIFICATE_CHANGED.send_event(certificate=CertificateData(
            user=UserData(
                pii=UserPersonalData(
                    username=self.user.username,
                    email=self.user.email,
                    name=self.user.profile.name,
                ),
                id=self.user.id,
                is_active=self.user.is_active,
            ),
            course=CourseData(course_key=self.course_id, ),
            mode=self.mode,
            grade=self.grade,
            current_status=self.status,
            download_url=self.download_url,
            name=self.name,
        ))

        if CertificateStatuses.is_passing_status(self.status):
            COURSE_CERT_AWARDED.send_robust(
                sender=self.__class__,
                user=self.user,
                course_key=self.course_id,
                mode=self.mode,
                status=self.status,
            )

            # .. event_implemented_name: CERTIFICATE_CREATED
            CERTIFICATE_CREATED.send_event(certificate=CertificateData(
                user=UserData(
                    pii=UserPersonalData(
                        username=self.user.username,
                        email=self.user.email,
                        name=self.user.profile.name,
                    ),
                    id=self.user.id,
                    is_active=self.user.is_active,
                ),
                course=CourseData(course_key=self.course_id, ),
                mode=self.mode,
                grade=self.grade,
                current_status=self.status,
                download_url=self.download_url,
                name=self.name,
            ))
Пример #15
0
    def _revoke_certificate(self, status, mode=None, grade=None, source=None):
        """
        Revokes a course certificate from a learner, updating the certificate's status as specified by the value of the
        `status` argument. This will prevent the learner from being able to access their certificate in the associated
        course run.

        We remove the `download_uuid` and the `download_url` as well, but this is only important to PDF certificates.

        Invalidating a certificate fires the `COURSE_CERT_REVOKED` signal. This kicks off a task to determine if there
        are any program certificates that also need to be revoked from the learner.

        If the certificate had a status of `downloadable` before being revoked then we will also emit an
        `edx.certificate.revoked` event for tracking purposes.

        Args:
            status (CertificateStatus) - certificate status to set for the `GeneratedCertificate` record
            mode (String) - learner's current enrollment mode
            grade (float) - snapshot of the learner's current grade as a decimal
            source (String) - source requesting invalidation of the certificate for tracking purposes
        """
        previous_certificate_status = self.status

        if not grade:
            grade = ''

        if not mode:
            mode = self.mode

        preferred_name = self._get_preferred_certificate_name(self.user)

        self.error_reason = ''
        self.download_uuid = ''
        self.download_url = ''
        self.grade = grade
        self.status = status
        self.mode = mode
        self.name = preferred_name
        self.save()

        COURSE_CERT_REVOKED.send_robust(
            sender=self.__class__,
            user=self.user,
            course_key=self.course_id,
            mode=self.mode,
            status=self.status,
        )

        # .. event_implemented_name: CERTIFICATE_REVOKED
        CERTIFICATE_REVOKED.send_event(certificate=CertificateData(
            user=UserData(
                pii=UserPersonalData(
                    username=self.user.username,
                    email=self.user.email,
                    name=self.user.profile.name,
                ),
                id=self.user.id,
                is_active=self.user.is_active,
            ),
            course=CourseData(course_key=self.course_id, ),
            mode=self.mode,
            grade=self.grade,
            current_status=self.status,
            download_url=self.download_url,
            name=self.name,
        ))

        if previous_certificate_status == CertificateStatuses.downloadable:
            # imported here to avoid a circular import issue
            from lms.djangoapps.certificates.utils import emit_certificate_event

            event_data = {
                'user_id': self.user.id,
                'course_id': str(self.course_id),
                'certificate_id': self.verify_uuid,
                'enrollment_mode': self.mode,
                'source': source or '',
            }
            emit_certificate_event('revoked',
                                   self.user,
                                   str(self.course_id),
                                   event_data=event_data)
Пример #16
0
def create_account_with_params(request, params):
    """
    Given a request and a dict of parameters (which may or may not have come
    from the request), create an account for the requesting user, including
    creating a comments service user object and sending an activation email.
    This also takes external/third-party auth into account, updates that as
    necessary, and authenticates the user for the request's session.

    Does not return anything.

    Raises AccountValidationError if an account with the username or email
    specified by params already exists, or ValidationError if any of the given
    parameters is invalid for any other reason.

    Issues with this code:
    * It is non-transactional except where explicitly wrapped in atomic to
      alleviate deadlocks and improve performance. This means failures at
      different places in registration can leave users in inconsistent
      states.
    * Third-party auth passwords are not verified. There is a comment that
      they are unused, but it would be helpful to have a sanity check that
      they are sane.
    * The user-facing text is rather unfriendly (e.g. "Username must be a
      minimum of two characters long" rather than "Please use a username of
      at least two characters").
    * Duplicate email raises a ValidationError (rather than the expected
      AccountValidationError). Duplicate username returns an inconsistent
      user message (i.e. "An account with the Public Username '{username}'
      already exists." rather than "It looks like {username} belongs to an
      existing account. Try again with a different username.") The two checks
      occur at different places in the code; as a result, registering with
      both a duplicate username and email raises only a ValidationError for
      email only.
    """
    # Copy params so we can modify it; we can't just do dict(params) because if
    # params is request.POST, that results in a dict containing lists of values
    params = dict(list(params.items()))

    # allow to define custom set of required/optional/hidden fields via configuration
    extra_fields = configuration_helpers.get_value(
        'REGISTRATION_EXTRA_FIELDS',
        getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})
    )
    if is_registration_api_v1(request):
        if 'confirm_email' in extra_fields:
            del extra_fields['confirm_email']

    # registration via third party (Google, Facebook) using mobile application
    # doesn't use social auth pipeline (no redirect uri(s) etc involved).
    # In this case all related info (required for account linking)
    # is sent in params.
    # `third_party_auth_credentials_in_api` essentially means 'request
    # is made from mobile application'
    third_party_auth_credentials_in_api = 'provider' in params
    is_third_party_auth_enabled = third_party_auth.is_enabled()

    if is_third_party_auth_enabled and (pipeline.running(request) or third_party_auth_credentials_in_api):
        params["password"] = generate_password()

    # in case user is registering via third party (Google, Facebook) and pipeline has expired, show appropriate
    # error message
    if is_third_party_auth_enabled and ('social_auth_provider' in params and not pipeline.running(request)):
        raise ValidationError(
            {
                'session_expired': [
                    _("Registration using {provider} has timed out.").format(
                        provider=params.get('social_auth_provider'))
                ],
                'error_code': 'tpa-session-expired',
            }
        )

    if is_third_party_auth_enabled:
        set_custom_attribute('register_user_tpa', pipeline.running(request))
    extended_profile_fields = configuration_helpers.get_value('extended_profile_fields', [])
    # Can't have terms of service for certain SHIB users, like at Stanford
    registration_fields = getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})
    tos_required = (
        registration_fields.get('terms_of_service') != 'hidden' or
        registration_fields.get('honor_code') != 'hidden'
    )

    form = AccountCreationForm(
        data=params,
        extra_fields=extra_fields,
        extended_profile_fields=extended_profile_fields,
        do_third_party_auth=False,
        tos_required=tos_required,
    )
    custom_form = get_registration_extension_form(data=params)

    # Perform operations within a transaction that are critical to account creation
    with outer_atomic():
        # first, create the account
        (user, profile, registration) = do_create_account(form, custom_form)

        third_party_provider, running_pipeline = _link_user_to_third_party_provider(
            is_third_party_auth_enabled, third_party_auth_credentials_in_api, user, request, params,
        )

        new_user = authenticate_new_user(request, user.username, form.cleaned_data['password'])
        django_login(request, new_user)
        request.session.set_expiry(0)

    # Sites using multiple languages need to record the language used during registration.
    # If not, compose_and_send_activation_email will be sent in site's default language only.
    create_or_set_user_attribute_created_on_site(user, request.site)

    # Only add a default user preference if user does not already has one.
    if not preferences_api.has_user_preference(user, LANGUAGE_KEY):
        preferences_api.set_user_preference(user, LANGUAGE_KEY, get_language())

    # Check if system is configured to skip activation email for the current user.
    skip_email = _skip_activation_email(
        user, running_pipeline, third_party_provider,
    )

    if skip_email:
        registration.activate()
    else:
        redirect_to, root_url = get_next_url_for_login_page(request, include_host=True)
        redirect_url = get_redirect_url_with_host(root_url, redirect_to)
        compose_and_send_activation_email(user, profile, registration, redirect_url)

    if settings.FEATURES.get('ENABLE_DISCUSSION_EMAIL_DIGEST'):
        try:
            enable_notifications(user)
        except Exception:  # pylint: disable=broad-except
            log.exception(f"Enable discussion notifications failed for user {user.id}.")

    _track_user_registration(user, profile, params, third_party_provider, registration)

    # Announce registration
    REGISTER_USER.send(sender=None, user=user, registration=registration)

    STUDENT_REGISTRATION_COMPLETED.send_event(
        user=UserData(
            pii=UserPersonalData(
                username=user.username,
                email=user.email,
                name=user.profile.name,
            ),
            id=user.id,
            is_active=user.is_active,
        ),
    )

    create_comments_service_user(user)

    try:
        _record_registration_attributions(request, new_user)
        _record_marketing_emails_opt_in_attribute(params.get('marketing_emails_opt_in'), new_user)
    # Don't prevent a user from registering due to attribution errors.
    except Exception:   # pylint: disable=broad-except
        log.exception('Error while attributing cookies to user registration.')

    # TODO: there is no error checking here to see that the user actually logged in successfully,
    # and is not yet an active user.
    is_new_user(request, new_user)
    return new_user
Пример #17
0
def create_edxapp_user(*args, **kwargs):
    """
    Creates a user on the open edx django site using calls to
    functions defined in the edx-platform codebase

    Example call:

    data = {
        'email': "*****@*****.**",
        'username': "******",
        'password': "******",
        'fullname': "Full Name",
        'activate': True,
        'site': request.site,
        'language_preference': 'es-419',
    }
    user = create_edxapp_user(**data)

    """
    errors = []

    extra_fields = getattr(settings, "REGISTRATION_EXTRA_FIELDS", {})
    extended_profile_fields = getattr(settings, "extended_profile_fields", [])
    kwargs["name"] = kwargs.pop("fullname", None)
    email = kwargs.get("email")
    username = kwargs.get("username")
    conflicts = check_edxapp_account_conflicts(email=email, username=username)
    if conflicts:
        return None, [
            "Fatal: account collition with the provided: {}".format(
                ", ".join(conflicts))
        ]

    # Go ahead and create the new user
    with transaction.atomic():
        # In theory is possible to extend the registration form with a custom app
        # An example form app for this can be found at http://github.com/open-craft/custom-form-app
        # form = get_registration_extension_form(data=params)
        # if not form:
        form = EdnxAccountCreationForm(
            data=kwargs,
            tos_required=False,
            extra_fields=extra_fields,
            extended_profile_fields=extended_profile_fields,
            # enforce_password_policy=enforce_password_policy,
        )
        (user, profile, registration) = do_create_account(form)  # pylint: disable=unused-variable

    site = kwargs.pop("site", False)
    if site:
        create_or_set_user_attribute_created_on_site(user, site)
    else:
        errors.append("The user was not assigned to any site")

    try:
        create_comments_service_user(user)
    except Exception:  # pylint: disable=broad-except
        errors.append("No comments_service_user was created")

    # TODO: link account with third party auth

    # Announce registration through API call
    post_register.send_robust(sender=None, user=user)  # pylint: disable=no-member

    STUDENT_REGISTRATION_COMPLETED.send_event(
        user=UserData(
            pii=UserPersonalData(
                username=user.username,
                email=user.email,
                name=user.profile.name,  # pylint: disable=no-member
            ),
            id=user.id,
            is_active=user.is_active,
        ), )

    lang_pref = kwargs.pop("language_preference", False)
    if lang_pref:
        try:
            preferences_api.set_user_preference(user, LANGUAGE_KEY, lang_pref)
        except Exception:  # pylint: disable=broad-except
            errors.append(
                "Could not set lang preference '{} for user '{}'".format(
                    lang_pref,
                    user.username,
                ))

    if kwargs.pop("activate_user", False):
        user.is_active = True
        user.save()

    # TODO: run conditional email sequence

    return user, errors