示例#1
0
    def update_or_create(cls, user_id, course_id, **kwargs):
        """
        Creates a course grade in the database.
        Returns a PersistedCourseGrade object.
        """
        passed = kwargs.pop('passed')

        if kwargs.get('course_version', None) is None:
            kwargs['course_version'] = ""

        grade, _ = cls.objects.update_or_create(user_id=user_id,
                                                course_id=course_id,
                                                defaults=kwargs)
        if passed and not grade.passed_timestamp:
            grade.passed_timestamp = now()
            grade.save()

            if not is_testing_environment():
                from openedx.adg.lms.applications.helpers import check_and_update_prereq_courses_status_to_completed

                check_and_update_prereq_courses_status_to_completed(
                    user_id, course_id)

        if not is_testing_environment():
            user = User.objects.get(id=user_id)
            emit_course_progress_event(user,
                                       CourseOverview.get_from_id(course_id),
                                       grade=grade)
        cls._emit_grade_calculated_event(grade)
        cls._update_cache(course_id, user_id, grade)
        return grade
示例#2
0
def enroll_email(course_id, student_email, auto_enroll=False, email_students=False, email_params=None, language=None):
    """
    Enroll a student by email.

    `student_email` is student's emails e.g. "*****@*****.**"
    `auto_enroll` determines what is put in CourseEnrollmentAllowed.auto_enroll
        if auto_enroll is set, then when the email registers, they will be
        enrolled in the course automatically.
    `email_students` determines if student should be notified of action by email.
    `email_params` parameters used while parsing email templates (a `dict`).
    `language` is the language used to render the email.

    returns two EmailEnrollmentState's
        representing state before and after the action.
    """
    previous_state = EmailEnrollmentState(course_id, student_email)
    enrollment_obj = None
    if previous_state.user and User.objects.get(email=student_email).is_active:
        # if the student is currently unenrolled, don't enroll them in their
        # previous mode

        # for now, White Labels use the
        # "honor" course_mode. Given the change to use "audit" as the default
        # course_mode in Open edX, we need to be backwards compatible with
        # how White Labels approach enrollment modes.
        if CourseMode.is_white_label(course_id):
            course_mode = CourseMode.HONOR
        else:
            course_mode = None

        if previous_state.enrollment:
            course_mode = previous_state.mode

        enrollment_obj = CourseEnrollment.enroll_by_email(student_email, course_id, course_mode)
        if email_students:
            email_params['message_type'] = 'enrolled_enroll'
            email_params['email_address'] = student_email
            email_params['full_name'] = previous_state.full_name
            if is_testing_environment():
                send_mail_to_student(student_email, email_params, language=language)
            else:
                compose_and_send_adg_course_enrollment_invitation_email(student_email, email_params)

    elif not is_email_retired(student_email):
        cea, _ = CourseEnrollmentAllowed.objects.get_or_create(course_id=course_id, email=student_email)
        cea.auto_enroll = auto_enroll
        cea.save()
        if email_students:
            email_params['message_type'] = 'allowed_enroll'
            email_params['email_address'] = student_email
            if is_testing_environment():
                send_mail_to_student(student_email, email_params, language=language)
            else:
                compose_and_send_adg_course_enrollment_invitation_email(student_email, email_params)

    after_state = EmailEnrollmentState(course_id, student_email)

    return previous_state, after_state, enrollment_obj
示例#3
0
def test_is_testing_environment(monkeypatch):
    """
    Test if the environment is set to test or not
    """

    monkeypatch.setitem(os.environ, 'DJANGO_SETTINGS_MODULE', 'lms.envs.test')
    assert utils.is_testing_environment()

    monkeypatch.setitem(os.environ, 'DJANGO_SETTINGS_MODULE', 'cms.envs.test')
    assert utils.is_testing_environment()

    monkeypatch.setitem(os.environ, 'DJANGO_SETTINGS_MODULE',
                        'something_other_than_test')
    assert not utils.is_testing_environment()
示例#4
0
def add_registration_form_defaults(request, form_desc):
    """
    It will add default values in the form if user is invited
    """
    if is_testing_environment():
        return form_desc

    email = request.GET.get('email', '')
    email = parse.unquote(email)
    invitation_obj = Invitation.objects.filter(
        email=email,
        status=Invitation.PENDING).select_related('business_unit').first()

    if not invitation_obj:
        return form_desc

    business_unit = invitation_obj.business_unit
    for form in form_desc.fields:
        if form['name'] == 'email':
            form['defaultValue'] = email
            form['restrictions']['readonly'] = True
            continue

        if form['name'] == 'is_adg_employee':
            form['defaultValue'] = bool(business_unit)
            continue

        if business_unit and form['name'] == 'company':
            for option in form['options']:
                option['default'] = option['value'] == business_unit.title
            continue
    return form_desc
示例#5
0
def get_courses(user):
    """
    Return courses using core method if environment is test environment else uses customized method for courses list.
    """
    if is_testing_environment():
        return get_courses_core(user)

    return MultilingualCourseGroup.objects.get_user_program_prereq_courses_and_all_non_prereq_courses(
        user)
示例#6
0
def get_login_session_form(request):
    """Return a description of the login form.

    This decouples clients from the API definition:
    if the API decides to modify the form, clients won't need
    to be updated.

    See `user_api.helpers.FormDescription` for examples
    of the JSON-encoded form description.

    Returns:
        HttpResponse

    """

    if not is_testing_environment():
        from openedx.adg.lms.user_authn_override.login_form import get_login_session_form_override

        return get_login_session_form_override(request)

    form_desc = FormDescription("post", reverse("user_api_login_session"))
    _apply_third_party_auth_overrides(request, form_desc)

    # Translators: This label appears above a field on the login form
    # meant to hold the user's email address.
    email_label = _("Email")

    # Translators: These instructions appear on the login form, immediately
    # below a field meant to hold the user's email address.
    email_instructions = _(
        "The email address you used to register with {platform_name}").format(
            platform_name=configuration_helpers.get_value(
                'PLATFORM_NAME', settings.PLATFORM_NAME))

    form_desc.add_field("email",
                        field_type="email",
                        label=email_label,
                        instructions=email_instructions,
                        restrictions={
                            "min_length": accounts.EMAIL_MIN_LENGTH,
                            "max_length": accounts.EMAIL_MAX_LENGTH,
                        })

    # Translators: This label appears above a field on the login form
    # meant to hold the user's password.
    password_label = _(u"Password")

    form_desc.add_field(
        "password",
        label=password_label,
        field_type="password",
        restrictions={'max_length': DEFAULT_MAX_PASSWORD_LENGTH})

    return form_desc
示例#7
0
def get_password_reset_form():
    """Return a description of the password reset form.

    This decouples clients from the API definition:
    if the API decides to modify the form, clients won't need
    to be updated.

    See `user_api.helpers.FormDescription` for examples
    of the JSON-encoded form description.

    Returns:
        HttpResponse

    """

    if not is_testing_environment():
        from openedx.adg.lms.user_authn_override.password_reset import get_password_reset_form_override

        return get_password_reset_form_override()

    form_desc = FormDescription("post", reverse("password_change_request"))

    # Translators: This label appears above a field on the password reset
    # form meant to hold the user's email address.
    email_label = _(u"Email")

    # Translators: This example email address is used as a placeholder in
    # a field on the password reset form meant to hold the user's email address.
    email_placeholder = _(u"*****@*****.**")

    # Translators: These instructions appear on the password reset form,
    # immediately below a field meant to hold the user's email address.
    # pylint: disable=no-member
    email_instructions = _(
        u"The email address you used to register with {platform_name}").format(
            platform_name=configuration_helpers.get_value(
                'PLATFORM_NAME', settings.PLATFORM_NAME))

    form_desc.add_field("email",
                        field_type="email",
                        label=email_label,
                        placeholder=email_placeholder,
                        instructions=email_instructions,
                        restrictions={
                            "min_length": accounts.EMAIL_MIN_LENGTH,
                            "max_length": accounts.EMAIL_MAX_LENGTH,
                        })

    return form_desc
示例#8
0
def is_referred_by_login_or_register(request):
    """
    Returns True if user is redirected from login or register, otherwise False

    Arguments:
        request: HTTP request

    Returns:
        Boolean: True if path in HTTP_REFERER contains login or register, otherwise False
    """
    if is_testing_environment():
        return True

    referer = request.META.get('HTTP_REFERER', '')
    path = parse.urlsplit(referer).path
    return path in ['/login', '/register']
示例#9
0
 def save(
         self,  # pylint: disable=arguments-differ
         use_https=False,
         token_generator=default_token_generator,
         request=None,
         **_kwargs):
     """
     Generates a one-use only link for resetting password and sends to the
     user.
     """
     for user in self.users_cache:
         if self.is_account_recovery:
             if is_testing_environment():
                 send_password_reset_email_for_user(user, request)
             else:
                 compose_and_send_adg_password_reset_email(user, request)
         else:
             send_account_recovery_email_for_user(
                 user, request, user.account_recovery.secondary_email)
示例#10
0
def send_adg_password_reset_success_email(user, request):
    """
    Sends edx default email if the environment is testing. Otherwise, sends adg password reset
    success email
    """
    from openedx.core.djangoapps.user_authn.views.password_reset import send_password_reset_success_email

    if is_testing_environment():
        send_password_reset_success_email(user, request)
        return

    root_url = configuration_helpers.get_value('LMS_ROOT_URL',
                                               settings.LMS_ROOT_URL)
    context = {
        'first_name': get_user_first_name(user),
        'signin_link': '{}/login'.format(root_url),
    }

    task_send_mandrill_email(MandrillClient.PASSWORD_RESET_SUCCESS,
                             [user.email], context)
示例#11
0
def send_account_activation_email(user, profile, registration=None):
    """
    Send adg account activation emails.

    Args:
        user (User): User to which email will be sent.
        profile (UserProfile): Profile of the user.
        registration (Registration): Registration of the user.

    Returns:
        None
    """
    from common.djangoapps.student.views import compose_and_send_activation_email
    from common.djangoapps.student.models import Registration

    if is_testing_environment():
        compose_and_send_activation_email(user, profile, registration)
        return

    if registration is None:
        registration = Registration.objects.get(user=user)

    compose_and_send_adg_activation_email(user, registration.activation_key)
示例#12
0
def get_extra_course_about_context(request, course):
    """
    Get all the extra context for the course_about page

    Arguments:
        request (Request): Request object
        course (CourseOverview): Course Overview object to add data to the context

    Returns:
        dict: Returns an empty dict if it is the testing environment otherwise returns a dict with added context
    """
    if is_testing_environment():
        return {}

    user = request.user
    course_language_names = []
    enrolled_course_group_course = None
    enroll_popup_message = cannot_enroll_message = ''

    multilingual_course = MultilingualCourse.objects.all(
    ).multilingual_course_with_course_id(course.id)
    if multilingual_course:
        course_group_courses = multilingual_course.multilingual_course_group.multilingual_courses
        course_language_codes = course_group_courses.open_multilingual_courses(
        ).language_codes_with_course_ids()
        course_language_names = get_language_names_from_codes(
            course_language_codes)

        enrolled_course_group_course = course_group_courses.all(
        ).enrolled_course(user)
        if enrolled_course_group_course:
            course_display_name = enrolled_course_group_course.course.display_name_with_default

            enroll_popup_message = Text(
                _('Warning: If you wish to change the language of this course, your progress in '
                  'the following course(s) will be erased.{line_break}{course_name}'
                  )).format(course_name=course_display_name,
                            line_break=HTML('<br>'))

    course_enrollment_count = CourseEnrollment.objects.enrollment_counts(
        course.id).get('total')

    course_requirements = course_grade = certificate = None
    if user.is_authenticated:
        course_requirements = get_pre_requisite_courses_not_completed(
            user, [course.id])
        course_grade = CourseGradeFactory().read(user, course_key=course.id)
        certificate = GeneratedCertificate.certificate_for_student(
            user, course.id)

    has_generated_cert_for_any_other_course_group_course = False
    if enrolled_course_group_course and enrolled_course_group_course.course != course:
        has_generated_cert_for_any_other_course_group_course = GeneratedCertificate.objects.filter(
            course_id=enrolled_course_group_course.course.id,
            user=user).exists()

        if has_generated_cert_for_any_other_course_group_course:
            cannot_enroll_message = _(
                'You cannot enroll in this course version as you have already earned a '
                'certificate for another version of this course!')

    context = {
        'course_languages':
        course_language_names,
        'course_requirements':
        course_requirements,
        'total_enrollments':
        course_enrollment_count,
        'self_paced':
        course.self_paced,
        'effort':
        course.effort,
        'is_course_passed':
        course_grade and getattr(course_grade, 'passed', False),
        'has_certificate':
        certificate,
        'has_user_enrolled_in_course_group_courses':
        bool(enrolled_course_group_course),
        'has_generated_cert_for_any_course_group_course':
        has_generated_cert_for_any_other_course_group_course,
        'enroll_popup_message':
        enroll_popup_message,
        'cannot_enroll_message':
        cannot_enroll_message,
    }

    return context
示例#13
0
    url(
        r'^courses/{}/search/'.format(settings.COURSE_ID_PATTERN, ),
        include('openedx.features.course_search.urls'),
    ),

    # Learner profile
    url(
        r'^u/',
        include('openedx.features.learner_profile.urls'),
    ),
]

# include all adg lms urls
urlpatterns.extend(adg_url_patterns)

if not is_testing_environment():
    urlpatterns = adg_override_core_url_patterns + urlpatterns

if settings.FEATURES.get('ENABLE_TEAMS'):
    # Teams endpoints
    urlpatterns += [
        url(r'^api/team/', include('lms.djangoapps.teams.api_urls')),
        url(
            r'^courses/{}/teams/'.format(settings.COURSE_ID_PATTERN, ),
            include('lms.djangoapps.teams.urls'),
            name='teams_endpoints',
        ),
    ]

# allow course staff to change to student view of courseware
if settings.FEATURES.get('ENABLE_MASQUERADE'):
示例#14
0
def confirm_email_change(request, key):
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {'old_email': user.email, 'new_email': pec.new_email}

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        use_https = request.is_secure()
        if settings.FEATURES['ENABLE_MKTG_SITE']:
            contact_link = marketing_link('CONTACT')
        else:
            contact_link = '{protocol}://{site}{link}'.format(
                protocol='https' if use_https else 'http',
                site=configuration_helpers.get_value('SITE_NAME',
                                                     settings.SITE_NAME),
                link=reverse('contact'),
            )

        site = Site.objects.get_current()
        message_context = get_base_template_context(site)
        message_context.update({
            'old_email':
            user.email,
            'new_email':
            pec.new_email,
            'contact_link':
            contact_link,
            'from_address':
            configuration_helpers.get_value('email_from_address',
                                            settings.DEFAULT_FROM_EMAIL),
        })

        msg = EmailChangeConfirmation().personalize(
            recipient=Recipient(user.username, user.email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )

        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append(
            [user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            if is_testing_environment():
                ace.send(msg)
            else:
                update_all_webinar_registrations_to_new_email(
                    user.email, pec.new_email)
                compose_and_send_adg_update_email_confirmation(
                    user, message_context)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        msg.recipient = Recipient(user.username, pec.new_email)
        try:
            if is_testing_environment():
                ace.send(msg)
            else:
                compose_and_send_adg_update_email_confirmation(
                    user, message_context)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html",
                                      address_context)
        return response
示例#15
0
def do_email_change_request(user,
                            new_email,
                            activation_key=None,
                            secondary_email_change_request=False):
    """
    Given a new email for a user, does some basic verification of the new address and sends an activation message
    to the new address. If any issues are encountered with verification or sending the message, a ValueError will
    be thrown.
    """
    # if activation_key is not passing as an argument, generate a random key
    if not activation_key:
        activation_key = uuid.uuid4().hex

    confirm_link = reverse('confirm_email_change',
                           kwargs={
                               'key': activation_key,
                           })

    if secondary_email_change_request:
        PendingSecondaryEmailChange.objects.update_or_create(
            user=user,
            defaults={
                'new_secondary_email': new_email,
                'activation_key': activation_key,
            })
        confirm_link = reverse('activate_secondary_email',
                               kwargs={'key': activation_key})
    else:
        PendingEmailChange.objects.update_or_create(user=user,
                                                    defaults={
                                                        'new_email':
                                                        new_email,
                                                        'activation_key':
                                                        activation_key,
                                                    })

    use_https = theming_helpers.get_current_request().is_secure()

    site = Site.objects.get_current()
    message_context = get_base_template_context(site)
    message_context.update({
        'old_email':
        user.email,
        'new_email':
        new_email,
        'confirm_link':
        '{protocol}://{site}{link}'.format(
            protocol='https' if use_https else 'http',
            site=configuration_helpers.get_value('SITE_NAME',
                                                 settings.SITE_NAME),
            link=confirm_link,
        ),
    })

    if secondary_email_change_request:
        msg = RecoveryEmailCreate().personalize(
            recipient=Recipient(user.username, new_email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )
    else:
        msg = EmailChange().personalize(
            recipient=Recipient(user.username, new_email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )

    try:
        if is_testing_environment():
            ace.send(msg)
        else:
            compose_and_send_adg_update_email_verification(
                user, new_email, use_https, confirm_link)
    except Exception:
        from_address = configuration_helpers.get_value(
            'email_from_address', settings.DEFAULT_FROM_EMAIL)
        log.error(u'Unable to send email activation link to user from "%s"',
                  from_address,
                  exc_info=True)
        raise ValueError(
            _('Unable to send email activation link. Please try again later.'))

    if not secondary_email_change_request:
        # When the email address change is complete, a "edx.user.settings.changed" event will be emitted.
        # But because changing the email address is multi-step, we also emit an event here so that we can
        # track where the request was initiated.
        tracker.emit(
            SETTING_CHANGE_INITIATED, {
                "setting": "email",
                "old": message_context['old_email'],
                "new": message_context['new_email'],
                "user_id": user.id,
            })
示例#16
0
def change_enrollment(request, check_access=True):
    """
    Modify the enrollment status for the logged-in user.

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

    The request parameter must be a POST request (other methods return 405)
    that specifies course_id and enrollment_action parameters. If course_id or
    enrollment_action is not specified, if course_id is not valid, if
    enrollment_action is something other than "enroll" or "unenroll", if
    enrollment_action is "enroll" and enrollment is closed for the course, or
    if enrollment_action is "unenroll" and the user is not enrolled in the
    course, a 400 error will be returned. If the user is not logged in, 403
    will be returned; it is important that only this case return 403 so the
    front end can redirect the user to a registration or login page when this
    happens. This function should only be called from an AJAX request, so
    the error messages in the responses should never actually be user-visible.

    Args:
        request (`Request`): The Django request object

    Keyword Args:
        check_access (boolean): If True, we check that an accessible course actually
            exists for the given course_key before we enroll the student.
            The default is set to False to avoid breaking legacy code or
            code with non-standard flows (ex. beta tester invitations), but
            for any standard enrollment flow you probably want this to be True.

    Returns:
        Response

    """
    # Get the user
    user = request.user

    # Ensure the user is authenticated
    if not user.is_authenticated:
        return HttpResponseForbidden()

    # Ensure we received a course_id
    action = request.POST.get("enrollment_action")
    if 'course_id' not in request.POST:
        return HttpResponseBadRequest(_("Course id not specified"))

    try:
        course_id = CourseKey.from_string(request.POST.get("course_id"))
    except InvalidKeyError:
        log.warning(
            u"User %s tried to %s with invalid course id: %s",
            user.username,
            action,
            request.POST.get("course_id"),
        )
        return HttpResponseBadRequest(_("Invalid course id"))

    # Allow us to monitor performance of this transaction on a per-course basis since we often roll-out features
    # on a per-course basis.
    monitoring_utils.set_custom_attribute('course_id', text_type(course_id))

    if action == "enroll":
        # Make sure the course exists
        # We don't do this check on unenroll, or a bad course id can't be unenrolled from
        if not modulestore().has_course(course_id):
            log.warning(u"User %s tried to enroll in non-existent course %s",
                        user.username, course_id)
            return HttpResponseBadRequest(_("Course id is invalid"))

        # Record the user's email opt-in preference
        if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'):
            _update_email_opt_in(request, course_id.org)

        available_modes = CourseMode.modes_for_course_dict(course_id)

        # Check whether the user is blocked from enrolling in this course
        # This can occur if the user's IP is on a global blacklist
        # or if the user is enrolling in a country in which the course
        # is not available.
        redirect_url = embargo_api.redirect_if_blocked(
            course_id, user=user, ip_address=get_ip(request), url=request.path)
        if redirect_url:
            return HttpResponse(redirect_url)

        if CourseEntitlement.check_for_existing_entitlement_and_enroll(
                user=user, course_run_key=course_id):
            return HttpResponse(
                reverse('courseware', args=[six.text_type(course_id)]))

        # Check that auto enrollment is allowed for this course
        # (= the course is NOT behind a paywall)
        if CourseMode.can_auto_enroll(course_id):
            # Enroll the user using the default mode (audit)
            # We're assuming that users of the course enrollment table
            # will NOT try to look up the course enrollment model
            # by its slug.  If they do, it's possible (based on the state of the database)
            # for no such model to exist, even though we've set the enrollment type
            # to "audit".
            try:
                enroll_mode = CourseMode.auto_enroll_mode(
                    course_id, available_modes)
                if enroll_mode:

                    if not is_testing_environment() and request.POST.get(
                            'unenroll_course_group_courses'):
                        course_group = MultilingualCourseGroup.objects.filter(
                            multilingual_courses__course__id=course_id).first(
                            )

                        if course_group:
                            course_group.unenroll_from_courses(request.user)

                    CourseEnrollment.enroll(user,
                                            course_id,
                                            check_access=check_access,
                                            mode=enroll_mode)
                    if not is_testing_environment():
                        compose_and_send_adg_course_enrollment_confirmation_email(
                            user, course_id)
            except Exception:  # pylint: disable=broad-except
                return HttpResponseBadRequest(_("Could not enroll"))

        # If we have more than one course mode or professional ed is enabled,
        # then send the user to the choose your track page.
        # (In the case of no-id-professional/professional ed, this will redirect to a page that
        # funnels users directly into the verification / payment flow)
        if CourseMode.has_verified_mode(
                available_modes) or CourseMode.has_professional_mode(
                    available_modes):
            return HttpResponse(
                reverse("course_modes_choose",
                        kwargs={'course_id': text_type(course_id)}))

        # Otherwise, there is only one mode available (the default)
        return HttpResponse()
    elif action == "unenroll":
        enrollment = CourseEnrollment.get_enrollment(user, course_id)
        if not enrollment:
            return HttpResponseBadRequest(
                _("You are not enrolled in this course"))

        certificate_info = cert_info(user, enrollment.course_overview)
        if certificate_info.get('status') in DISABLE_UNENROLL_CERT_STATES:
            return HttpResponseBadRequest(
                _("Your certificate prevents you from unenrolling from this course"
                  ))

        CourseEnrollment.unenroll(user, course_id)
        REFUND_ORDER.send(sender=None, course_enrollment=enrollment)
        return HttpResponse()
    else:
        return HttpResponseBadRequest(_("Enrollment action is invalid"))
示例#17
0
from common.djangoapps.student.helpers import (
    AccountValidationError, authenticate_new_user,
    create_or_set_user_attribute_created_on_site, do_create_account)
from common.djangoapps.student.models import (RegistrationCookieConfiguration,
                                              UserAttribute,
                                              create_comments_service_user,
                                              email_exists_or_retired,
                                              username_exists_or_retired)
from common.djangoapps.student.views import compose_and_send_activation_email
from common.djangoapps.third_party_auth import pipeline, provider
from common.djangoapps.third_party_auth.saml import SAP_SUCCESSFACTORS_SAML_KEY
from common.djangoapps.track import segment
from common.djangoapps.util.db import outer_atomic
from common.djangoapps.util.json_request import JsonResponse

if is_testing_environment():
    from openedx.core.djangoapps.user_authn.views.registration_form import RegistrationFormFactory
else:
    from openedx.adg.lms.registration_extension.forms import RegistrationFormFactory
    from openedx.adg.lms.registration_extension.analytics import emit_registration_event

log = logging.getLogger("edx.student")
AUDIT_LOG = logging.getLogger("audit")

# Used as the name of the user attribute for tracking affiliate registrations
REGISTRATION_AFFILIATE_ID = 'registration_affiliate_id'
REGISTRATION_UTM_PARAMETERS = {
    'utm_source': 'registration_utm_source',
    'utm_medium': 'registration_utm_medium',
    'utm_campaign': 'registration_utm_campaign',
    'utm_term': 'registration_utm_term',
示例#18
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': [
                _(u"Registration using {provider} has timed out.").format(
                    provider=params.get('social_auth_provider'))
            ]
        })

    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(read_committed=True):
        # 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:
        send_account_activation_email(user, profile, registration)

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

    if is_testing_environment():
        _track_user_registration(user, profile, params, third_party_provider)
    else:
        emit_registration_event(user)

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

    create_comments_service_user(user)

    try:
        _record_registration_attributions(request, 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.
    if new_user is not None:
        AUDIT_LOG.info(u"Login success on new account creation - {0}".format(
            new_user.username))

    return new_user