Skip to content
This repository was archived by the owner on May 5, 2020. It is now read-only.

Commit 19ae435

Browse files
committed
Merge pull request #54 from smajda/host_from_request
Pass host from request to login_url
2 parents 63b6f2b + 5466877 commit 19ae435

File tree

10 files changed

+62
-31
lines changed

10 files changed

+62
-31
lines changed

docs/settings.rst

+11-3
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,33 @@ django-nopassword settings
2727
By default, the login code url requires a POST request to authenticate the user. A GET request renders ``registration/login_submit.html``, which contains some Javascript that automatically performs the POST on page load. To authenticate directly inside the initial GET request instead, set this to ``False``.
2828

2929
.. attribute:: NOPASSWORD_CODE_LENGTH
30+
3031
The length of the code used to log people in. Default is 20.
3132

3233
.. attribute:: NOPASSWORD_TWILIO_SID
34+
3335
Account ID for Twilio.
3436

3537
.. attribute:: NOPASSWORD_TWILIO_AUTH_TOKEN
38+
3639
Account secret for Twilio
3740

3841
.. attribute:: NOPASSWORD_NUMERIC_CODES
42+
3943
A boolean flag if set to True, codes will contain numeric characters only (0-9). Default: False
4044

4145
Django settings used in django-nopassword
4246
+++++++++++++++++++++++++++++++++++++++++
4347

4448
.. attribute:: SERVER_URL
4549

46-
Default: `example.com`
50+
Default: `example.com`
4751

48-
.. attribute:: DEFAULT_FROM_EMAIL
52+
By default, ``nopassword.views.login`` passes the result of ``result.get_host()`` to
53+
``LoginCode.send_login_code`` to build the login URL. If you write your own view
54+
and/or want to avoid this behavior by not passing a value for host, the
55+
``SERVER_URL`` setting will be used instead.
4956

50-
57+
.. attribute:: DEFAULT_FROM_EMAIL
5158

59+
Default: `[email protected]`

docs/usage.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ the *EmailBackend*::
4848

4949
class EmailBackend(NoPasswordBackend):
5050

51-
def send_login_code(self, code, secure=False):
51+
def send_login_code(self, code, secure=False, host=None):
5252
subject = getattr(settings, 'NOPASSWORD_LOGIN_EMAIL_SUBJECT', _('Login code'))
5353
to_email = [code.user.email]
5454
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', '[email protected]')
5555

56-
context = {'url': code.login_url(secure), 'code': code}
56+
context = {'url': code.login_url(secure=secure, host=host), 'code': code}
5757
text_content = render_to_string('registration/login_email.txt', context)
5858
html_content = render_to_string('registration/login_email.html', context)
5959

nopassword/backends/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def get_user(self, user_id):
3232
except get_user_model().DoesNotExist:
3333
return None
3434

35-
def send_login_code(self, code, secure=False, **kwargs):
35+
def send_login_code(self, code, secure=False, host=None, **kwargs):
3636
raise NotImplementedError
3737

3838
def verify_user(self, user):

nopassword/backends/email.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99

1010
class EmailBackend(NoPasswordBackend):
1111

12-
def send_login_code(self, code, secure=False, **kwargs):
12+
def send_login_code(self, code, secure=False, host=None, **kwargs):
1313
subject = getattr(settings, 'NOPASSWORD_LOGIN_EMAIL_SUBJECT', _('Login code'))
1414
to_email = [code.user.email]
1515
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', '[email protected]')
1616

17-
context = {'url': code.login_url(secure=secure), 'code': code}
17+
context = {'url': code.login_url(secure=secure, host=host), 'code': code}
1818
text_content = render_to_string('registration/login_email.txt', context)
1919
html_content = render_to_string('registration/login_email.html', context)
2020

nopassword/backends/sms.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ def __init__(self):
1414
)
1515
super(TwilioBackend, self).__init__()
1616

17-
def send_login_code(self, code, secure=False, **kwargs):
17+
def send_login_code(self, code, secure=False, host=None, **kwargs):
1818
"""
1919
Send a login code via SMS
2020
"""
2121
from_number = getattr(settings, 'DEFAULT_FROM_NUMBER')
2222

23-
context = {'url': code.login_url(secure=secure), 'code': code}
23+
context = {'url': code.login_url(secure=secure, host=host), 'code': code}
2424
sms_content = render_to_string('registration/login_sms.txt', context)
2525

2626
self.twilio_client.messages.create(

nopassword/models.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ def save(self, *args, **kwargs):
3333
self.next = '/'
3434
super(LoginCode, self).save(*args, **kwargs)
3535

36-
def login_url(self, secure=False):
36+
def login_url(self, secure=False, host=None):
3737
username = get_username(self.user)
38+
host = host or getattr(settings, 'SERVER_URL', None) or 'example.com'
3839
if getattr(settings, 'NOPASSWORD_HIDE_USERNAME', False):
3940
view = reverse_lazy('nopassword.views.login_with_code', args=[self.code]),
4041
else:
@@ -43,15 +44,15 @@ def login_url(self, secure=False):
4344

4445
return '%s://%s%s?next=%s' % (
4546
'https' if secure else 'http',
46-
getattr(settings, 'SERVER_URL', 'example.com'),
47+
host,
4748
view[0],
4849
self.next
4950
)
5051

51-
def send_login_code(self, secure=False, **kwargs):
52+
def send_login_code(self, secure=False, host=None, **kwargs):
5253
for backend in get_backends():
5354
if hasattr(backend, 'send_login_code'):
54-
backend.send_login_code(self, secure=secure, **kwargs)
55+
backend.send_login_code(self, secure=secure, host=host, **kwargs)
5556

5657
@classmethod
5758
def create_code_for_user(cls, user, next=None):

nopassword/views.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ def login(request):
1919
})[0]
2020
code.next = request.GET.get('next')
2121
code.save()
22-
code.send_login_code(secure=request.is_secure())
22+
code.send_login_code(
23+
secure=request.is_secure(),
24+
host=request.get_host(),
25+
)
2326
return render(request, 'registration/sent_mail.html')
2427

2528
return django_login(request, authentication_form=AuthenticationForm)

tests/test_backends.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ def test_twilio_backend(self, mock_object):
5757
def test_twilio_backend_with_https(self, mock_object):
5858
self.backend = TwilioBackend()
5959
self.backend.twilio_client.messages.create = MagicMock()
60-
self.backend.send_login_code(self.code, secure=True)
60+
self.backend.send_login_code(self.code, secure=True, host='secure.example.com')
6161
_, kwargs = self.backend.twilio_client.messages.create.call_args
62-
self.assertIn(self.code.login_url(secure=True), kwargs.get('body'))
62+
login_url = self.code.login_url(secure=True, host='secure.example.com')
63+
self.assertIn(login_url, kwargs.get('body'))
6364

6465

6566
@skipIf(django.VERSION < (1, 5), 'Custom user not supported')
@@ -89,10 +90,10 @@ def test_email_backend(self):
8990
def test_email_backend_with_https(self):
9091
"Send email via EmailBackend with secure=True"
9192
mail.outbox = []
92-
self.backend.send_login_code(self.code, secure=True)
93+
self.backend.send_login_code(self.code, secure=True, host='secure.example.com')
9394
self.assertEqual(1, len(mail.outbox))
9495
message = mail.outbox[0]
95-
https_url = self.code.login_url(secure=True)
96+
https_url = self.code.login_url(secure=True, host='secure.example.com')
9697
self.assertTrue(https_url.startswith('https:'))
9798
self.assertIn(https_url, message.body)
9899

tests/test_models.py

+28-10
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ class TestLoginCodes(unittest.TestCase):
1313
def setUp(self):
1414
self.user = get_user_model().objects.create(username='test_user')
1515
self.inactive_user = get_user_model().objects.create(username='inactive', is_active=False)
16+
self.code = LoginCode.create_code_for_user(self.user)
1617

1718
def tearDown(self):
1819
self.user.delete()
1920
self.inactive_user.delete()
2021

2122
def test_login_backend(self):
22-
self.code = LoginCode.create_code_for_user(self.user)
2323
self.assertEqual(len(self.code.code), 20)
2424
self.assertIsNotNone(authenticate(username=self.user.username, code=self.code.code))
2525
self.assertEqual(LoginCode.objects.filter(user=self.user, code=self.code.code).count(), 0)
@@ -32,21 +32,39 @@ def test_login_backend(self):
3232

3333
@override_settings(NOPASSWORD_CODE_LENGTH=8)
3434
def test_shorter_code(self):
35-
self.code = LoginCode.create_code_for_user(self.user)
36-
self.assertEqual(len(self.code.code), 8)
35+
code = LoginCode.create_code_for_user(self.user)
36+
self.assertEqual(len(code.code), 8)
3737

3838
@override_settings(NOPASSWORD_NUMERIC_CODES=True)
3939
def test_numeric_code(self):
40-
self.code = LoginCode.create_code_for_user(self.user)
41-
self.assertEqual(len(self.code.code), 20)
42-
self.assertTrue(self.code.code.isdigit())
40+
code = LoginCode.create_code_for_user(self.user)
41+
self.assertEqual(len(code.code), 20)
42+
self.assertTrue(code.code.isdigit())
4343

4444
def test_next_value(self):
45-
self.code = LoginCode.create_code_for_user(self.user, next='/secrets/')
46-
self.assertEqual(self.code.next, '/secrets/')
45+
code = LoginCode.create_code_for_user(self.user, next='/secrets/')
46+
self.assertEqual(code.next, '/secrets/')
4747

4848
@override_settings(NOPASSWORD_LOGIN_CODE_TIMEOUT=1)
4949
def test_code_timeout(self):
50-
self.timeout_code = LoginCode.create_code_for_user(self.user)
50+
timeout_code = LoginCode.create_code_for_user(self.user)
5151
time.sleep(3)
52-
self.assertIsNone(authenticate(username=self.user.username, code=self.timeout_code.code))
52+
self.assertIsNone(authenticate(username=self.user.username, code=timeout_code.code))
53+
54+
def test_login_url_secure(self):
55+
self.assertTrue(self.code.login_url(secure=True).startswith('https:'))
56+
57+
def test_login_url_insecure(self):
58+
self.assertTrue(self.code.login_url().startswith('http:'))
59+
60+
def test_login_url_host(self):
61+
host = 'nopassword.example.com'
62+
self.assertIn(host, self.code.login_url(host=host))
63+
64+
@override_settings(SERVER_URL='server_url_setting.example.com')
65+
def test_login_url_default_setting(self):
66+
self.assertIn('server_url_setting.example.com', self.code.login_url())
67+
68+
@override_settings(SERVER_URL=None)
69+
def test_login_url_no_setting(self):
70+
self.assertIn('example.com', self.code.login_url())

tests/test_views.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ def test_https_request(self, mock_send_login_code):
8484
{'username': self.user.username},
8585
**{'wsgi.url_scheme': 'https'})
8686
self.assertEqual(login.status_code, 200)
87-
mock_send_login_code.assert_called_with(secure=True)
87+
mock_send_login_code.assert_called_with(secure=True, host='testserver:80')
8888

8989
@patch.object(LoginCode, 'send_login_code')
9090
def test_http_request(self, mock_send_login_code):
9191
login = self.c.post('/accounts/login/?next=/secret/',
9292
{'username': self.user.username},
9393
**{'wsgi.url_scheme': 'http'})
9494
self.assertEqual(login.status_code, 200)
95-
mock_send_login_code.assert_called_with(secure=False)
95+
mock_send_login_code.assert_called_with(secure=False, host='testserver')

0 commit comments

Comments
 (0)