Skip to content

Commit

Permalink
Issue #46: added SEND_RESET_PASSWORD_LINK_SERIALIZER_USE_EMAIL setting
Browse files Browse the repository at this point in the history
  • Loading branch information
apragacz committed May 12, 2019
1 parent 7f148ff commit bc09027
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 19 deletions.
2 changes: 1 addition & 1 deletion docs/detailed_configuration/reset_password.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ DefaultSendResetPasswordLinkSerializer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. autoclass:: rest_registration.api.serializers.DefaultSendResetPasswordLinkSerializer
:members:
:members: get_user_or_none

List of settings
----------------
Expand Down
31 changes: 28 additions & 3 deletions rest_registration/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,34 @@ def get_email(self):


class DefaultSendResetPasswordLinkSerializer(serializers.Serializer):
login = serializers.CharField(required=True)
"""
Default serializer used for sending reset password link.
It will use :ref:`send-reset-password-link-serializer-use-email-setting`
setting.
"""

def get_fields(self):
fields = super().get_fields()
if registration_settings.SEND_RESET_PASSWORD_LINK_SERIALIZER_USE_EMAIL:
fields['email'] = serializers.CharField(required=True)
else:
fields['login'] = serializers.CharField(required=True)
return fields

def get_user_or_none(self):
"""
Return user if login matches.
Return user if matching given criteria (login fields / e-mail).
Return ``None`` otherwise.
"""
login = self.validated_data['login']
if registration_settings.SEND_RESET_PASSWORD_LINK_SERIALIZER_USE_EMAIL:
email = self.validated_data['email']
return self._get_user_by_email_or_none(email)
else:
login = self.validated_data['login']
return self._get_user_by_login_or_none(login)

def _get_user_by_login_or_none(self, login):
user = None
for login_field in get_user_login_fields():
user = get_user_by_lookup_dict(
Expand All @@ -66,6 +86,11 @@ def get_user_or_none(self):

return user

def _get_user_by_email_or_none(self, email):
email_field = get_user_setting('EMAIL_FIELD')
return get_user_by_lookup_dict(
{email_field: email}, default=None, require_verified=False)


class DefaultUserProfileSerializer(serializers.ModelSerializer):
"""
Expand Down
10 changes: 10 additions & 0 deletions rest_registration/settings_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ def __new__(cls, name, *, default=None, help=None, import_string=False):
method which is used to obtain the user matching the criteria.
""")
),
Field(
'SEND_RESET_PASSWORD_LINK_SERIALIZER_USE_EMAIL',
default=False,
help=dedent("""\
Used specifically by ``DefaultSendResetPasswordLinkSerializer``.
If ``True``, use e-mail field instead of login fields to find
the user who should receive the reset password link.
""")
),
Field('RESET_PASSWORD_VERIFICATION_ENABLED', default=True),
Field(
'RESET_PASSWORD_VERIFICATION_PERIOD',
Expand Down
89 changes: 74 additions & 15 deletions tests/api/test_reset_password.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import math
import time
from unittest import skip
from unittest.mock import patch
Expand Down Expand Up @@ -34,14 +33,78 @@ def test_send_link_ok(self):
request = self.create_post_request({
'login': user.username,
})
time_before = math.floor(time.time())
with self.assert_one_mail_sent() as sent_emails:
with self.assert_one_mail_sent() as sent_emails, self.timer() as timer:
response = self.view_func(request)
time_after = math.ceil(time.time())
self.assert_valid_response(response, status.HTTP_200_OK)
sent_email = sent_emails[0]
self._assert_valid_send_link_email(
sent_email, user, time_before, time_after)
self._assert_valid_send_link_email(sent_email, user, timer)

def test_send_link_but_email_not_in_login_fields(self):
user = self.create_test_user(
username='testusername', email='testuser@example.com')
request = self.create_post_request({
'login': user.email,
})
with self.assert_no_mail_sent():
response = self.view_func(request)
self.assert_valid_response(response, status.HTTP_404_NOT_FOUND)

@override_settings(
REST_REGISTRATION=shallow_merge_dicts(
REST_REGISTRATION_WITH_RESET_PASSWORD, {
'USER_LOGIN_FIELDS': ['username', 'email'],
}
),
)
def test_send_link_via_login_username_ok(self):
user = self.create_test_user(username='testusername')
request = self.create_post_request({
'login': user.username,
})
with self.assert_one_mail_sent() as sent_emails, self.timer() as timer:
response = self.view_func(request)
self.assert_valid_response(response, status.HTTP_200_OK)
sent_email = sent_emails[0]
self._assert_valid_send_link_email(sent_email, user, timer)

@override_settings(
REST_REGISTRATION=shallow_merge_dicts(
REST_REGISTRATION_WITH_RESET_PASSWORD, {
'USER_LOGIN_FIELDS': ['username', 'email'],
}
),
)
def test_send_link_via_login_email_ok(self):
user = self.create_test_user(
username='testusername', email='testuser@example.com')
request = self.create_post_request({
'login': user.email,
})
with self.assert_one_mail_sent() as sent_emails, self.timer() as timer:
response = self.view_func(request)
self.assert_valid_response(response, status.HTTP_200_OK)
sent_email = sent_emails[0]
self._assert_valid_send_link_email(sent_email, user, timer)

@override_settings(
REST_REGISTRATION=shallow_merge_dicts(
REST_REGISTRATION_WITH_RESET_PASSWORD, {
'USER_LOGIN_FIELDS': ['username', 'email'],
'SEND_RESET_PASSWORD_LINK_SERIALIZER_USE_EMAIL': True,
}
),
)
def test_send_link_via_email_ok(self):
user = self.create_test_user(
username='testusername', email='testuser@example.com')
request = self.create_post_request({
'email': user.email,
})
with self.assert_one_mail_sent() as sent_emails, self.timer() as timer:
response = self.view_func(request)
self.assert_valid_response(response, status.HTTP_200_OK)
sent_email = sent_emails[0]
self._assert_valid_send_link_email(sent_email, user, timer)

@skip("TODO: Issue #35")
def test_send_link_disabled_user(self):
Expand All @@ -52,14 +115,11 @@ def test_send_link_unverified_user(self):
request = self.create_post_request({
'login': user.username,
})
time_before = math.floor(time.time())
with self.assert_one_mail_sent() as sent_emails:
with self.assert_one_mail_sent() as sent_emails, self.timer() as timer:
response = self.view_func(request)
time_after = math.ceil(time.time())
self.assert_valid_response(response, status.HTTP_200_OK)
sent_email = sent_emails[0]
self._assert_valid_send_link_email(
sent_email, user, time_before, time_after)
self._assert_valid_send_link_email(sent_email, user, timer)

@override_settings(
REST_REGISTRATION=shallow_merge_dicts(
Expand All @@ -86,8 +146,7 @@ def test_send_link_invalid_login(self):
response = self.view_func(request)
self.assert_response_is_not_found(response)

def _assert_valid_send_link_email(
self, sent_email, user, time_before, time_after):
def _assert_valid_send_link_email(self, sent_email, user, timer):
self.assertEqual(
sent_email.from_email,
REST_REGISTRATION_WITH_RESET_PASSWORD['VERIFICATION_FROM_EMAIL'],
Expand All @@ -101,8 +160,8 @@ def _assert_valid_send_link_email(
)
self.assertEqual(int(verification_data['user_id']), user.id)
url_sig_timestamp = int(verification_data['timestamp'])
self.assertGreaterEqual(url_sig_timestamp, time_before)
self.assertLessEqual(url_sig_timestamp, time_after)
self.assertGreaterEqual(url_sig_timestamp, timer.start_time)
self.assertLessEqual(url_sig_timestamp, timer.end_time)
signer = ResetPasswordSigner(verification_data)
signer.verify()

Expand Down

0 comments on commit bc09027

Please sign in to comment.