From 26b264adacd873e33082c9c269cb1ce67cc18e70 Mon Sep 17 00:00:00 2001 From: Ruben Grill Date: Fri, 14 Oct 2016 10:58:31 +0200 Subject: [PATCH 1/2] Add tests for email verification edge cases with missing associated email address --- rest_auth/tests/test_api.py | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index a24e9f0..681a0f1 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -10,6 +10,21 @@ from allauth.account import app_settings as account_app_settings from .test_base import BaseAPITestCase +class CustomUser(object): + """ + User without `emailaddress_set`. + Should not be able to login via API. + """ + + is_active = True + + +class CustomUserAuthenticationBackend(object): + + def authenticate(self, *args, **kwargs): + return CustomUser() + + @override_settings(ROOT_URLCONF="tests.urls") class APITestCase1(TestCase, BaseAPITestCase): """ @@ -468,6 +483,61 @@ class APITestCase1(TestCase, BaseAPITestCase): self._login() self._logout() + @override_settings( + ACCOUNT_EMAIL_VERIFICATION='mandatory', + ACCOUNT_EMAIL_REQUIRED=True, + ACCOUNT_EMAIL_CONFIRMATION_HMAC=False + ) + def test_registration_with_email_verification_but_missing_email_address(self): + """ + Possible if user was created without using the register API, e.g. in admin backend. + """ + + UserModel = get_user_model() + user = UserModel(username=self.USERNAME) + user.set_password(self.PASS) + user.save() + + payload = { + "username": self.USERNAME, + "password": self.PASS, + } + + response = self.post( + self.login_url, + data=payload, + status=status.HTTP_400_BAD_REQUEST + ) + + # Check against localized message to be sure that the user could not login because of an unverified email + self.assertEqual(response.data['non_field_errors'], ['E-mail is not verified.']) + + @override_settings( + ACCOUNT_EMAIL_VERIFICATION='mandatory', + ACCOUNT_EMAIL_REQUIRED=True, + ACCOUNT_EMAIL_CONFIRMATION_HMAC=False, + AUTHENTICATION_BACKENDS=['rest_auth.tests.test_api.CustomUserAuthenticationBackend', 'django.contrib.auth.backends.ModelBackend'] + ) + def test_registration_with_email_verification_and_custom_authentication_backend(self): + """ + Authenticated user must not strictly be of type AUTH_USER_MODEL. + Thus, it is possible that there is also not an email address associated to the user. + """ + + payload = { + "username": self.USERNAME, + "password": self.PASS, + } + + response = self.post( + self.login_url, + data=payload, + status=status.HTTP_400_BAD_REQUEST + ) + + # Check against localized message to be sure that the user could not login because of an unverified email + self.assertEqual(response.data['non_field_errors'], ['E-mail is not verified.']) + @override_settings(ACCOUNT_LOGOUT_ON_GET=True) def test_logout_on_get(self): payload = { From 1fc84ce03ed571161f779499b65a74d50beae377 Mon Sep 17 00:00:00 2001 From: Ruben Grill Date: Fri, 14 Oct 2016 10:25:59 +0200 Subject: [PATCH 2/2] Fix errors when authenticated user has no associated email address --- rest_auth/locale/de/LC_MESSAGES/django.mo | Bin 2300 -> 2271 bytes rest_auth/locale/de/LC_MESSAGES/django.po | 44 ++++++++++------------ rest_auth/serializers.py | 17 ++++++++- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/rest_auth/locale/de/LC_MESSAGES/django.mo b/rest_auth/locale/de/LC_MESSAGES/django.mo index 07861ff6d19caf35d4f2e04407f02f6af7402bd5..c0118c755208449c35c0f08fbc958f44901b5689 100644 GIT binary patch delta 503 zcmXxhy-LGS6u|M5n5OZonkuN&j|{CMRY|TNpd#WTh>JMaUFaf$a~`0h6dWBp3ITEO z0dx@5#lHFe&$xjvsNVxu zq=Gf;9!~Inf(9>e1Up#8J6ymnZepRYS7#SjL{e!m(1k6W#2c*PBi8W;r?BGnHdse$ z$tgBDR1>xETR|kk&=;9QV?9Q#caFO53QPEk^XxBQ3>KLPiXuB`u!?7>9kuQJ18S#! zAhLwBxQd4uVT#*$je5X$<{9 delta 531 zcmXxhze~eF6u|M9nAG3ZRK-da3$_-iN)mrSEsjFbK^)v&3IQuZaJD!If;fq|xVSkv z7#wvHC+nYJb#N31CqcyTUE?F~eJ+>q?s925KTiE6Wt|BzM2?aV549c!)k8V;|mQ4xh1rUs%FSo9\n" "Language-Team: LANGUAGE \n" @@ -18,82 +18,78 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: registration/serializers.py:54 +#: registration/serializers.py:52 msgid "View is not defined, pass it as a context variable" msgstr "\"View\" ist nicht definiert, übergib es als Contextvariable" -#: registration/serializers.py:59 +#: registration/serializers.py:57 msgid "Define adapter_class in view" msgstr "Definier \"adapter_class\" in view" -#: registration/serializers.py:78 +#: registration/serializers.py:76 msgid "Define callback_url in view" msgstr "Definier \"callback_url\" in view" -#: registration/serializers.py:82 +#: registration/serializers.py:80 msgid "Define client_class in view" msgstr "Definier \"client_class\" in view" -#: registration/serializers.py:102 +#: registration/serializers.py:100 msgid "Incorrect input. access_token or code is required." msgstr "Falsche Eingabe. \"access_token\" oder \"code\" erforderlich." -#: registration/serializers.py:111 +#: registration/serializers.py:109 msgid "Incorrect value" msgstr "Falscher Wert." -#: registration/serializers.py:140 +#: registration/serializers.py:138 msgid "A user is already registered with this e-mail address." msgstr "Ein User mit dieser E-Mail Adresse ist schon registriert." -#: registration/serializers.py:148 +#: registration/serializers.py:146 msgid "The two password fields didn't match." msgstr "Die beiden Passwörter sind nicht identisch." -#: registration/views.py:64 +#: registration/views.py:79 msgid "ok" msgstr "Ok" -#: serializers.py:29 +#: serializers.py:30 msgid "Must include \"email\" and \"password\"." msgstr "Muss \"email\" und \"password\" enthalten." -#: serializers.py:40 +#: serializers.py:41 msgid "Must include \"username\" and \"password\"." msgstr "Muss \"username\" und \"password\" enthalten." -#: serializers.py:53 +#: serializers.py:54 msgid "Must include either \"username\" or \"email\" and \"password\"." msgstr "Muss entweder \"username\" oder \"email\" und password \"password\"" -#: serializers.py:94 +#: serializers.py:95 msgid "User account is disabled." msgstr "Der Useraccount ist deaktiviert." -#: serializers.py:97 +#: serializers.py:98 msgid "Unable to log in with provided credentials." msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden." -#: serializers.py:106 +#: serializers.py:105 msgid "E-mail is not verified." msgstr "E-Mail Adresse ist nicht verifiziert." -#: serializers.py:152 -msgid "Error" -msgstr "Fehler" - -#: views.py:71 +#: views.py:120 msgid "Successfully logged out." msgstr "Erfolgreich ausgeloggt." -#: views.py:111 +#: views.py:161 msgid "Password reset e-mail has been sent." msgstr "Die E-Mail zum Zurücksetzen des Passwortes wurde verschickt." -#: views.py:132 +#: views.py:182 msgid "Password has been reset with the new password." msgstr "Das Passwort wurde mit dem neuen Passwort ersetzt." -#: views.py:150 +#: views.py:200 msgid "New password has been saved." msgstr "Das neue Passwort wurde gespeichert." diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 3d4faae..9769e5c 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model, authenticate from django.conf import settings from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm from django.contrib.auth.tokens import default_token_generator +from django.core.exceptions import ObjectDoesNotExist from django.utils.http import urlsafe_base64_decode as uid_decoder from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text @@ -100,10 +101,22 @@ class LoginSerializer(serializers.Serializer): # If required, is the email verified? if 'rest_auth.registration' in settings.INSTALLED_APPS: from allauth.account import app_settings + + email_not_verified_msg = _('E-mail is not verified.') + if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY: - email_address = user.emailaddress_set.get(email=user.email) + # The authenticated user must not strictly be an instance of AUTH_USER_MODEL, + # depending on used authentication backends + if not hasattr(user, 'emailaddress_set'): + raise serializers.ValidationError(email_not_verified_msg) + + try: + email_address = user.emailaddress_set.get(email=user.email) + except ObjectDoesNotExist: + raise serializers.ValidationError(email_not_verified_msg) + if not email_address.verified: - raise serializers.ValidationError(_('E-mail is not verified.')) + raise serializers.ValidationError(email_not_verified_msg) attrs['user'] = user return attrs