Beispiel #1
0
def _authenticate_first_party(request, unauthenticated_user,
                              third_party_auth_requested):
    """
    Use Django authentication on the given request, using rate limiting if configured
    """

    # If the user doesn't exist, we want to set the username to an invalid username so that authentication is guaranteed
    # to fail and we can take advantage of the ratelimited backend
    username = unauthenticated_user.username if unauthenticated_user else ""

    # First time when a user login through third_party_auth account then user needs to link
    # third_party account with the platform account by login through email and password that's
    # why we need to by-pass this check when user is already authenticated by third_party_auth.
    if not third_party_auth_requested:
        _check_user_auth_flow(request.site, unauthenticated_user)

    try:
        password = normalize_password(request.POST['password'])
        return authenticate(username=username,
                            password=password,
                            request=request)

    # This occurs when there are too many attempts from the same IP address
    except RateLimitException:
        raise AuthFailedError(
            _('Too many failed login attempts. Try again later.'))
Beispiel #2
0
    def post(self, request, *args, **kwargs):
        # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
        # We have to use request.POST because the password_reset_confirm method takes in the request and a user's
        # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
        # field so it passes the equivalence check that new_password1 == new_password2
        # In order to switch out of having to do this copy, we would want to move the normalize_password code into
        # a custom User model's set_password method to ensure it is always happening upon calling set_password.
        request.POST = request.POST.copy()
        request.POST['new_password1'] = normalize_password(
            request.POST['new_password1'])
        request.POST['new_password2'] = normalize_password(
            request.POST['new_password2'])

        password = request.POST['new_password1']
        response = self._validate_password(password, request)
        if response:
            return response

        if 'is_account_recovery' in request.GET:
            self.post_reset_login = True
            response = self._process_password_reset_success(
                request,
                self.token,
                self.uidb64,
                extra_context=self.platform_name)
        else:
            response = self._process_password_reset_success(
                request,
                self.token,
                self.uidb64,
                extra_context=self.platform_name)

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            return self._handle_password_reset_failure(response)

        updated_user = User.objects.get(id=self.uid_int)
        if 'is_account_recovery' in request.GET:
            self._handle_primary_email_update(updated_user)

        updated_user.save()
        if response.status_code == 302 and 'is_account_recovery' in request.GET:
            self._handle_password_creation(request, updated_user)

        return response
Beispiel #3
0
def do_create_account_no_registration(data):
    """
    Given cleaned post variables, create the User and UserProfile objects, as well as the
    registration for this user.

    Returns a tuple (User, UserProfile, Registration).

    Note: this function is also used for creating test users.
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation

    proposed_username = data["username"]
    user = User(username=proposed_username,
                email=data["phone_number"] +
                settings.DEFAULT_EMAIL_ACCOUNT_DOMAIN,
                is_active=True)
    log.warning("phone: " + data["phone_number"])
    password = normalize_password(data["password"])
    user.set_password(password)
    registration = Registration()
    try:
        with transaction.atomic():
            user.save()
    except IntegrityError:
        # Figure out the cause of the integrity error
        # TODO duplicate email is already handled by form.errors above as a ValidationError.
        # The checks for duplicate email/username should occur in the same place with an
        # AccountValidationError and a consistent user message returned (i.e. both should
        # return "It looks like {username} belongs to an existing account. Try again with a
        # different username.")
        if username_exists_or_retired(user.username):
            raise AccountValidationError(
                USERNAME_EXISTS_MSG_FMT.format(username=proposed_username),
                field="username")
        elif email_exists_or_retired(user.email):
            raise AccountValidationError(_(
                "An account with the Email '{email}' already exists.").format(
                    email=user.email),
                                         field="email")
        else:
            raise

    registration.register(user)
    profile_fields = [
        "name", "level_of_education", "gender", "mailing_address", "city",
        "country", "goals", "year_of_birth", "phone_number",
        "web_accelerator_name", "web_accelerator_link"
    ]
    profile = UserProfile(user=user,
                          **{key: data.get(key)
                             for key in profile_fields})
    profile.save()
    # except Exception:
    #     log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
    #     raise
    log.warning("Testing the process to register {id}".format(id=user.id))
    return user, profile
def password_reset_logistration(request, **kwargs):
    """Reset learner password using passed token and new credentials"""

    reset_status = False
    uidb36 = kwargs.get('uidb36')
    token = kwargs.get('token')

    has_required_values, uid_int = _check_token_has_required_values(
        uidb36, token)
    if not has_required_values:
        AUDIT_LOG.exception("Invalid password reset confirm token")
        return JsonResponse({'reset_status': reset_status})

    request.POST = request.POST.copy()
    request.POST['new_password1'] = normalize_password(
        request.POST['new_password1'])
    request.POST['new_password2'] = normalize_password(
        request.POST['new_password2'])

    password = request.POST['new_password1']
    try:
        user = User.objects.get(id=uid_int)
        if not default_token_generator.check_token(user, token):
            AUDIT_LOG.exception("Token validation failed")
            return JsonResponse({'reset_status': reset_status})

        validate_password(password, user=user)
        form = SetPasswordForm(user, request.POST)
        if form.is_valid():
            form.save()
            reset_status = True

    except ValidationError as err:
        AUDIT_LOG.exception("Password validation failed")
        error_status = {
            'reset_status': reset_status,
            'err_msg': ' '.join(err.messages)
        }
        return JsonResponse(error_status)
    except Exception:  # pylint: disable=broad-except
        AUDIT_LOG.exception("Setting new password failed")

    return JsonResponse({'reset_status': reset_status})
Beispiel #5
0
def authenticate_new_user(request, username, password):
    """
    Immediately after a user creates an account, we log them in. They are only
    logged in until they close the browser. They can't log in again until they click
    the activation link from the email.
    """
    password = normalize_password(password)
    backend = load_backend(NEW_USER_AUTH_BACKEND)
    user = backend.authenticate(request=request, username=username, password=password)
    user.backend = NEW_USER_AUTH_BACKEND
    return user
Beispiel #6
0
def authenticate_new_user(request, username, password):
    """
    Immediately after a user creates an account, we log them in. They are only
    logged in until they close the browser. They can't log in again until they click
    the activation link from the email.
    """
    password = normalize_password(password)
    backend = load_backend(NEW_USER_AUTH_BACKEND)
    user = backend.authenticate(request=request, username=username, password=password)
    user.backend = NEW_USER_AUTH_BACKEND
    return user
Beispiel #7
0
def _authenticate_first_party(request, unauthenticated_user):
    """
    Use Django authentication on the given request, using rate limiting if configured
    """

    # If the user doesn't exist, we want to set the username to an invalid username so that authentication is guaranteed
    # to fail and we can take advantage of the ratelimited backend
    username = unauthenticated_user.username if unauthenticated_user else ""

    try:
        password = normalize_password(request.POST['password'])
        return authenticate(username=username,
                            password=password,
                            request=request)

    # This occurs when there are too many attempts from the same IP address
    except RateLimitException:
        raise AuthFailedError(
            _('Too many failed login attempts. Try again later.'))
Beispiel #8
0
def _authenticate_first_party(request, unauthenticated_user):
    """
    Use Django authentication on the given request, using rate limiting if configured
    """

    # If the user doesn't exist, we want to set the username to an invalid username so that authentication is guaranteed
    # to fail and we can take advantage of the ratelimited backend
    username = unauthenticated_user.username if unauthenticated_user else ""

    try:
        password = normalize_password(request.POST['password'])
        return authenticate(
            username=username,
            password=password,
            request=request
        )

    # This occurs when there are too many attempts from the same IP address
    except RateLimitException:
        raise AuthFailedError(_('Too many failed login attempts. Try again later.'))
Beispiel #9
0
def do_create_account(form, custom_form=None):
    """
    Given cleaned post variables, create the User and UserProfile objects, as well as the
    registration for this user.

    Returns a tuple (User, UserProfile, Registration).

    Note: this function is also used for creating test users.
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        raise PermissionDenied()

    errors = {}
    errors.update(form.errors)
    if custom_form:
        errors.update(custom_form.errors)

    if errors:
        raise ValidationError(errors)

    proposed_username = form.cleaned_data["username"]
    user = User(
        username=proposed_username,
        email=form.cleaned_data["email"],
        is_active=False
    )
    password = normalize_password(form.cleaned_data["password"])
    user.set_password(password)
    registration = Registration()

    # TODO: Rearrange so that if part of the process fails, the whole process fails.
    # Right now, we can have e.g. no registration e-mail sent out and a zombie account
    try:
        with transaction.atomic():
            user.save()
            if custom_form:
                custom_model = custom_form.save(commit=False)
                custom_model.user = user
                custom_model.save()
    except IntegrityError:
        # Figure out the cause of the integrity error
        # TODO duplicate email is already handled by form.errors above as a ValidationError.
        # The checks for duplicate email/username should occur in the same place with an
        # AccountValidationError and a consistent user message returned (i.e. both should
        # return "It looks like {username} belongs to an existing account. Try again with a
        # different username.")
        if username_exists_or_retired(user.username):
            raise AccountValidationError(
                USERNAME_EXISTS_MSG_FMT.format(username=proposed_username),
                field="username"
            )
        elif email_exists_or_retired(user.email):
            raise AccountValidationError(
                _("An account with the Email '{email}' already exists.").format(email=user.email),
                field="email"
            )
        else:
            raise

    registration.register(user)

    profile_fields = [
        "name", "level_of_education", "gender", "mailing_address", "city", "country", "goals",
        "year_of_birth"
    ]
    profile = UserProfile(
        user=user,
        **{key: form.cleaned_data.get(key) for key in profile_fields}
    )
    extended_profile = form.cleaned_extended_profile
    if extended_profile:
        profile.meta = json.dumps(extended_profile)
    try:
        profile.save()
    except Exception:
        log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
        raise

    return user, profile, registration
Beispiel #10
0
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
    """
    A wrapper around django.contrib.auth.views.password_reset_confirm.
    Needed because we want to set the user as active at this step.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name":
        configuration_helpers.get_value('platform_name',
                                        settings.PLATFORM_NAME)
    }
    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(request,
                                      uidb64=uidb64,
                                      token=token,
                                      extra_context=platform_name)

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(request,
                                'registration/password_reset_confirm.html',
                                context)

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(request,
                                'registration/password_reset_confirm.html',
                                context)

    if request.method == 'POST':
        # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
        # We have to use request.POST because the password_reset_confirm method takes in the request and a user's
        # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
        # field so it passes the equivalence check that new_password1 == new_password2
        # In order to switch out of having to do this copy, we would want to move the normalize_password code into
        # a custom User model's set_password method to ensure it is always happening upon calling set_password.
        request.POST = request.POST.copy()
        request.POST['new_password1'] = normalize_password(
            request.POST['new_password1'])
        request.POST['new_password2'] = normalize_password(
            request.POST['new_password2'])

        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': ' '.join(err.messages),
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context)

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        response = password_reset_confirm(request,
                                          uidb64=uidb64,
                                          token=token,
                                          extra_context=platform_name)

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid(
            ) if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _(
                    'Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)

        # did the password hash change, if so record it in the PasswordHistory
        if updated_user.password != old_password_hash:
            entry = PasswordHistory()
            entry.create(updated_user)

    else:
        response = password_reset_confirm(request,
                                          uidb64=uidb64,
                                          token=token,
                                          extra_context=platform_name)

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response
Beispiel #11
0
def create_account(username, password, email):
    """Create a new user account.

    This will implicitly create an empty profile for the user.

    WARNING: This function does NOT yet implement all the features
    in `student/views.py`.  Until it does, please use this method
    ONLY for tests of the account API, not in production code.
    In particular, these are currently missing:

    * 3rd party auth
    * External auth (shibboleth)

    In addition, we assume that some functionality is handled
    at higher layers:

    * Analytics events
    * Activation email
    * Terms of service / honor code checking
    * Recording demographic info (use profile API)
    * Auto-enrollment in courses (if invited via instructor dash)

    Args:
        username (unicode): The username for the new account.
        password (unicode): The user's password.
        email (unicode): The email address associated with the account.

    Returns:
        unicode: an activation key for the account.

    Raises:
        errors.AccountUserAlreadyExists
        errors.AccountUsernameInvalid
        errors.AccountEmailInvalid
        errors.AccountPasswordInvalid
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)

    # Validate the username, password, and email
    # This will raise an exception if any of these are not in a valid format.
    _validate_username(username)
    _validate_password(password, username, email)
    _validate_email(email)

    # Create the user account, setting them to "inactive" until they activate their account.
    user = User(username=username, email=email, is_active=False)
    password = normalize_password(password)
    user.set_password(password)

    try:
        user.save()
    except IntegrityError:
        raise errors.AccountUserAlreadyExists

    # Create a registration to track the activation process
    # This implicitly saves the registration.
    registration = Registration()
    registration.register(user)

    # Create an empty user profile with default values
    UserProfile(user=user).save()

    # Return the activation key, which the caller should send to the user
    return registration.activation_key
Beispiel #12
0
def do_create_account(form, custom_form=None):
    """
    Given cleaned post variables, create the User and UserProfile objects, as well as the
    registration for this user.

    Returns a tuple (User, UserProfile, Registration).

    Note: this function is also used for creating test users.
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        raise PermissionDenied()

    errors = {}
    errors.update(form.errors)
    if custom_form:
        errors.update(custom_form.errors)

    if errors:
        raise ValidationError(errors)

    proposed_username = form.cleaned_data["username"]
    user = User(
        username=proposed_username,
        email=form.cleaned_data["email"],
        is_active=False
    )
    password = normalize_password(form.cleaned_data["password"])
    user.set_password(password)
    registration = Registration()

    # TODO: Rearrange so that if part of the process fails, the whole process fails.
    # Right now, we can have e.g. no registration e-mail sent out and a zombie account
    try:
        with transaction.atomic():
            user.save()
            if custom_form:
                custom_model = custom_form.save(commit=False)
                custom_model.user = user
                custom_model.save()
    except IntegrityError:
        # Figure out the cause of the integrity error
        # TODO duplicate email is already handled by form.errors above as a ValidationError.
        # The checks for duplicate email/username should occur in the same place with an
        # AccountValidationError and a consistent user message returned (i.e. both should
        # return "It looks like {username} belongs to an existing account. Try again with a
        # different username.")
        if username_exists_or_retired(user.username):
            raise AccountValidationError(
                USERNAME_EXISTS_MSG_FMT.format(username=proposed_username),
                field="username"
            )
        elif email_exists_or_retired(user.email):
            raise AccountValidationError(
                _("An account with the Email '{email}' already exists.").format(email=user.email),
                field="email"
            )
        else:
            raise

    registration.register(user)

    profile_fields = [
        "name", "level_of_education", "gender", "mailing_address", "city", "country", "goals",
        "year_of_birth"
    ]
    profile = UserProfile(
        user=user,
        **{key: form.cleaned_data.get(key) for key in profile_fields}
    )
    extended_profile = form.cleaned_extended_profile
    if extended_profile:
        profile.meta = json.dumps(extended_profile)
    try:
        profile.save()
    except Exception:
        log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
        raise

    return user, profile, registration
Beispiel #13
0
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
    """
    A wrapper around django.contrib.auth.views.password_reset_confirm.
    Needed because we want to set the user as active at this step.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name": configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
    }

    # User can not get this link unless account recovery feature is enabled.
    if 'is_account_recovery' in request.GET and not is_secondary_email_feature_enabled():
        raise Http404

    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if request.method == 'POST':
        # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
        # We have to use request.POST because the password_reset_confirm method takes in the request and a user's
        # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
        # field so it passes the equivalence check that new_password1 == new_password2
        # In order to switch out of having to do this copy, we would want to move the normalize_password code into
        # a custom User model's set_password method to ensure it is always happening upon calling set_password.
        request.POST = request.POST.copy()
        request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
        request.POST['new_password2'] = normalize_password(request.POST['new_password2'])

        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': ' '.join(err.messages),
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context
            )

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        if 'is_account_recovery' in request.GET:
            response = password_reset_confirm(
                request,
                uidb64=uidb64,
                token=token,
                extra_context=platform_name,
                template_name='registration/password_reset_confirm.html',
                post_reset_redirect='signin_user',
            )
        else:
            response = password_reset_confirm(
                request, uidb64=uidb64, token=token, extra_context=platform_name
            )

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid() if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _('Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)
        if 'is_account_recovery' in request.GET:
            try:
                updated_user.email = updated_user.account_recovery.secondary_email
                updated_user.account_recovery.delete()
                # emit an event that the user changed their secondary email to the primary email
                tracker.emit(
                    SETTING_CHANGE_INITIATED,
                    {
                        "setting": "email",
                        "old": user.email,
                        "new": updated_user.email,
                        "user_id": updated_user.id,
                    }
                )
            except ObjectDoesNotExist:
                log.error(
                    'Account recovery process initiated without AccountRecovery instance for user {username}'.format(
                        username=updated_user.username
                    )
                )

        updated_user.save()

        if response.status_code == 302 and 'is_account_recovery' in request.GET:
            messages.success(
                request,
                HTML(_(
                    '{html_start}Password Creation Complete{html_end}'
                    'Your password has been created. {bold_start}{email}{bold_end} is now your primary login email.'
                )).format(
                    support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                    bold_start=HTML('<b>'),
                    bold_end=HTML('</b>'),
                    email=updated_user.email,
                ),
                extra_tags='account-recovery aa-icon submission-success'
            )
    else:
        response = password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response
def password_reset_logistration(request, **kwargs):
    """Reset learner password using passed token and new credentials"""

    reset_status = False
    uidb36 = kwargs.get('uidb36')
    token = kwargs.get('token')

    has_required_values, uid_int = _check_token_has_required_values(
        uidb36, token)
    if not has_required_values:
        AUDIT_LOG.exception("Invalid password reset confirm token")
        return JsonResponse({'reset_status': reset_status})

    request.POST = request.POST.copy()
    request.POST['new_password1'] = normalize_password(
        request.POST['new_password1'])
    request.POST['new_password2'] = normalize_password(
        request.POST['new_password2'])

    password = request.POST['new_password1']
    try:
        user = User.objects.get(id=uid_int)
        if not default_token_generator.check_token(user, token):
            AUDIT_LOG.exception("Token validation failed")
            return JsonResponse({'reset_status': reset_status})

        validate_password(password, user=user)
        form = SetPasswordForm(user, request.POST)
        if form.is_valid():
            form.save()
            reset_status = True

            if 'is_account_recovery' in request.GET:
                try:
                    old_primary_email = user.email
                    user.email = user.account_recovery.secondary_email
                    user.account_recovery.delete()
                    # emit an event that the user changed their secondary email to the primary email
                    tracker.emit(
                        SETTING_CHANGE_INITIATED, {
                            "setting": "email",
                            "old": old_primary_email,
                            "new": user.email,
                            "user_id": user.id,
                        })
                    user.save()
                    send_password_reset_success_email(user, request)
                except ObjectDoesNotExist:
                    log.error(
                        'Account recovery process initiated without AccountRecovery instance for user {username}'
                        .format(username=user.username))
    except ValidationError as err:
        AUDIT_LOG.exception("Password validation failed")
        error_status = {
            'reset_status': reset_status,
            'err_msg': ' '.join(err.messages)
        }
        return JsonResponse(error_status)
    except Exception:  # pylint: disable=broad-except
        AUDIT_LOG.exception("Setting new password failed")

    return JsonResponse({'reset_status': reset_status})
Beispiel #15
0
def create_account(username, password, email):
    """Create a new user account.

    This will implicitly create an empty profile for the user.

    WARNING: This function does NOT yet implement all the features
    in `student/views.py`.  Until it does, please use this method
    ONLY for tests of the account API, not in production code.
    In particular, these are currently missing:

    * 3rd party auth
    * External auth (shibboleth)

    In addition, we assume that some functionality is handled
    at higher layers:

    * Analytics events
    * Activation email
    * Terms of service / honor code checking
    * Recording demographic info (use profile API)
    * Auto-enrollment in courses (if invited via instructor dash)

    Args:
        username (unicode): The username for the new account.
        password (unicode): The user's password.
        email (unicode): The email address associated with the account.

    Returns:
        unicode: an activation key for the account.

    Raises:
        errors.AccountUserAlreadyExists
        errors.AccountUsernameInvalid
        errors.AccountEmailInvalid
        errors.AccountPasswordInvalid
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)

    # Validate the username, password, and email
    # This will raise an exception if any of these are not in a valid format.
    _validate_username(username)
    _validate_password(password, username, email)
    _validate_email(email)

    # Create the user account, setting them to "inactive" until they activate their account.
    user = User(username=username, email=email, is_active=False)
    password = normalize_password(password)
    user.set_password(password)

    try:
        user.save()
    except IntegrityError:
        raise errors.AccountUserAlreadyExists

    # Create a registration to track the activation process
    # This implicitly saves the registration.
    registration = Registration()
    registration.register(user)

    # Create an empty user profile with default values
    UserProfile(user=user).save()

    # Return the activation key, which the caller should send to the user
    return registration.activation_key
Beispiel #16
0
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
    """
    A wrapper around django.contrib.auth.views.password_reset_confirm.
    Needed because we want to set the user as active at this step.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name": configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
    }

    # User can not get this link unless account recovery feature is enabled.
    if 'is_account_recovery' in request.GET and not is_secondary_email_feature_enabled():
        raise Http404

    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if request.method == 'POST':
        # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
        # We have to use request.POST because the password_reset_confirm method takes in the request and a user's
        # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
        # field so it passes the equivalence check that new_password1 == new_password2
        # In order to switch out of having to do this copy, we would want to move the normalize_password code into
        # a custom User model's set_password method to ensure it is always happening upon calling set_password.
        request.POST = request.POST.copy()
        request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
        request.POST['new_password2'] = normalize_password(request.POST['new_password2'])

        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': ' '.join(err.messages),
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context
            )

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        if 'is_account_recovery' in request.GET:
            response = password_reset_confirm(
                request,
                uidb64=uidb64,
                token=token,
                extra_context=platform_name,
                template_name='registration/password_reset_confirm.html',
                post_reset_redirect='signin_user',
            )
        else:
            response = password_reset_confirm(
                request, uidb64=uidb64, token=token, extra_context=platform_name
            )

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid() if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _('Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)
        if 'is_account_recovery' in request.GET:
            try:
                updated_user.email = updated_user.account_recovery.secondary_email
                updated_user.account_recovery.delete()
                # emit an event that the user changed their secondary email to the primary email
                tracker.emit(
                    SETTING_CHANGE_INITIATED,
                    {
                        "setting": "email",
                        "old": user.email,
                        "new": updated_user.email,
                        "user_id": updated_user.id,
                    }
                )
            except ObjectDoesNotExist:
                log.error(
                    'Account recovery process initiated without AccountRecovery instance for user {username}'.format(
                        username=updated_user.username
                    )
                )

        updated_user.save()

        if response.status_code == 302 and 'is_account_recovery' in request.GET:
            messages.success(
                request,
                HTML(_(
                    '{html_start}Password Creation Complete{html_end}'
                    'Your password has been created. {bold_start}{email}{bold_end} is now your primary login email.'
                )).format(
                    support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                    bold_start=HTML('<b>'),
                    bold_end=HTML('</b>'),
                    email=updated_user.email,
                ),
                extra_tags='account-recovery aa-icon submission-success'
            )
    else:
        response = password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response