0001-whitelist-send_registration_email_next_url-using-HMA.patch
src/authentic2/constants.py | ||
---|---|---|
21 | 21 |
SWITCH_USER_SESSION_KEY = '_switch_user' |
22 | 22 |
LAST_LOGIN_SESSION_KEY = '_last_login' |
23 | 23 |
SERVICE_FIELD_NAME = 'service' |
24 |
NEXT_URL_SIGNATURE = 'next-signature' |
src/authentic2/crypto.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import base64 |
18 | 18 |
import hashlib |
19 |
import hmac |
|
19 | 20 |
import struct |
20 | 21 | |
21 | 22 |
from Crypto.Cipher import AES |
... | ... | |
24 | 25 |
from Crypto.Hash import HMAC |
25 | 26 |
from Crypto import Random |
26 | 27 | |
28 |
from django.utils.crypto import constant_time_compare |
|
29 | ||
27 | 30 | |
28 | 31 |
class DecryptionError(Exception): |
29 | 32 |
pass |
... | ... | |
177 | 180 |
if not raise_on_error: |
178 | 181 |
return None |
179 | 182 |
raise |
183 | ||
184 | ||
185 |
def hmac_url(key, url): |
|
186 |
if hasattr(key, 'encode'): |
|
187 |
key = key.encode('utf-8') |
|
188 |
if hasattr(url, 'decode'): |
|
189 |
url = url.encode('ascii') |
|
190 |
return base64.b32encode(hmac.HMAC(key=key, msg=url, digestmod=hashlib.sha256).digest()).strip('=') |
|
191 | ||
192 | ||
193 |
def check_hmac_url(key, url, signature): |
|
194 |
if hasattr(signature, 'decode'): |
|
195 |
signature = signature.decode('ascii') |
|
196 |
return constant_time_compare(signature, hmac_url(key, url)) |
src/authentic2/utils.py | ||
---|---|---|
62 | 62 |
from authentic2.saml.saml2utils import filter_attribute_private_key, \ |
63 | 63 |
filter_element_private_key |
64 | 64 | |
65 |
from . import plugins, app_settings, constants |
|
65 |
from . import plugins, app_settings, constants, crypto
|
|
66 | 66 | |
67 | 67 | |
68 | 68 |
class CleanLogMessage(logging.Filter): |
... | ... | |
731 | 731 |
logger.info(u'account deletion mail sent to %s', user.email) |
732 | 732 | |
733 | 733 | |
734 |
def build_reset_password_url(user, request=None, next_url=None, set_random_password=True): |
|
734 |
def build_reset_password_url(user, request=None, next_url=None, set_random_password=True, sign_next_url=True):
|
|
735 | 735 |
'''Build a reset password URL''' |
736 | 736 |
from .compat import default_token_generator |
737 | 737 | |
... | ... | |
744 | 744 |
if request: |
745 | 745 |
reset_url = request.build_absolute_uri(reset_url) |
746 | 746 |
if next_url: |
747 |
reset_url += '?' + urlparse.urlencode({'next': next_url}) |
|
747 |
params = {'next': next_url} |
|
748 |
if sign_next_url: |
|
749 |
params[constants.NEXT_URL_SIGNATURE] = crypto.hmac_url(settings.SECRET_KEY, next_url) |
|
750 |
reset_url += '?' + urlparse.urlencode(params) |
|
748 | 751 |
return reset_url, token |
749 | 752 | |
750 | 753 | |
... | ... | |
754 | 757 |
legacy_subject_templates=['registration/password_reset_subject.txt'], |
755 | 758 |
legacy_body_templates=['registration/password_reset_email.html'], |
756 | 759 |
set_random_password=True, |
760 |
sign_next_url=True, |
|
757 | 761 |
**kwargs): |
758 | 762 |
from . import middleware |
759 | 763 | |
... | ... | |
775 | 779 | |
776 | 780 |
# Build reset URL |
777 | 781 |
ctx['reset_url'], token = build_reset_password_url(user, request=request, next_url=next_url, |
778 |
set_random_password=set_random_password) |
|
782 |
set_random_password=set_random_password, |
|
783 |
sign_next_url=sign_next_url) |
|
779 | 784 | |
780 | 785 |
send_templated_mail(user.email, template_names, ctx, request=request, |
781 | 786 |
legacy_subject_templates=legacy_subject_templates, |
... | ... | |
894 | 899 |
return True |
895 | 900 |
if same_origin(request.build_absolute_uri(), next_url): |
896 | 901 |
return True |
902 |
signature = request.POST.get(constants.NEXT_URL_SIGNATURE) or request.GET.get(constants.NEXT_URL_SIGNATURE) |
|
903 | ||
904 |
if signature: |
|
905 |
return crypto.check_hmac_url(settings.SECRET_KEY, next_url, signature) |
|
906 | ||
897 | 907 |
for origin in app_settings.A2_REDIRECT_WHITELIST: |
898 | 908 |
if same_origin(next_url, origin): |
899 | 909 |
return True |
src/authentic2/views.py | ||
---|---|---|
644 | 644 |
def get_form_kwargs(self, **kwargs): |
645 | 645 |
kwargs = super(PasswordResetView, self).get_form_kwargs(**kwargs) |
646 | 646 |
initial = kwargs.setdefault('initial', {}) |
647 |
initial['next_url'] = self.request.GET.get(REDIRECT_FIELD_NAME, '')
|
|
647 |
initial['next_url'] = utils.select_next_url(self.request, '')
|
|
648 | 648 |
return kwargs |
649 | 649 | |
650 | 650 |
def get_context_data(self, **kwargs): |
tests/test_api.py | ||
---|---|---|
575 | 575 |
'email': 'john.doe@example.net', |
576 | 576 |
'title': 'Mr', |
577 | 577 |
'send_registration_email': True, |
578 |
'send_registration_email_next_url': 'http://example.com/', |
|
578 | 579 |
} |
579 | 580 |
assert len(mail.outbox) == 0 |
580 | 581 |
resp = app.post_json('/api/users/', params=payload, status=201) |
... | ... | |
586 | 587 |
resp = app.get(relative_url, status=200) |
587 | 588 |
resp.form.set('new_password1', '1234==aA') |
588 | 589 |
resp.form.set('new_password2', '1234==aA') |
589 |
resp = resp.form.submit().follow()
|
|
590 |
resp = resp.form.submit() |
|
590 | 591 |
# Check user was properly logged in |
591 | 592 |
assert str(app.session['_auth_user_id']) == str(user_id) |
593 |
assert resp.location == 'http://example.com/' |
|
592 | 594 | |
593 | 595 | |
594 | 596 |
def test_api_users_create_force_password_reset(app, client, settings, superuser): |
595 |
- |