Merge pull request #7140 from edx/christina/delete-enable-new-dashboard
Delete ENABLE_NEW_DASHBOARD code.
This commit is contained in:
@@ -309,32 +309,6 @@ class UserProfile(models.Model):
|
||||
self.name = new_name
|
||||
self.save()
|
||||
|
||||
@transaction.commit_on_success
|
||||
def update_email(self, new_email):
|
||||
"""Update the user's email and save the change in the history.
|
||||
|
||||
Implicitly saves the model.
|
||||
If the new email is the same as the old email, do not update the history.
|
||||
|
||||
Arguments:
|
||||
new_email (unicode): The new email for the user.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if self.user.email == new_email:
|
||||
return
|
||||
|
||||
meta = self.get_meta()
|
||||
if 'old_emails' not in meta:
|
||||
meta['old_emails'] = []
|
||||
meta['old_emails'].append([self.user.email, datetime.now(UTC).isoformat()])
|
||||
self.set_meta(meta)
|
||||
self.save()
|
||||
|
||||
self.user.email = new_email
|
||||
self.user.save()
|
||||
|
||||
|
||||
class UserSignupSource(models.Model):
|
||||
"""
|
||||
|
||||
@@ -112,7 +112,6 @@ AUTH_EMAIL_OPT_IN_KEY = 'email_opt_in'
|
||||
|
||||
AUTH_ENTRY_DASHBOARD = 'dashboard'
|
||||
AUTH_ENTRY_LOGIN = 'login'
|
||||
AUTH_ENTRY_PROFILE = 'profile'
|
||||
AUTH_ENTRY_REGISTER = 'register'
|
||||
|
||||
# This is left-over from an A/B test
|
||||
@@ -143,16 +142,11 @@ AUTH_DISPATCH_URLS = {
|
||||
AUTH_ENTRY_LOGIN_2: '/account/login/',
|
||||
AUTH_ENTRY_REGISTER_2: '/account/register/',
|
||||
|
||||
# If linking/unlinking an account from the new student profile
|
||||
# page, redirect to the profile page. Only used if
|
||||
# `FEATURES['ENABLE_NEW_DASHBOARD']` is true.
|
||||
AUTH_ENTRY_PROFILE: '/profile/',
|
||||
}
|
||||
|
||||
_AUTH_ENTRY_CHOICES = frozenset([
|
||||
AUTH_ENTRY_DASHBOARD,
|
||||
AUTH_ENTRY_LOGIN,
|
||||
AUTH_ENTRY_PROFILE,
|
||||
AUTH_ENTRY_REGISTER,
|
||||
|
||||
# This is left-over from an A/B test
|
||||
@@ -452,8 +446,6 @@ def parse_query_params(strategy, response, *args, **kwargs):
|
||||
'is_login': auth_entry in [AUTH_ENTRY_LOGIN, AUTH_ENTRY_LOGIN_2],
|
||||
# Whether the auth pipeline entered from /register.
|
||||
'is_register': auth_entry in [AUTH_ENTRY_REGISTER, AUTH_ENTRY_REGISTER_2],
|
||||
# Whether the auth pipeline entered from /profile.
|
||||
'is_profile': auth_entry == AUTH_ENTRY_PROFILE,
|
||||
# Whether the auth pipeline entered from an API
|
||||
'is_api': auth_entry == AUTH_ENTRY_API,
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ _MIDDLEWARE_CLASSES = (
|
||||
'third_party_auth.middleware.ExceptionMiddleware',
|
||||
)
|
||||
_SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/dashboard'
|
||||
_SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = '/profile'
|
||||
_SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = '/profile'
|
||||
|
||||
|
||||
def _merge_auth_info(django_settings, auth_info):
|
||||
@@ -97,11 +95,6 @@ def _set_global_settings(django_settings):
|
||||
# Where to send the user once social authentication is successful.
|
||||
django_settings.SOCIAL_AUTH_LOGIN_REDIRECT_URL = _SOCIAL_AUTH_LOGIN_REDIRECT_URL
|
||||
|
||||
# Change redirects to the profile page if we enable the new dashboard.
|
||||
if django_settings.FEATURES.get('ENABLE_NEW_DASHBOARD', ''):
|
||||
django_settings.SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = _SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL
|
||||
django_settings.SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = _SOCIAL_AUTH_DISCONNECT_REDIRECT_URL
|
||||
|
||||
# Inject our customized auth pipeline. All auth backends must work with
|
||||
# this pipeline.
|
||||
django_settings.SOCIAL_AUTH_PIPELINE = (
|
||||
|
||||
@@ -59,7 +59,6 @@ class StudentAccountUpdateTest(UrlResetMixin, TestCase):
|
||||
|
||||
INVALID_KEY = u"123abc"
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_NEW_DASHBOARD': True})
|
||||
def setUp(self):
|
||||
super(StudentAccountUpdateTest, self).setUp("student_account.urls")
|
||||
|
||||
@@ -71,141 +70,6 @@ class StudentAccountUpdateTest(UrlResetMixin, TestCase):
|
||||
result = self.client.login(username=self.USERNAME, password=self.OLD_PASSWORD)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_index(self):
|
||||
response = self.client.get(reverse('account_index'))
|
||||
self.assertContains(response, "Student Account")
|
||||
|
||||
def test_change_email(self):
|
||||
response = self._change_email(self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
# Verify that the email associated with the account remains unchanged
|
||||
profile_info = profile_api.profile_info(self.USERNAME)
|
||||
self.assertEquals(profile_info['email'], self.OLD_EMAIL)
|
||||
|
||||
# Check that an email was sent with the activation key
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self._assert_email(
|
||||
mail.outbox[0],
|
||||
[self.NEW_EMAIL],
|
||||
u"Email Change Request",
|
||||
u"There was recently a request to change the email address"
|
||||
)
|
||||
|
||||
# Retrieve the activation key from the email
|
||||
email_body = mail.outbox[0].body
|
||||
result = re.search('/email/confirmation/([^ \n]+)', email_body)
|
||||
self.assertIsNot(result, None)
|
||||
activation_key = result.group(1)
|
||||
|
||||
# Attempt to activate the email
|
||||
response = self.client.get(reverse('email_change_confirm', kwargs={'key': activation_key}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Verify that the email was changed
|
||||
profile_info = profile_api.profile_info(self.USERNAME)
|
||||
self.assertEquals(profile_info['email'], self.NEW_EMAIL)
|
||||
|
||||
# Verify that notification emails were sent
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
self._assert_email(
|
||||
mail.outbox[1],
|
||||
[self.OLD_EMAIL, self.NEW_EMAIL],
|
||||
u"Email Change Successful",
|
||||
u"You successfully changed the email address"
|
||||
)
|
||||
|
||||
def test_email_change_wrong_password(self):
|
||||
response = self._change_email(self.NEW_EMAIL, "wrong password")
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_email_change_request_no_user(self):
|
||||
# Patch account API to raise an internal error when an email change is requested
|
||||
with mock.patch('student_account.views.account_api.request_email_change') as mock_call:
|
||||
mock_call.side_effect = account_api.AccountUserNotFound
|
||||
response = self._change_email(self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
|
||||
self.assertEquals(response.status_code, 400)
|
||||
|
||||
def test_email_change_request_email_taken_by_active_account(self):
|
||||
# Create/activate a second user with the new email
|
||||
activation_key = account_api.create_account(self.ALTERNATE_USERNAME, self.OLD_PASSWORD, self.NEW_EMAIL)
|
||||
account_api.activate_account(activation_key)
|
||||
|
||||
# Request to change the original user's email to the email now used by the second user
|
||||
response = self._change_email(self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
self.assertEquals(response.status_code, 409)
|
||||
|
||||
def test_email_change_request_email_taken_by_inactive_account(self):
|
||||
# Create a second user with the new email, but don't active them
|
||||
account_api.create_account(self.ALTERNATE_USERNAME, self.OLD_PASSWORD, self.NEW_EMAIL)
|
||||
|
||||
# Request to change the original user's email to the email used by the inactive user
|
||||
response = self._change_email(self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
@ddt.data(*INVALID_EMAILS)
|
||||
def test_email_change_request_email_invalid(self, invalid_email):
|
||||
# Request to change the user's email to an invalid address
|
||||
response = self._change_email(invalid_email, self.OLD_PASSWORD)
|
||||
self.assertEquals(response.status_code, 400)
|
||||
|
||||
def test_email_change_confirmation(self):
|
||||
# Get an email change activation key
|
||||
activation_key = account_api.request_email_change(self.USERNAME, self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
|
||||
# Follow the link sent in the confirmation email
|
||||
response = self.client.get(reverse('email_change_confirm', kwargs={'key': activation_key}))
|
||||
self.assertContains(response, "Email change successful")
|
||||
|
||||
# Verify that the email associated with the account has changed
|
||||
profile_info = profile_api.profile_info(self.USERNAME)
|
||||
self.assertEquals(profile_info['email'], self.NEW_EMAIL)
|
||||
|
||||
def test_email_change_confirmation_invalid_key(self):
|
||||
# Visit the confirmation page with an invalid key
|
||||
response = self.client.get(reverse('email_change_confirm', kwargs={'key': self.INVALID_KEY}))
|
||||
self.assertContains(response, "Something went wrong")
|
||||
|
||||
# Verify that the email associated with the account has not changed
|
||||
profile_info = profile_api.profile_info(self.USERNAME)
|
||||
self.assertEquals(profile_info['email'], self.OLD_EMAIL)
|
||||
|
||||
def test_email_change_confirmation_email_already_exists(self):
|
||||
# Get an email change activation key
|
||||
email_activation_key = account_api.request_email_change(self.USERNAME, self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
|
||||
# Create/activate a second user with the new email
|
||||
account_activation_key = account_api.create_account(self.ALTERNATE_USERNAME, self.OLD_PASSWORD, self.NEW_EMAIL)
|
||||
account_api.activate_account(account_activation_key)
|
||||
|
||||
# Follow the link sent to the original user
|
||||
response = self.client.get(reverse('email_change_confirm', kwargs={'key': email_activation_key}))
|
||||
self.assertContains(response, "address you wanted to use is already used")
|
||||
|
||||
# Verify that the email associated with the original account has not changed
|
||||
profile_info = profile_api.profile_info(self.USERNAME)
|
||||
self.assertEquals(profile_info['email'], self.OLD_EMAIL)
|
||||
|
||||
def test_email_change_confirmation_internal_error(self):
|
||||
# Get an email change activation key
|
||||
activation_key = account_api.request_email_change(self.USERNAME, self.NEW_EMAIL, self.OLD_PASSWORD)
|
||||
|
||||
# Patch account API to return an internal error
|
||||
with mock.patch('student_account.views.account_api.confirm_email_change') as mock_call:
|
||||
mock_call.side_effect = account_api.AccountInternalError
|
||||
response = self.client.get(reverse('email_change_confirm', kwargs={'key': activation_key}))
|
||||
|
||||
self.assertContains(response, "Something went wrong")
|
||||
|
||||
def test_email_change_request_missing_email_param(self):
|
||||
response = self._change_email(None, self.OLD_PASSWORD)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_email_change_request_missing_password_param(self):
|
||||
response = self._change_email(self.OLD_EMAIL, None)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
|
||||
def test_password_change(self):
|
||||
# Request a password change while logged in, simulating
|
||||
@@ -316,25 +180,6 @@ class StudentAccountUpdateTest(UrlResetMixin, TestCase):
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
@ddt.data(
|
||||
('get', 'account_index', []),
|
||||
('post', 'email_change_request', []),
|
||||
('get', 'email_change_confirm', [123])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_require_login(self, method, url_name, args):
|
||||
# Access the page while logged out
|
||||
self.client.logout()
|
||||
url = reverse(url_name, args=args)
|
||||
response = getattr(self.client, method)(url, follow=True)
|
||||
|
||||
# Should have been redirected to the login page
|
||||
self.assertEqual(len(response.redirect_chain), 1)
|
||||
self.assertIn('accounts/login?next=', response.redirect_chain[0][0])
|
||||
|
||||
@ddt.data(
|
||||
('get', 'account_index', []),
|
||||
('post', 'email_change_request', []),
|
||||
('get', 'email_change_confirm', [123]),
|
||||
('post', 'password_change_request', []),
|
||||
)
|
||||
@ddt.unpack
|
||||
@@ -346,24 +191,6 @@ class StudentAccountUpdateTest(UrlResetMixin, TestCase):
|
||||
response = getattr(self.client, method)(url)
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
def _assert_email(self, email, expected_to, expected_subject, expected_body):
|
||||
"""Check whether an email has the correct properties. """
|
||||
self.assertEqual(email.to, expected_to)
|
||||
self.assertIn(expected_subject, email.subject)
|
||||
self.assertIn(expected_body, email.body)
|
||||
|
||||
def _change_email(self, new_email, password):
|
||||
"""Request to change the user's email. """
|
||||
data = {}
|
||||
|
||||
if new_email is not None:
|
||||
data['email'] = new_email
|
||||
if password is not None:
|
||||
# We can't pass a Unicode object to urlencode, so we encode the Unicode object
|
||||
data['password'] = password.encode('utf-8')
|
||||
|
||||
return self.client.post(path=reverse('email_change_request'), data=data)
|
||||
|
||||
def _change_password(self, email=None):
|
||||
"""Request to change the user's password. """
|
||||
data = {}
|
||||
|
||||
@@ -11,11 +11,3 @@ if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
|
||||
url(r'^register/$', 'login_and_registration_form', {'initial_mode': 'register'}, name='account_register'),
|
||||
url(r'^password$', 'password_change_request_handler', name='password_change_request'),
|
||||
)
|
||||
|
||||
if settings.FEATURES.get('ENABLE_NEW_DASHBOARD'):
|
||||
urlpatterns += patterns(
|
||||
'student_account.views',
|
||||
url(r'^$', 'index', name='account_index'),
|
||||
url(r'^email$', 'email_change_request_handler', name='email_change_request'),
|
||||
url(r'^email/confirmation/(?P<key>[^/]*)$', 'email_change_confirmation_handler', name='email_change_confirm'),
|
||||
)
|
||||
|
||||
@@ -42,31 +42,6 @@ from student_account.helpers import auth_pipeline_urls
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['GET'])
|
||||
def index(request):
|
||||
"""Render the account info page.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 if the index page was sent successfully
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
|
||||
Example usage:
|
||||
|
||||
GET /account
|
||||
|
||||
"""
|
||||
return render_to_response(
|
||||
'student_account/index.html', {
|
||||
'disable_courseware_js': True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(['GET'])
|
||||
@ensure_csrf_cookie
|
||||
def login_and_registration_form(request, initial_mode="login"):
|
||||
@@ -124,145 +99,6 @@ def login_and_registration_form(request, initial_mode="login"):
|
||||
return render_to_response('student_account/login_and_register.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['POST'])
|
||||
@ensure_csrf_cookie
|
||||
def email_change_request_handler(request):
|
||||
"""Handle a request to change the user's email address.
|
||||
|
||||
Sends an email to the newly specified address containing a link
|
||||
to a confirmation page.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 if the confirmation email was sent successfully
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 400 if the format of the new email is incorrect, or if
|
||||
an email change is requested for a user which does not exist
|
||||
HttpResponse: 401 if the provided password (in the form) is incorrect
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
HttpResponse: 409 if the provided email is already in use
|
||||
|
||||
Example usage:
|
||||
|
||||
POST /account/email
|
||||
|
||||
"""
|
||||
username = request.user.username
|
||||
password = request.POST.get('password')
|
||||
new_email = request.POST.get('email')
|
||||
|
||||
if new_email is None:
|
||||
return HttpResponseBadRequest("Missing param 'email'")
|
||||
if password is None:
|
||||
return HttpResponseBadRequest("Missing param 'password'")
|
||||
|
||||
old_email = profile_api.profile_info(username)['email']
|
||||
|
||||
try:
|
||||
key = account_api.request_email_change(username, new_email, password)
|
||||
except (account_api.AccountEmailInvalid, account_api.AccountUserNotFound):
|
||||
return HttpResponseBadRequest()
|
||||
except account_api.AccountEmailAlreadyExists:
|
||||
return HttpResponse(status=409)
|
||||
except account_api.AccountNotAuthorized:
|
||||
return HttpResponse(status=401)
|
||||
|
||||
context = {
|
||||
'key': key,
|
||||
'old_email': old_email,
|
||||
'new_email': new_email,
|
||||
}
|
||||
|
||||
subject = render_to_string('student_account/emails/email_change_request/subject_line.txt', context)
|
||||
subject = ''.join(subject.splitlines())
|
||||
message = render_to_string('student_account/emails/email_change_request/message_body.txt', context)
|
||||
|
||||
from_address = microsite.get_value(
|
||||
'email_from_address',
|
||||
settings.DEFAULT_FROM_EMAIL
|
||||
)
|
||||
|
||||
# Send a confirmation email to the new address containing the activation key
|
||||
send_mail(subject, message, from_address, [new_email])
|
||||
|
||||
return HttpResponse(status=200)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['GET'])
|
||||
def email_change_confirmation_handler(request, key):
|
||||
"""Complete a change of the user's email address.
|
||||
|
||||
This is called when the activation link included in the confirmation
|
||||
email is clicked.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 if the email change is successful, the activation key
|
||||
is invalid, the new email is already in use, or the
|
||||
user to which the email change will be applied does
|
||||
not exist
|
||||
HttpResponse: 302 if not logged in (redirect to login page)
|
||||
HttpResponse: 405 if using an unsupported HTTP method
|
||||
|
||||
Example usage:
|
||||
|
||||
GET /account/email/confirmation/{key}
|
||||
|
||||
"""
|
||||
try:
|
||||
old_email, new_email = account_api.confirm_email_change(key)
|
||||
except account_api.AccountNotAuthorized:
|
||||
return render_to_response(
|
||||
'student_account/email_change_failed.html', {
|
||||
'disable_courseware_js': True,
|
||||
'error': 'key_invalid',
|
||||
}
|
||||
)
|
||||
except account_api.AccountEmailAlreadyExists:
|
||||
return render_to_response(
|
||||
'student_account/email_change_failed.html', {
|
||||
'disable_courseware_js': True,
|
||||
'error': 'email_used',
|
||||
}
|
||||
)
|
||||
except account_api.AccountInternalError:
|
||||
return render_to_response(
|
||||
'student_account/email_change_failed.html', {
|
||||
'disable_courseware_js': True,
|
||||
'error': 'internal',
|
||||
}
|
||||
)
|
||||
|
||||
context = {
|
||||
'old_email': old_email,
|
||||
'new_email': new_email,
|
||||
}
|
||||
|
||||
subject = render_to_string('student_account/emails/email_change_confirmation/subject_line.txt', context)
|
||||
subject = ''.join(subject.splitlines())
|
||||
message = render_to_string('student_account/emails/email_change_confirmation/message_body.txt', context)
|
||||
|
||||
from_address = microsite.get_value(
|
||||
'email_from_address',
|
||||
settings.DEFAULT_FROM_EMAIL
|
||||
)
|
||||
|
||||
# Notify both old and new emails of the change
|
||||
send_mail(subject, message, from_address, [old_email, new_email])
|
||||
|
||||
return render_to_response(
|
||||
'student_account/email_change_successful.html', {
|
||||
'disable_courseware_js': True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def password_change_request_handler(request):
|
||||
"""Handle password change requests originating from the account page.
|
||||
|
||||
@@ -313,9 +313,6 @@ FEATURES = {
|
||||
# ENABLE_OAUTH2_PROVIDER to True
|
||||
'ENABLE_MOBILE_REST_API': False,
|
||||
|
||||
# Enable the new dashboard, account, and profile pages
|
||||
'ENABLE_NEW_DASHBOARD': False,
|
||||
|
||||
# Enable the combined login/registration form
|
||||
'ENABLE_COMBINED_LOGIN_REGISTRATION': False,
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%inherit file="../main.html" />
|
||||
|
||||
<section class="container activation">
|
||||
<section class="message">
|
||||
<h1 class="invalid">${_("Email change failed.")}</h1>
|
||||
<hr class="horizontal-divider">
|
||||
|
||||
<p>
|
||||
% if error is 'key_invalid' or error is 'internal':
|
||||
${_("Something went wrong. Please contact {support} for help.").format(
|
||||
support="<a href='mailto:{support_email}'>{support_email}</a>".format(
|
||||
support_email=settings.TECH_SUPPORT_EMAIL
|
||||
)
|
||||
)}
|
||||
% elif error is 'email_used':
|
||||
${_("The email address you wanted to use is already used by another "
|
||||
"{platform_name} account.").format(platform_name=settings.PLATFORM_NAME)}
|
||||
% endif
|
||||
</p>
|
||||
|
||||
<p>
|
||||
${_("You can try again from the {link_start}account settings{link_end} page.").format(
|
||||
link_start="<a href='{url}'>".format(url=reverse('account_index')),
|
||||
link_end="</a>"
|
||||
)}
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,18 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%inherit file="../main.html" />
|
||||
|
||||
<section class="container activation">
|
||||
<section class="message">
|
||||
<h1 class="valid">${_("Email change successful!")}</h1>
|
||||
<hr class="horizontal-divider">
|
||||
|
||||
<p>
|
||||
${_("You should see your new email address listed on the "
|
||||
"{link_start}account settings{link_end} page.").format(
|
||||
link_start="<a href='{url}'>".format(url=reverse('account_index')),
|
||||
link_end="</a>",
|
||||
)}
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,21 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
## TODO: Get sign-off from Product on new copy, and think about
|
||||
## turning this into a large, multi-line message for i18n purposes.
|
||||
## Greeting
|
||||
${_("Hi there,")}
|
||||
|
||||
## Preamble
|
||||
${_("You successfully changed the email address associated with your "
|
||||
"{platform_name} account from {old_email} to {new_email}.").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
old_email=old_email,
|
||||
new_email=new_email
|
||||
)
|
||||
}
|
||||
|
||||
## Translators: This is the signature of an email. "\n" is a newline character,
|
||||
## and should be placed between the closing word and the signing team's name.
|
||||
${_("Thanks,\n - The {platform_name} Team").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
)}
|
||||
@@ -1,3 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
${_("{platform_name} Email Change Successful").format(platform_name=settings.PLATFORM_NAME)}
|
||||
@@ -1,34 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
## TODO: Get sign-off from Product on new copy, and think about
|
||||
## turning this into a large, multi-line message for i18n purposes.
|
||||
## Greeting
|
||||
${_("Hi there,")}
|
||||
|
||||
## Preamble
|
||||
${_("There was recently a request to change the email address associated "
|
||||
"with your {platform_name} account from {old_email} to {new_email}. "
|
||||
"If you requested this change, please confirm your new email address "
|
||||
"by following the link below:").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
old_email=old_email,
|
||||
new_email=new_email
|
||||
)
|
||||
}
|
||||
|
||||
## Confirmation link
|
||||
% if is_secure:
|
||||
https://${site}/account/email/confirmation/${key}
|
||||
% else:
|
||||
http://${site}/account/email/confirmation/${key}
|
||||
% endif
|
||||
|
||||
## Closing
|
||||
${_("If you don't want to change the email address associated with your "
|
||||
"account, ignore this message.")}
|
||||
|
||||
## Translators: This is the signature of an email. "\n" is a newline character,
|
||||
## and should be placed between the closing word and the signing team's name.
|
||||
${_("Thanks,\n - The {platform_name} Team").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
)}
|
||||
@@ -1,3 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
${_("{platform_name} Email Change Request").format(platform_name=settings.PLATFORM_NAME)}
|
||||
@@ -1,26 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
|
||||
<%inherit file="../main.html" />
|
||||
|
||||
<%block name="pagetitle">${_("Student Account")}</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
|
||||
<%static:js group='student_account'/>
|
||||
</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["account"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="student_account/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<h1>Student Account</h1>
|
||||
|
||||
<p>This is a placeholder for the student's account page.</p>
|
||||
|
||||
<div id="account-container"></div>
|
||||
@@ -45,11 +45,6 @@ class AccountUsernameAlreadyExists(AccountUserAlreadyExists):
|
||||
pass
|
||||
|
||||
|
||||
class AccountEmailAlreadyExists(AccountUserAlreadyExists):
|
||||
"""An account already exists with the requested email. """
|
||||
pass
|
||||
|
||||
|
||||
class AccountUsernameInvalid(AccountRequestError):
|
||||
"""The requested username is not in a valid format. """
|
||||
pass
|
||||
@@ -216,122 +211,6 @@ def activate_account(activation_key):
|
||||
# This implicitly saves the registration
|
||||
registration.activate()
|
||||
|
||||
|
||||
@intercept_errors(AccountInternalError, ignore_errors=[AccountRequestError])
|
||||
def request_email_change(username, new_email, password):
|
||||
"""Request an email change.
|
||||
|
||||
Users must confirm the change before we update their information.
|
||||
|
||||
Args:
|
||||
username (unicode): The username associated with the account.
|
||||
new_email (unicode): The user's new email address.
|
||||
password (unicode): The password the user entered to authorize the change.
|
||||
|
||||
Returns:
|
||||
unicode: an activation key for the account.
|
||||
|
||||
Raises:
|
||||
AccountUserNotFound
|
||||
AccountEmailAlreadyExists
|
||||
AccountEmailInvalid
|
||||
AccountNotAuthorized
|
||||
|
||||
"""
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
raise AccountUserNotFound
|
||||
|
||||
# Check the user's credentials
|
||||
if not user.check_password(password):
|
||||
raise AccountNotAuthorized
|
||||
|
||||
# Validate the email, raising an exception if it is not in the correct format
|
||||
_validate_email(new_email)
|
||||
|
||||
# Verify that no active account has taken the email in between
|
||||
# the request and the activation.
|
||||
# We'll check again before confirming and persisting the change,
|
||||
# but if the email is already taken by an active account, we should
|
||||
# let the user know as soon as possible.
|
||||
if User.objects.filter(email=new_email, is_active=True).exists():
|
||||
raise AccountEmailAlreadyExists
|
||||
|
||||
try:
|
||||
pending_change = PendingEmailChange.objects.get(user=user)
|
||||
except PendingEmailChange.DoesNotExist:
|
||||
pending_change = PendingEmailChange(user=user)
|
||||
|
||||
# Update the change (re-using the same record if it already exists)
|
||||
# This will generate a new activation key and save the record.
|
||||
return pending_change.request_change(new_email)
|
||||
|
||||
|
||||
@intercept_errors(AccountInternalError, ignore_errors=[AccountRequestError])
|
||||
@transaction.commit_on_success
|
||||
def confirm_email_change(activation_key):
|
||||
"""Confirm an email change.
|
||||
|
||||
Users can confirm the change by providing an activation key
|
||||
they received via email.
|
||||
|
||||
Args:
|
||||
activation_key (unicode): The activation key the user received
|
||||
when he/she requested the email change.
|
||||
|
||||
Returns:
|
||||
Tuple: (old_email, new_email)
|
||||
|
||||
Raises:
|
||||
AccountNotAuthorized: The activation code is invalid.
|
||||
AccountEmailAlreadyExists: Someone else has already taken the email address.
|
||||
AccountInternalError
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
# Activation key has a uniqueness constraint, so we're guaranteed to get
|
||||
# at most one pending change.
|
||||
pending_change = PendingEmailChange.objects.select_related('user').get(
|
||||
activation_key=activation_key
|
||||
)
|
||||
except PendingEmailChange.DoesNotExist:
|
||||
# If there are no changes, then the activation key is invalid
|
||||
raise AccountNotAuthorized
|
||||
else:
|
||||
old_email = pending_change.user.email
|
||||
new_email = pending_change.new_email
|
||||
|
||||
# Verify that no one else has taken the email in between
|
||||
# the request and the activation.
|
||||
# In our production database, email has a uniqueness constraint,
|
||||
# so there is no danger of a race condition here.
|
||||
if User.objects.filter(email=new_email).exists():
|
||||
raise AccountEmailAlreadyExists
|
||||
|
||||
# Update the email history (in the user profile)
|
||||
try:
|
||||
profile = UserProfile.objects.get(user=pending_change.user)
|
||||
except UserProfile.DoesNotExist:
|
||||
raise AccountInternalError(
|
||||
"No profile exists for the user '{username}'".format(
|
||||
username=pending_change.user.username
|
||||
)
|
||||
)
|
||||
else:
|
||||
profile.update_email(new_email)
|
||||
|
||||
# Delete the pending change, so that the activation code
|
||||
# will be single-use
|
||||
pending_change.delete()
|
||||
|
||||
# Return the old and new email
|
||||
# This allows the caller of the function to notify users at both
|
||||
# the new and old email, which is necessary for security reasons.
|
||||
return (old_email, new_email)
|
||||
|
||||
|
||||
@intercept_errors(AccountInternalError, ignore_errors=[AccountRequestError])
|
||||
def request_password_change(email, orig_host, is_secure):
|
||||
"""Email a single-use link for performing a password reset.
|
||||
|
||||
@@ -76,40 +76,6 @@ class AccountApiTest(TestCase):
|
||||
account = account_api.account_info(self.USERNAME)
|
||||
self.assertTrue(account['is_active'])
|
||||
|
||||
def test_change_email(self):
|
||||
# Request an email change
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
activation_key = account_api.request_email_change(
|
||||
self.USERNAME, u'new+email@example.com', self.PASSWORD
|
||||
)
|
||||
|
||||
# Verify that the email has not yet changed
|
||||
account = account_api.account_info(self.USERNAME)
|
||||
self.assertEqual(account['email'], self.EMAIL)
|
||||
|
||||
# Confirm the change, using the activation code
|
||||
old_email, new_email = account_api.confirm_email_change(activation_key)
|
||||
self.assertEqual(old_email, self.EMAIL)
|
||||
self.assertEqual(new_email, u'new+email@example.com')
|
||||
|
||||
# Verify that the email is changed
|
||||
account = account_api.account_info(self.USERNAME)
|
||||
self.assertEqual(account['email'], u'new+email@example.com')
|
||||
|
||||
def test_confirm_email_change_repeat(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
activation_key = account_api.request_email_change(
|
||||
self.USERNAME, u'new+email@example.com', self.PASSWORD
|
||||
)
|
||||
|
||||
# Confirm the change once
|
||||
account_api.confirm_email_change(activation_key)
|
||||
|
||||
# Confirm the change again. The activation code should be
|
||||
# single-use, so this should raise an error.
|
||||
with self.assertRaises(account_api.AccountNotAuthorized):
|
||||
account_api.confirm_email_change(activation_key)
|
||||
|
||||
def test_create_account_duplicate_username(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
with self.assertRaises(account_api.AccountUserAlreadyExists):
|
||||
@@ -155,125 +121,6 @@ class AccountApiTest(TestCase):
|
||||
def test_activate_account_invalid_key(self):
|
||||
account_api.activate_account(u'invalid')
|
||||
|
||||
@raises(account_api.AccountUserNotFound)
|
||||
def test_request_email_change_no_user(self):
|
||||
account_api.request_email_change(u'no_such_user', self.EMAIL, self.PASSWORD)
|
||||
|
||||
@ddt.data(*INVALID_EMAILS)
|
||||
def test_request_email_change_invalid_email(self, invalid_email):
|
||||
# Create an account with a valid email address
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
# Attempt to change the account to an invalid email
|
||||
with self.assertRaises(account_api.AccountEmailInvalid):
|
||||
account_api.request_email_change(self.USERNAME, invalid_email, self.PASSWORD)
|
||||
|
||||
def test_request_email_change_already_exists(self):
|
||||
# Create two accounts, both activated
|
||||
activation_key = account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
account_api.activate_account(activation_key)
|
||||
activation_key = account_api.create_account(u'another_user', u'password', u'another+user@example.com')
|
||||
account_api.activate_account(activation_key)
|
||||
|
||||
# Try to change the first user's email to the same as the second user's
|
||||
with self.assertRaises(account_api.AccountEmailAlreadyExists):
|
||||
account_api.request_email_change(self.USERNAME, u'another+user@example.com', self.PASSWORD)
|
||||
|
||||
def test_request_email_change_duplicates_unactivated_account(self):
|
||||
# Create two accounts, but the second account is inactive
|
||||
activation_key = account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
account_api.activate_account(activation_key)
|
||||
account_api.create_account(u'another_user', u'password', u'another+user@example.com')
|
||||
|
||||
# Try to change the first user's email to the same as the second user's
|
||||
# Since the second user has not yet activated, this should succeed.
|
||||
account_api.request_email_change(self.USERNAME, u'another+user@example.com', self.PASSWORD)
|
||||
|
||||
def test_request_email_change_same_address(self):
|
||||
# Create and activate the account
|
||||
activation_key = account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
account_api.activate_account(activation_key)
|
||||
|
||||
# Try to change the email address to the current address
|
||||
with self.assertRaises(account_api.AccountEmailAlreadyExists):
|
||||
account_api.request_email_change(self.USERNAME, self.EMAIL, self.PASSWORD)
|
||||
|
||||
def test_request_email_change_wrong_password(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
# Use the wrong password
|
||||
with self.assertRaises(account_api.AccountNotAuthorized):
|
||||
account_api.request_email_change(self.USERNAME, u'new+email@example.com', u'wrong password')
|
||||
|
||||
def test_confirm_email_change_invalid_activation_key(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
account_api.request_email_change(self.USERNAME, u'new+email@example.com', self.PASSWORD)
|
||||
|
||||
with self.assertRaises(account_api.AccountNotAuthorized):
|
||||
account_api.confirm_email_change(u'invalid')
|
||||
|
||||
def test_confirm_email_change_no_request_pending(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
def test_confirm_email_already_exists(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
# Request a change
|
||||
activation_key = account_api.request_email_change(
|
||||
self.USERNAME, u'new+email@example.com', self.PASSWORD
|
||||
)
|
||||
|
||||
# Another use takes the email before we confirm the change
|
||||
account_api.create_account(u'other_user', u'password', u'new+email@example.com')
|
||||
|
||||
# When we try to confirm our change, we get an error because the email is taken
|
||||
with self.assertRaises(account_api.AccountEmailAlreadyExists):
|
||||
account_api.confirm_email_change(activation_key)
|
||||
|
||||
# Verify that the email was NOT changed
|
||||
self.assertEqual(account_api.account_info(self.USERNAME)['email'], self.EMAIL)
|
||||
|
||||
def test_confirm_email_no_user_profile(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
activation_key = account_api.request_email_change(
|
||||
self.USERNAME, u'new+email@example.com', self.PASSWORD
|
||||
)
|
||||
|
||||
# This should never happen, but just in case...
|
||||
UserProfile.objects.get(user__username=self.USERNAME).delete()
|
||||
|
||||
with self.assertRaises(account_api.AccountInternalError):
|
||||
account_api.confirm_email_change(activation_key)
|
||||
|
||||
def test_record_email_change_history(self):
|
||||
account_api.create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
# Change the email once
|
||||
activation_key = account_api.request_email_change(
|
||||
self.USERNAME, u'new+email@example.com', self.PASSWORD
|
||||
)
|
||||
account_api.confirm_email_change(activation_key)
|
||||
|
||||
# Verify that the old email appears in the history
|
||||
meta = UserProfile.objects.get(user__username=self.USERNAME).get_meta()
|
||||
self.assertEqual(len(meta['old_emails']), 1)
|
||||
email, timestamp = meta['old_emails'][0]
|
||||
self.assertEqual(email, self.EMAIL)
|
||||
self._assert_is_datetime(timestamp)
|
||||
|
||||
# Change the email again
|
||||
activation_key = account_api.request_email_change(
|
||||
self.USERNAME, u'another_new+email@example.com', self.PASSWORD
|
||||
)
|
||||
account_api.confirm_email_change(activation_key)
|
||||
|
||||
# Verify that both emails appear in the history
|
||||
meta = UserProfile.objects.get(user__username=self.USERNAME).get_meta()
|
||||
self.assertEqual(len(meta['old_emails']), 2)
|
||||
email, timestamp = meta['old_emails'][1]
|
||||
self.assertEqual(email, 'new+email@example.com')
|
||||
self._assert_is_datetime(timestamp)
|
||||
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
|
||||
def test_request_password_change(self):
|
||||
# Create and activate an account
|
||||
|
||||
Reference in New Issue
Block a user