Newer
Older
user.id,
"edx.bi.user.account.authenticated",
{
'category': "conversion",
'label': registration_course_id,
'provider': None
},
context={
'Google Analytics': {
}
}
)
request.session['registration_course_id'] = None
Brian Wilson
committed
# We do not log here, because we have a handler registered
# to perform logging on successful logins.
if request.POST.get('remember') == 'true':
Calen Pennington
committed
request.session.set_expiry(604800)
log.debug("Setting user session to never expire")
else:
request.session.set_expiry(0)
except Exception as exc: # pylint: disable=broad-except
Brian Wilson
committed
AUDIT_LOG.critical("Login failed - Could not create session. Is memcached running?")
log.critical("Login failed - Could not create session. Is memcached running?")
Brian Wilson
committed
raise
redirect_url = try_change_enrollment(request)
if third_party_auth_successful:
redirect_url = pipeline.get_complete_url(backend_name)
response = JsonResponse({
"success": True,
"redirect_url": redirect_url,
})
# set the login cookie for the edx marketing site
# we want this cookie to be accessed via javascript
# so httponly is set to None
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
response.set_cookie(
settings.EDXMKTG_COOKIE_NAME, 'true', max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path='/', secure=None, httponly=None,
)
return response
Chris Dodge
committed
if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
AUDIT_LOG.warning(u"Login failed - Account not active for user.id: {0}, resending activation".format(user.id))
else:
AUDIT_LOG.warning(u"Login failed - Account not active for user {0}, resending activation".format(username))
reactivation_email_for_user(user)
not_activated_msg = _("This account has not been activated. We have sent another activation message. Please check your e-mail for the activation instructions.")
return JsonResponse({
"success": False,
"value": not_activated_msg,
}) # TODO: this should be status code 400 # pylint: disable=fixme
@ensure_csrf_cookie
HTTP request to log out the user. Redirects to marketing page.
Deletes both the CSRF and sessionid cookies so the marketing
site can determine the logged in state of the user
Brian Wilson
committed
# We do not log here, because we have a handler registered
# to perform logging on successful logouts.
if settings.FEATURES.get('AUTH_USE_CAS'):
target = reverse('cas-logout')
else:
target = '/'
response = redirect(target)
response.delete_cookie(
settings.EDXMKTG_COOKIE_NAME,
path='/', domain=settings.SESSION_COOKIE_DOMAIN,
)
return response
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
@require_GET
@login_required
@ensure_csrf_cookie
def manage_user_standing(request):
"""
Renders the view used to manage user standing. Also displays a table
of user accounts that have been disabled and who disabled them.
"""
if not request.user.is_staff:
raise Http404
all_disabled_accounts = UserStanding.objects.filter(
account_status=UserStanding.ACCOUNT_DISABLED
)
all_disabled_users = [standing.user for standing in all_disabled_accounts]
headers = ['username', 'account_changed_by']
rows = []
for user in all_disabled_users:
row = [user.username, user.standing.all()[0].changed_by]
rows.append(row)
context = {'headers': headers, 'rows': rows}
return render_to_response("manage_user_standing.html", context)
@require_POST
@login_required
@ensure_csrf_cookie
def disable_account_ajax(request):
"""
Ajax call to change user standing. Endpoint of the form
in manage_user_standing.html
"""
if not request.user.is_staff:
raise Http404
username = request.POST.get('username')
context = {}
if username is None or username.strip() == '':
context['message'] = _('Please enter a username')
return JsonResponse(context, status=400)
account_action = request.POST.get('account_action')
if account_action is None:
context['message'] = _('Please choose an option')
return JsonResponse(context, status=400)
username = username.strip()
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
context['message'] = _("User with username {} does not exist").format(username)
return JsonResponse(context, status=400)
else:
user_account, _success = UserStanding.objects.get_or_create(
user=user, defaults={'changed_by': request.user},
)
if account_action == 'disable':
user_account.account_status = UserStanding.ACCOUNT_DISABLED
context['message'] = _("Successfully disabled {}'s account").format(username)
log.info("{} disabled {}'s account".format(request.user, username))
elif account_action == 'reenable':
user_account.account_status = UserStanding.ACCOUNT_ENABLED
context['message'] = _("Successfully reenabled {}'s account").format(username)
log.info("{} reenabled {}'s account".format(request.user, username))
else:
context['message'] = _("Unexpected account status")
return JsonResponse(context, status=400)
user_account.changed_by = request.user
user_account.standing_last_changed_at = datetime.datetime.now(UTC)
user_account.save()
return JsonResponse(context)
Calen Pennington
committed
@login_required
@ensure_csrf_cookie
def change_setting(request):
"""JSON call to change a profile setting: Right now, location"""
# TODO (vshnayder): location is no longer used
u_prof = UserProfile.objects.get(user=request.user) # request.user.profile_cache
u_prof.location = request.POST['location']
u_prof.save()
class AccountValidationError(Exception):
def __init__(self, message, field):
super(AccountValidationError, self).__init__(message)
self.field = field
asadiqbal08
committed
@receiver(post_save, sender=User)
def user_signup_handler(sender, **kwargs): # pylint: disable=W0613
"""
handler that saves the user Signup Source
when the user is created
"""
if 'created' in kwargs and kwargs['created']:
site = microsite.get_value('SITE_NAME')
if site:
user_signup_source = UserSignupSource(user=kwargs['instance'], site=site)
asadiqbal08
committed
user_signup_source.save()
log.info(u'user {} originated from a white labeled "Microsite"'.format(kwargs['instance'].id))
Chris Dodge
committed
def _do_create_account(post_vars, extended_profile=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.
"""
user = User(username=post_vars['username'],
email=post_vars['email'],
is_active=False)
user.set_password(post_vars['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:
except IntegrityError:
# Figure out the cause of the integrity error
if len(User.objects.filter(username=post_vars['username'])) > 0:
raise AccountValidationError(
_("An account with the Public Username '{username}' already exists.").format(username=post_vars['username']),
field="username"
elif len(User.objects.filter(email=post_vars['email'])) > 0:
raise AccountValidationError(
_("An account with the Email '{email}' already exists.").format(email=post_vars['email']),
field="email"
else:
raise
# add this account creation to password history
# NOTE, this will be a NOP unless the feature has been turned on in configuration
password_history_entry = PasswordHistory()
password_history_entry.create(user)
profile = UserProfile(user=user)
profile.name = post_vars['name']
profile.level_of_education = post_vars.get('level_of_education')
profile.gender = post_vars.get('gender')
profile.mailing_address = post_vars.get('mailing_address')
profile.city = post_vars.get('city')
profile.country = post_vars.get('country')
profile.goals = post_vars.get('goals')
Chris Dodge
committed
# add any extended profile information in the denormalized 'meta' field in the profile
if extended_profile:
profile.meta = json.dumps(extended_profile)
profile.year_of_birth = int(post_vars['year_of_birth'])
except (ValueError, KeyError):
# If they give us garbage, just ignore it instead
# of asking them to put an integer.
log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
UserPreference.set_preference(user, LANGUAGE_KEY, get_language())
return (user, profile, registration)
@ensure_csrf_cookie
def create_account(request, post_override=None): # pylint: disable-msg=too-many-statements
JSON call to create new edX account.
Used by form in signup_modal.html, which is included into navigation.html
js = {'success': False} # pylint: disable-msg=invalid-name
Bridger Maxwell
committed
post_vars = post_override if post_override else request.POST
Chris Dodge
committed
# allow for microsites to define their own set of required/optional/hidden fields
extra_fields = microsite.get_value(
'REGISTRATION_EXTRA_FIELDS',
getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})
)
if microsite.get_value('ENABLE_THIRD_PARTY_AUTH', settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH')) and pipeline.running(request):
post_vars = dict(post_vars.items())
post_vars.update({'password': pipeline.make_random_password()})
# if doing signup for an external authorization, then get email, password, name from the eamap
# don't use the ones from the form, since the user could have hacked those
# unless originally we didn't get a valid email or name from the external auth
do_external_auth = 'ExternalAuthMap' in request.session
if do_external_auth:
try:
validate_email(eamap.external_email)
email = eamap.external_email
except ValidationError:
email = post_vars.get('email', '')
if eamap.external_name.strip() == '':
name = post_vars.get('name', '')
else:
password = eamap.internal_password
post_vars = dict(post_vars.items())
post_vars.update(dict(email=email, name=name, password=password))
Brian Wilson
committed
log.debug(u'In create_account with external_auth: user = %s, email=%s', name, email)
for req_field in ['username', 'email', 'password', 'name']:
if req_field not in post_vars:
js['value'] = _("Error (401 {field}). E-mail us.").format(field=req_field)
js['field'] = req_field
return JsonResponse(js, status=400)
if extra_fields.get('honor_code', 'required') == 'required' and \
post_vars.get('honor_code', 'false') != u'true':
js['value'] = _("To enroll, you must follow the honor code.")
js['field'] = 'honor_code'
return JsonResponse(js, status=400)
# Can't have terms of service for certain SHIB users, like at Stanford
tos_required = (
not settings.FEATURES.get("AUTH_USE_SHIB") or
not settings.FEATURES.get("SHIB_DISABLE_TOS") or
not eamap.external_domain.startswith(
external_auth.views.SHIBBOLETH_DOMAIN_PREFIX
)
)
if tos_required:
if post_vars.get('terms_of_service', 'false') != u'true':
js['value'] = _("You must accept the terms of service.")
js['field'] = 'terms_of_service'
return JsonResponse(js, status=400)
# Confirm appropriate fields are there.
# TODO: Check e-mail format is correct.
# TODO: Confirm e-mail is not from a generic domain (mailinator, etc.)? Not sure if
# this is a good idea
# TODO: Check password is sane
required_post_vars = ['username', 'email', 'name', 'password']
required_post_vars += [fieldname for fieldname, val in extra_fields.items()
if val == 'required']
if tos_required:
required_post_vars.append('terms_of_service')
for field_name in required_post_vars:
if field_name in ('gender', 'level_of_education'):
min_length = 1
else:
min_length = 2
Chris Dodge
committed
if field_name not in post_vars or len(post_vars[field_name]) < min_length:
error_str = {
'username': _('Username must be minimum of two characters long'),
'email': _('A properly formatted e-mail is required'),
'name': _('Your legal name must be a minimum of two characters long'),
'password': _('A valid password is required'),
'terms_of_service': _('Accepting Terms of Service is required'),
'honor_code': _('Agreeing to the Honor Code is required'),
'level_of_education': _('A level of education is required'),
'gender': _('Your gender is required'),
'year_of_birth': _('Your year of birth is required'),
'mailing_address': _('Your mailing address is required'),
'goals': _('A description of your goals is required'),
'city': _('A city is required'),
'country': _('A country is required')
}
Chris Dodge
committed
if field_name in error_str:
js['value'] = error_str[field_name]
else:
js['value'] = _('You are missing one or more required fields')
js['field'] = field_name
return JsonResponse(js, status=400)
max_length = 75
if field_name == 'username':
max_length = 30
if field_name in ('email', 'username') and len(post_vars[field_name]) > max_length:
error_str = {
'username': _('Username cannot be more than {num} characters long').format(num=max_length),
'email': _('Email cannot be more than {num} characters long').format(num=max_length)
}
js['value'] = error_str[field_name]
js['field'] = field_name
return JsonResponse(js, status=400)
Bridger Maxwell
committed
validate_email(post_vars['email'])
return JsonResponse(js, status=400)
Bridger Maxwell
committed
validate_slug(post_vars['username'])
js['value'] = _("Username should only consist of A-Z and 0-9, with no spaces.")
return JsonResponse(js, status=400)
Chris Dodge
committed
# enforce password complexity as an optional feature
# but not if we're doing ext auth b/c those pws never get used and are auto-generated so might not pass validation
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False) and not do_external_auth:
Chris Dodge
committed
try:
password = post_vars['password']
validate_password_length(password)
validate_password_complexity(password)
validate_password_dictionary(password)
except ValidationError, err:
js['value'] = _('Password: ') + '; '.join(err.messages)
js['field'] = 'password'
return JsonResponse(js, status=400)
Chris Dodge
committed
# allow microsites to define 'extended profile fields' which are
# captured on user signup (for example via an overriden registration.html)
# and then stored in the UserProfile
extended_profile_fields = microsite.get_value('extended_profile_fields', [])
extended_profile = None
for field in extended_profile_fields:
if field in post_vars:
if not extended_profile:
extended_profile = {}
extended_profile[field] = post_vars[field]
# Make sure that password and username fields do not match
username = post_vars['username']
password = post_vars['password']
if username == password:
js['value'] = _("Username and password fields cannot match")
js['field'] = 'username'
return JsonResponse(js, status=400)
# Ok, looks like everything is legit. Create the account.
try:
with transaction.commit_on_success():
Chris Dodge
committed
ret = _do_create_account(post_vars, extended_profile)
except AccountValidationError as exc:
return JsonResponse({'success': False, 'value': exc.message, 'field': exc.field}, status=400)
(user, profile, registration) = ret
dog_stats_api.increment("common.student.account_created")
email = post_vars['email']
# Track the user's registration
if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr(settings, 'SEGMENT_IO_LMS_KEY'):
tracking_context = tracker.get_tracker().resolve_context()
analytics.identify(user.id, {
# If the user is registering via 3rd party auth, track which provider they use
provider_name = None
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH') and pipeline.running(request):
running_pipeline = pipeline.get(request)
current_provider = provider.Registry.get_by_backend_name(running_pipeline.get('backend'))
provider_name = current_provider.NAME
registration_course_id = request.session.get('registration_course_id')
analytics.track(
user.id,
'category': 'conversion',
'label': registration_course_id,
'provider': provider_name
},
context={
'Google Analytics': {
}
}
)
request.session['registration_course_id'] = None
create_comments_service_user(user)
Chris Dodge
committed
context = {
'name': post_vars['name'],
'key': registration.activation_key,
}
Chris Dodge
committed
subject = render_to_string('emails/activation_email_subject.txt', context)
Chris Dodge
committed
message = render_to_string('emails/activation_email.txt', context)
# don't send email if we are doing load testing or random user generation for some reason
# or external auth with bypass activated
send_email = (
not settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING') and
not (do_external_auth and settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'))
)
if send_email:
from_address = microsite.get_value(
Chris Dodge
committed
'email_from_address',
settings.DEFAULT_FROM_EMAIL
)
if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
'-' * 80 + '\n\n' + message)
Chris Dodge
committed
send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
user.email_user(subject, message, from_address)
except Exception: # pylint: disable=broad-except
log.error('Unable to send activation email to user from "{from_address}"'.format(from_address=from_address), exc_info=True)
js['value'] = _('Could not send activation e-mail.')
# What is the correct status code to use here? I think it's 500, because
# the problem is on the server's end -- but also, the account was created.
# Seems like the core part of the request was successful.
return JsonResponse(js, status=500)
# 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.
new_user = authenticate(username=post_vars['username'], password=post_vars['password'])
login(request, new_user)
Brian Wilson
committed
# TODO: there is no error checking here to see that the user actually logged in successfully,
# and is not yet an active user.
if new_user is not None:
AUDIT_LOG.info(u"Login success on new account creation - {0}".format(new_user.username))
Brian Wilson
committed
eamap.dtsignup = datetime.datetime.now(UTC)
Brian Wilson
committed
AUDIT_LOG.info("User registered with external_auth %s", post_vars['username'])
AUDIT_LOG.info('Updated ExternalAuthMap for %s to be %s', post_vars['username'], eamap)
if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
log.info('bypassing activation email')
new_user.is_active = True
new_user.save()
AUDIT_LOG.info(u"Login activated on extauth account - {0} ({1})".format(new_user.username, new_user.email))
dog_stats_api.increment("common.student.account_created")
redirect_url = try_change_enrollment(request)
# Resume the third-party-auth pipeline if necessary.
if microsite.get_value('ENABLE_THIRD_PARTY_AUTH', settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH')) and pipeline.running(request):
running_pipeline = pipeline.get(request)
redirect_url = pipeline.get_complete_url(running_pipeline['backend'])
response = JsonResponse({
'success': True,
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
# set the login cookie for the edx marketing site
# we want this cookie to be accessed via javascript
# so httponly is set to None
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
'true', max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path='/',
secure=None,
httponly=None)
return response
def auto_auth(request):
Create or configure a user account, then log in as that user.
Enabled only when
settings.FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] is true.
Accepts the following querystring parameters:
* `username`, `email`, and `password` for the user account
* `full_name` for the user profile (the user's full name; defaults to the username)
* `staff`: Set to "true" to make the user global staff.
* `course_id`: Enroll the student in the course with `course_id`
* `roles`: Comma-separated list of roles to grant the student in the course with `course_id`
If username, email, or password are not provided, use
randomly generated credentials.
"""
# Generate a unique name to use if none provided
unique_name = uuid.uuid4().hex[0:30]
# Use the params from the request, otherwise use these defaults
username = request.GET.get('username', unique_name)
password = request.GET.get('password', unique_name)
email = request.GET.get('email', unique_name + "@example.com")
full_name = request.GET.get('full_name', username)
is_staff = request.GET.get('staff', None)
course_id = request.GET.get('course_id', None)
course_key = None
if course_id:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
role_names = [v.strip() for v in request.GET.get('roles', '').split(',') if v.strip()]
# Get or create the user object
post_data = {
'username': username,
'email': email,
'password': password,
'name': full_name,
'honor_code': u'true',
'terms_of_service': u'true',
}
Bridger Maxwell
committed
# Attempt to create the account.
# If successful, this will return a tuple containing
# the new user object.
try:
except AccountValidationError:
# Attempt to retrieve the existing user.
user = User.objects.get(username=username)
user.email = email
user.set_password(password)
user.save()
reg = Registration.objects.get(user=user)
# Set the user's global staff bit
if is_staff is not None:
user.is_staff = (is_staff == "true")
user.save()
# Activate the user
reg.activate()
reg.save()
if course_key is not None:
CourseEnrollment.enroll(user, course_key)
# Apply the roles
for role_name in role_names:
role = Role.objects.get(name=role_name, course_id=course_key)
# Log in as the user
user = authenticate(username=username, password=password)
login(request, user)
create_comments_service_user(user)
# Provide the user with a valid CSRF token
# then return a 200 response
success_msg = u"Logged in user {0} ({1}) with password {2} and user_id {3}".format(
username, email, password, user.id
)
response = HttpResponse(success_msg)
response.set_cookie('csrftoken', csrf(request)['csrf_token'])
return response
Bridger Maxwell
committed
@ensure_csrf_cookie
"""When link in activation e-mail is clicked"""
regs = Registration.objects.filter(activation_key=key)
if len(regs) == 1:
Bridger Maxwell
committed
user_logged_in = request.user.is_authenticated()
already_active = True
if not regs[0].user.is_active:
regs[0].activate()
Bridger Maxwell
committed
already_active = False
Brian Wilson
committed
# Enroll student in any pending courses he/she may have if auto_enroll flag is set
if student:
ceas = CourseEnrollmentAllowed.objects.filter(email=student[0].email)
for cea in ceas:
if cea.auto_enroll:
David Ormsbee
committed
CourseEnrollment.enroll(student[0], cea.course_id)
resp = render_to_response(
"registration/activation_complete.html",
{
'user_logged_in': user_logged_in,
'already_active': already_active
}
)
David Ormsbee
committed
return render_to_response(
"registration/activation_invalid.html",
{'csrf': csrf(request)['csrf_token']}
)
return HttpResponse(_("Unknown error. Please e-mail us to let us know how it happened."))
@ensure_csrf_cookie
""" Attempts to send a password reset e-mail. """
# Add some rate limiting here by re-using the RateLimitMixin as a helper class
limiter = BadRequestRateLimiter()
if limiter.is_rate_limit_exceeded(request):
AUDIT_LOG.warning("Rate limit exceeded in password_reset")
return HttpResponseForbidden()
form = PasswordResetFormNoActive(request.POST)
form.save(use_https=request.is_secure(),
from_email=settings.DEFAULT_FROM_EMAIL,
request=request,
domain_override=request.get_host())
else:
# bad user? tick the rate limiter counter
AUDIT_LOG.info("Bad password_reset user passed in.")
limiter.tick_bad_request_counter(request)
return JsonResponse({
'success': True,
'value': render_to_string('registration/password_reset_done.html', {}),
})
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.
Brian Wilson
committed
# cribbed from django.contrib.auth.views.password_reset_confirm
try:
uid_int = base36_to_int(uidb36)
user = User.objects.get(id=uid_int)
user.is_active = True
user.save()
except (ValueError, User.DoesNotExist):
pass
# tie in password strength enforcement as an optional level of
# security protection
err_msg = None
if request.method == 'POST':
password = request.POST['new_password1']
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
try:
validate_password_length(password)
validate_password_complexity(password)
validate_password_dictionary(password)
except ValidationError, err:
err_msg = _('Password: ') + '; '.join(err.messages)
# also, check the password reuse policy
if not PasswordHistory.is_allowable_password_reuse(user, password):
if user.is_staff:
num_distinct = settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STAFF_PASSWORDS_BEFORE_REUSE']
else:
num_distinct = settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STUDENT_PASSWORDS_BEFORE_REUSE']
err_msg = ungettext(
"You are re-using a password that you have used recently. You must have {num} distinct password before reusing a previous password.",
"You are re-using a password that you have used recently. You must have {num} distinct passwords before reusing a previous password.",
num_distinct
).format(num=num_distinct)
# also, check to see if passwords are getting reset too frequent
if PasswordHistory.is_password_reset_too_soon(user):
num_days = settings.ADVANCED_SECURITY_CONFIG['MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS']
err_msg = ungettext(
"You are resetting passwords too frequently. Due to security policies, {num} day must elapse between password resets.",
"You are resetting passwords too frequently. Due to security policies, {num} days must elapse between password resets.",
num_days
).format(num=num_days)
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
if err_msg:
# We have an password reset attempt which violates some security policy, use the
# existing Django template to communicate this back to the user
context = {
'validlink': True,
'form': None,
'title': _('Password reset unsuccessful'),
'err_msg': err_msg,
}
return TemplateResponse(request, 'registration/password_reset_confirm.html', context)
else:
# we also want to pass settings.PLATFORM_NAME in as extra_context
extra_context = {"platform_name": settings.PLATFORM_NAME}
if request.method == 'POST':
# remember what the old password hash is before we call down
old_password_hash = user.password
result = password_reset_confirm(
request, uidb36=uidb36, token=token, extra_context=extra_context
)
# 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)
return result
else:
return password_reset_confirm(
request, uidb36=uidb36, token=token, extra_context=extra_context
)
def reactivation_email_for_user(user):
try:
reg = Registration.objects.get(user=user)
except Registration.DoesNotExist:
return JsonResponse({
"success": False,
"error": _('No inactive user with this e-mail exists'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
context = {
'name': user.profile.name,
'key': reg.activation_key,
}
subject = render_to_string('emails/activation_email_subject.txt', context)
subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', context)
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except Exception: # pylint: disable=broad-except
log.error('Unable to send reactivation email from "{from_address}"'.format(from_address=settings.DEFAULT_FROM_EMAIL), exc_info=True)
return JsonResponse({
"success": False,
"error": _('Unable to send reactivation email')
}) # TODO: this should be status code 500 # pylint: disable=fixme
@ensure_csrf_cookie
def change_email_request(request):
""" AJAX call from the profile page. User wants a new e-mail.
"""
## Make sure it checks for existing e-mail conflicts
if not request.user.is_authenticated():
user = request.user
if not user.check_password(request.POST['password']):
return JsonResponse({
"success": False,
"error": _('Invalid password'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
new_email = request.POST['new_email']
try:
validate_email(new_email)
except ValidationError:
return JsonResponse({
"success": False,
"error": _('Valid e-mail address required.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
if User.objects.filter(email=new_email).count() != 0:
## CRITICAL TODO: Handle case sensitivity for e-mails
return JsonResponse({
"success": False,
"error": _('An account with this e-mail already exists.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
pec_list = PendingEmailChange.objects.filter(user=request.user)
pec = PendingEmailChange()
pec.user = user
pec = pec_list[0]
pec.new_email = request.POST['new_email']
pec.activation_key = uuid.uuid4().hex
pec.save()
return JsonResponse({
"success": False,
"error": _('Old email is the same as the new email.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
Chris Dodge
committed
context = {
'key': pec.activation_key,
'old_email': user.email,
'new_email': pec.new_email
}
Chris Dodge
committed
subject = render_to_string('emails/email_change_subject.txt', context)
subject = ''.join(subject.splitlines())
Chris Dodge
committed
message = render_to_string('emails/email_change.txt', context)
from_address = microsite.get_value(
Chris Dodge
committed
'email_from_address',
settings.DEFAULT_FROM_EMAIL
)
try:
send_mail(subject, message, from_address, [pec.new_email])
except Exception: # pylint: disable=broad-except
log.error('Unable to send email activation link to user from "{from_address}"'.format(from_address=from_address), exc_info=True)
return JsonResponse({
"success": False,
"error": _('Unable to send email activation link. Please try again later.')
})
@transaction.commit_manually
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
try:
pec = PendingEmailChange.objects.get(activation_key=key)
except PendingEmailChange.DoesNotExist:
response = render_to_response("invalid_email_key.html", {})
transaction.rollback()
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.rollback()
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()])
# Send it to the old email...
try:
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
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.rollback()
return response
user.email = pec.new_email
user.save()
pec.delete()
# And send it to the new email...
try:
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
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})