def activate_account(activation_key): """Activate a user's account. Args: activation_key (unicode): The activation key the user received via email. Returns: None Raises: errors.UserNotAuthorized errors.UserAPIInternalError: the operation failed due to an unexpected error. """ # TODO: Confirm this `activate_account` is only used for tests. If so, this should not be used for tests, and we # should instead use the `activate_account` used for /activate. set_custom_metric('user_api_activate_account', 'True') if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG) try: registration = Registration.objects.get(activation_key=activation_key) except Registration.DoesNotExist: raise errors.UserNotAuthorized else: # This implicitly saves the registration registration.activate()
def dispatch(self, *args, **kwargs): self.uidb36 = kwargs.get('uidb36') self.token = kwargs.get('token') self.uidb64 = _uidb36_to_uidb64(self.uidb36) # User can not get this link unless account recovery feature is enabled. if 'is_account_recovery' in self.request.GET and not is_secondary_email_feature_enabled( ): raise Http404 response = self._set_user(self.request) if response: return response if UserRetirementRequest.has_user_requested_retirement(self.user): return self._handle_retired_user(self.request) if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return self._handle_system_unavailability(self.request) if self.request.method == 'POST': return self.post(self.request, *args, **kwargs) else: self._set_token_in_session(self.request, self.token) token = self.reset_url_token response = super(PasswordResetConfirmWrapper, self).dispatch(self.request, uidb64=self.uidb64, token=token, extra_context=self.platform_name) response_was_successful = response.context_data.get('validlink') if response_was_successful and not self.user.is_active: self.user.is_active = True self.user.save() return response
def activate_account_studio(request, key): """ When link in activation e-mail is clicked and the link belongs to studio. """ try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): return render_to_response( "registration/activation_invalid.html", {'csrf': csrf(request)['csrf_token']} ) else: user_logged_in = request.user.is_authenticated already_active = True if not registration.user.is_active: if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('registration/activation_invalid.html', {'csrf': csrf(request)['csrf_token']}) registration.activate() already_active = False return render_to_response( "registration/activation_complete.html", { 'user_logged_in': user_logged_in, 'already_active': already_active } )
def test_activate_account_prevent_auth_user_writes(self): activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL) with pytest.raises(UserAPIInternalError, message=SYSTEM_MAINTENANCE_MSG): with waffle().override(PREVENT_AUTH_USER_WRITES, True): activate_account(activation_key)
def activate_account_studio(request, key): """ When link in activation e-mail is clicked and the link belongs to studio. """ try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): return render_to_response( "registration/activation_invalid.html", {'csrf': csrf(request)['csrf_token']} ) else: user_logged_in = request.user.is_authenticated already_active = True if not registration.user.is_active: if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('registration/activation_invalid.html', {'csrf': csrf(request)['csrf_token']}) registration.activate() already_active = False return render_to_response( "registration/activation_complete.html", { 'user_logged_in': user_logged_in, 'already_active': already_active } )
def update_last_login(sender, user, **kwargs): # pylint: disable=unused-argument """ Replacement for Django's ``user_logged_in`` signal handler that knows not to attempt updating the ``last_login`` field when we're trying to avoid writes to the ``auth_user`` table while running a migration. """ if not waffle().is_enabled(PREVENT_AUTH_USER_WRITES): user.last_login = timezone.now() user.save(update_fields=['last_login'])
def update_last_login(sender, user, **kwargs): # pylint: disable=unused-argument """ Replacement for Django's ``user_logged_in`` signal handler that knows not to attempt updating the ``last_login`` field when we're trying to avoid writes to the ``auth_user`` table while running a migration. """ if not waffle().is_enabled(PREVENT_AUTH_USER_WRITES): user.last_login = timezone.now() user.save(update_fields=['last_login'])
def test_account_activation_prevent_auth_user_writes(self): login_page_url = "{login_url}?next={redirect_url}".format( login_url=reverse('signin_user'), redirect_url=reverse('dashboard'), ) with waffle().override(PREVENT_AUTH_USER_WRITES, True): response = self.client.get(reverse('activate', args=[self.registration.activation_key]), follow=True) self.assertRedirects(response, login_page_url) self.assertContains(response, SYSTEM_MAINTENANCE_MSG) assert not self.user.is_active
def test_account_activation_prevent_auth_user_writes(self): login_page_url = "{login_url}?next={redirect_url}".format( login_url=reverse('signin_user'), redirect_url=reverse('dashboard'), ) with waffle().override(PREVENT_AUTH_USER_WRITES, True): response = self.client.get(reverse('activate', args=[self.registration.activation_key]), follow=True) self.assertRedirects(response, login_page_url) self.assertContains(response, SYSTEM_MAINTENANCE_MSG) assert not self.user.is_active
def test_password_reset_prevent_auth_user_writes(self): with waffle().override(PREVENT_AUTH_USER_WRITES, True): url = reverse( "password_reset_confirm", kwargs={"uidb36": self.uidb36, "token": self.token} ) for request in [self.request_factory.get(url), self.request_factory.post(url)]: response = password_reset_confirm_wrapper(request, self.uidb36, self.token) assert response.context_data['err_msg'] == SYSTEM_MAINTENANCE_MSG self.user.refresh_from_db() assert not self.user.is_active
def test_password_reset_prevent_auth_user_writes(self): with waffle().override(PREVENT_AUTH_USER_WRITES, True): url = reverse( "password_reset_confirm", kwargs={"uidb36": self.uidb36, "token": self.token} ) for request in [self.request_factory.get(url), self.request_factory.post(url)]: response = password_reset_confirm_wrapper(request, self.uidb36, self.token) assert response.context_data['err_msg'] == SYSTEM_MAINTENANCE_MSG self.user.refresh_from_db() assert not self.user.is_active
def create_account(request, post_override=None): """ Deprecated. Use RegistrationView instead. JSON call to create new edX account. Used by form in signup_modal.html, which is included into header.html """ # 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): return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG) warnings.warn("Please use RegistrationView instead.", DeprecationWarning) try: user = create_account_with_params(request, post_override or request.POST) except AccountValidationError as exc: return JsonResponse( { 'success': False, 'value': text_type(exc), 'field': exc.field }, status=400) except ValidationError as exc: field, error_list = next(iteritems(exc.message_dict)) return JsonResponse( { "success": False, "field": field, "value": ' '.join(error_list), }, status=400) redirect_url = None # The AJAX method calling should know the default destination upon success # Resume the third-party-auth pipeline if necessary. if third_party_auth.is_enabled() and pipeline.running(request): running_pipeline = pipeline.get(request) redirect_url = pipeline.get_complete_url(running_pipeline['backend']) response = JsonResponse({ 'success': True, 'redirect_url': redirect_url, }) set_logged_in_cookies(request, response, user) return response
def create_account(request, post_override=None): """ Deprecated. Use RegistrationView instead. JSON call to create new edX account. Used by form in signup_modal.html, which is included into header.html """ # 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): return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG) warnings.warn("Please use RegistrationView instead.", DeprecationWarning) try: user = create_account_with_params(request, post_override or request.POST) except AccountValidationError as exc: return JsonResponse({'success': False, 'value': text_type(exc), 'field': exc.field}, status=400) except ValidationError as exc: field, error_list = next(iteritems(exc.message_dict)) return JsonResponse( { "success": False, "field": field, "value": ' '.join(error_list), }, status=400 ) redirect_url = None # The AJAX method calling should know the default destination upon success # Resume the third-party-auth pipeline if necessary. if third_party_auth.is_enabled() and pipeline.running(request): running_pipeline = pipeline.get(request) redirect_url = pipeline.get_complete_url(running_pipeline['backend']) response = JsonResponse({ 'success': True, 'redirect_url': redirect_url, }) set_logged_in_cookies(request, response, user) return response
def activate_account(activation_key): """Activate a user's account. Args: activation_key (unicode): The activation key the user received via email. Returns: None Raises: errors.UserNotAuthorized errors.UserAPIInternalError: the operation failed due to an unexpected error. """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG) try: registration = Registration.objects.get(activation_key=activation_key) except Registration.DoesNotExist: raise errors.UserNotAuthorized else: # This implicitly saves the registration registration.activate()
def activate_account(activation_key): """Activate a user's account. Args: activation_key (unicode): The activation key the user received via email. Returns: None Raises: errors.UserNotAuthorized errors.UserAPIInternalError: the operation failed due to an unexpected error. """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG) try: registration = Registration.objects.get(activation_key=activation_key) except Registration.DoesNotExist: raise errors.UserNotAuthorized else: # This implicitly saves the registration registration.activate()
def test_login_success_prevent_auth_user_writes(self): with waffle().override(PREVENT_AUTH_USER_WRITES, True): old_last_login = self.user.last_login self.test_login_success() self.user.refresh_from_db() assert old_last_login == self.user.last_login
def activate_account(request, key): """ When link in activation e-mail is clicked """ # If request is in Studio call the appropriate view if theming_helpers.get_project_root_name().lower() == u'cms': return activate_account_studio(request, key) try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): messages.error( request, HTML(_( '{html_start}Your account could not be activated{html_end}' 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.' )).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>'), ), extra_tags='account-activation aa-icon' ) else: if registration.user.is_active: messages.info( request, HTML(_('{html_start}This account has already been activated.{html_end}')).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) elif waffle().is_enabled(PREVENT_AUTH_USER_WRITES): messages.error( request, HTML(u'{html_start}{message}{html_end}').format( message=Text(SYSTEM_MAINTENANCE_MSG), html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) else: registration.activate() # Success message for logged in users. message = _('{html_start}Success{html_end} You have activated your account.') if not request.user.is_authenticated: # Success message for logged out users message = _( '{html_start}Success! You have activated your account.{html_end}' 'You will now receive email updates and alerts from us related to' ' the courses you are enrolled in. Sign In to continue.' ) # Add message for later use. messages.success( request, HTML(message).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) return redirect('dashboard')
def confirm_email_change(request, key): # pylint: disable=unused-argument """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG}) 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 subject = render_to_string('emails/email_change_subject.txt', address_context) subject = ''.join(subject.splitlines()) message = render_to_string('emails/confirm_email_change.txt', address_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: user.email_user( subject, message, configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)) 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... try: user.email_user( subject, message, configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)) 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
def test_activate_account_prevent_auth_user_writes(self): activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL) with pytest.raises(UserAPIInternalError, message=SYSTEM_MAINTENANCE_MSG): with waffle().override(PREVENT_AUTH_USER_WRITES, True): activate_account(activation_key)
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) * Complex password policies (ENFORCE_PASSWORD_POLICY) 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) _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) 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
def confirm_email_change(request, key): # pylint: disable=unused-argument """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG}) 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: ace.send(msg) 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: ace.send(msg) 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
def test_prevent_auth_user_writes(self, email_user): # pylint: disable=unused-argument with waffle().override(PREVENT_AUTH_USER_WRITES, True): self.check_confirm_email_change('email_change_failed.html', { 'err_msg': SYSTEM_MAINTENANCE_MSG }) self.assertRolledBack()
def confirm_email_change(request, key): # pylint: disable=unused-argument """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG}) 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: ace.send(msg) 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: ace.send(msg) 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
def test_create_account_prevent_auth_user_writes(self): with waffle().override(PREVENT_AUTH_USER_WRITES, True): response = self.client.get(self.url) assert response.status_code == 403
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) * Complex password policies (ENFORCE_PASSWORD_POLICY) 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) _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) 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
def confirm_email_change(request, key): # pylint: disable=unused-argument """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG}) 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 subject = render_to_string('emails/email_change_subject.txt', address_context) subject = ''.join(subject.splitlines()) message = render_to_string('emails/confirm_email_change.txt', address_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: user.email_user( subject, message, configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) ) 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... try: user.email_user( subject, message, configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) ) 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
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': 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': err.message, } 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
def test_create_account_prevent_auth_user_writes(self): with waffle().override(PREVENT_AUTH_USER_WRITES, True): response = self.client.get(self.url) assert response.status_code == 403
def activate_account(request, key): """ When link in activation e-mail is clicked """ # If request is in Studio call the appropriate view if theming_helpers.get_project_root_name().lower() == u'cms': monitoring_utils.set_custom_metric('student_activate_account', 'cms') return activate_account_studio(request, key) # TODO: Use metric to determine if there are any `activate_account` calls for cms in Production. # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests. monitoring_utils.set_custom_metric('student_activate_account', 'lms') try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): messages.error( request, HTML( _('{html_start}Your account could not be activated{html_end}' 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.' )).format( support_url=configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK, html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon') else: if registration.user.is_active: messages.info( request, HTML( _('{html_start}This account has already been activated.{html_end}' )).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) elif waffle().is_enabled(PREVENT_AUTH_USER_WRITES): messages.error( request, HTML(u'{html_start}{message}{html_end}').format( message=Text(SYSTEM_MAINTENANCE_MSG), html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) else: registration.activate() # Success message for logged in users. message = _( '{html_start}Success{html_end} You have activated your account.' ) if not request.user.is_authenticated: # Success message for logged out users message = _( '{html_start}Success! You have activated your account.{html_end}' 'You will now receive email updates and alerts from us related to' ' the courses you are enrolled in. Sign In to continue.') # Add message for later use. messages.success( request, HTML(message).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) return redirect('dashboard')
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 test_login_success_prevent_auth_user_writes(self): with waffle().override(PREVENT_AUTH_USER_WRITES, True): old_last_login = self.user.last_login self.test_login_success() self.user.refresh_from_db() assert old_last_login == self.user.last_login
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 test_prevent_auth_user_writes(self, email_user): # pylint: disable=unused-argument with waffle().override(PREVENT_AUTH_USER_WRITES, True): self.check_confirm_email_change('email_change_failed.html', { 'err_msg': SYSTEM_MAINTENANCE_MSG }) self.assertRolledBack()
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': 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': err.message, } 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