diff --git a/zerver/forms.py b/zerver/forms.py index 661583ed41..cea7b1e88e 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -288,8 +288,9 @@ Please contact %s to reactivate this group.""" % ( if username is not None and password: subdomain = get_subdomain(self.request) + realm = get_realm(subdomain) self.user_cache = authenticate(self.request, username=username, password=password, - realm_subdomain=subdomain) + realm=realm) if self.user_cache is None: raise forms.ValidationError( self.error_messages['invalid_login'], diff --git a/zerver/lib/subdomains.py b/zerver/lib/subdomains.py index a9ed87d3b5..b4bcae98ab 100644 --- a/zerver/lib/subdomains.py +++ b/zerver/lib/subdomains.py @@ -43,7 +43,7 @@ def is_subdomain_root_or_alias(request: HttpRequest) -> bool: def user_matches_subdomain(realm_subdomain: Optional[Text], user_profile: UserProfile) -> bool: if realm_subdomain is None: - return True + return True # nocoverage # This state may no longer be possible. return user_profile.realm.subdomain == realm_subdomain def is_root_domain_available() -> bool: diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index bdf5c76931..bda498937b 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -292,10 +292,10 @@ class ZulipTestCase(TestCase): password = initial_password(email) if not fails: self.assertTrue(self.client.login(username=email, password=password, - realm_subdomain=realm.subdomain)) + realm=realm)) else: self.assertFalse(self.client.login(username=email, password=password, - realm_subdomain=realm.subdomain)) + realm=realm)) def logout(self): # type: () -> None diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 9901beda7e..a96a111c67 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -168,6 +168,7 @@ class AuthBackendTest(ZulipTestCase): return_value=True): return_data = {} # type: Dict[str, bool] user = EmailAuthBackend().authenticate(self.example_email('hamlet'), + realm=get_realm("zulip"), password=password, return_data=return_data) self.assertEqual(user, None) @@ -176,11 +177,20 @@ class AuthBackendTest(ZulipTestCase): self.verify_backend(EmailAuthBackend(), good_kwargs=dict(password=password, username=username, - realm_subdomain='zulip', + realm=get_realm('zulip'), return_data=dict()), bad_kwargs=dict(password=password, username=username, - realm_subdomain='acme', + realm=get_realm('zephyr'), + return_data=dict())) + self.verify_backend(EmailAuthBackend(), + good_kwargs=dict(password=password, + username=username, + realm=get_realm('zulip'), + return_data=dict()), + bad_kwargs=dict(password=password, + username=username, + realm=None, return_data=dict())) def test_email_auth_backend_disabled_password_auth(self): @@ -191,7 +201,9 @@ class AuthBackendTest(ZulipTestCase): user_profile.save() # Verify if a realm has password auth disabled, correct password is rejected with mock.patch('zproject.backends.password_auth_enabled', return_value=False): - self.assertIsNone(EmailAuthBackend().authenticate(self.example_email('hamlet'), password)) + self.assertIsNone(EmailAuthBackend().authenticate(self.example_email('hamlet'), + password, + realm=get_realm("zulip"))) @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',)) def test_no_backend_enabled(self): @@ -266,7 +278,7 @@ class AuthBackendTest(ZulipTestCase): mock.patch('django_auth_ldap.backend._LDAPUser._check_requirements')), ( mock.patch('django_auth_ldap.backend._LDAPUser._get_user_attrs', return_value=dict(full_name=['Hamlet']))): - self.assertIsNone(backend.authenticate(email, password)) + self.assertIsNone(backend.authenticate(email, password, realm=get_realm("zulip"))) with mock.patch('django_auth_ldap.backend._LDAPUser._authenticate_user_dn'), ( mock.patch('django_auth_ldap.backend._LDAPUser._check_requirements')), ( @@ -275,10 +287,17 @@ class AuthBackendTest(ZulipTestCase): self.verify_backend(backend, bad_kwargs=dict(username=username, password=password, - realm_subdomain='acme'), + realm=get_realm('zephyr')), good_kwargs=dict(username=username, password=password, - realm_subdomain='zulip')) + realm=get_realm('zulip'))) + self.verify_backend(backend, + bad_kwargs=dict(username=username, + password=password, + realm=get_realm('acme')), + good_kwargs=dict(username=username, + password=password, + realm=get_realm('zulip'))) def test_devauth_backend(self): # type: () -> None @@ -1864,7 +1883,8 @@ class TestLDAP(ZulipTestCase): LDAP_APPEND_DOMAIN='zulip.com', AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): - user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing') + user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing', + realm=get_realm('zulip')) assert(user_profile is not None) self.assertEqual(user_profile.email, self.example_email("hamlet")) @@ -1881,7 +1901,8 @@ class TestLDAP(ZulipTestCase): with self.settings(LDAP_EMAIL_ATTR='email', AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): - user_profile = self.backend.authenticate("letham", 'testing') + user_profile = self.backend.authenticate("letham", 'testing', + realm=get_realm('zulip')) assert (user_profile is not None) self.assertEqual(user_profile.email, self.example_email("hamlet")) @@ -2055,11 +2076,11 @@ class TestLDAP(ZulipTestCase): AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing', - realm_subdomain='acme') + realm=get_realm('zephyr')) self.assertIs(user_profile, None) @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',)) - def test_login_failure_due_to_empty_subdomain(self): + def test_login_failure_due_to_invalid_subdomain(self): # type: () -> None self.mock_ldap.directory = { 'uid=hamlet,ou=users,dc=zulip,dc=com': { @@ -2071,26 +2092,9 @@ class TestLDAP(ZulipTestCase): AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing', - realm_subdomain='') + realm=None) self.assertIs(user_profile, None) - @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',)) - def test_login_success_when_subdomain_is_none(self): - # type: () -> None - self.mock_ldap.directory = { - 'uid=hamlet,ou=users,dc=zulip,dc=com': { - 'userPassword': 'testing' - } - } - with self.settings( - LDAP_APPEND_DOMAIN='zulip.com', - AUTH_LDAP_BIND_PASSWORD='', - AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): - user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing', - realm_subdomain=None) - assert(user_profile is not None) - self.assertEqual(user_profile.email, self.example_email("hamlet")) - @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',)) def test_login_success_with_valid_subdomain(self): # type: () -> None @@ -2104,7 +2108,7 @@ class TestLDAP(ZulipTestCase): AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing', - realm_subdomain='zulip') + realm=get_realm('zulip')) assert(user_profile is not None) self.assertEqual(user_profile.email, self.example_email("hamlet")) @@ -2122,7 +2126,7 @@ class TestLDAP(ZulipTestCase): AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=acme,dc=com'): user_profile = self.backend.authenticate('nonexisting@acme.com', 'testing', - realm_subdomain='zulip') + realm=get_realm('zulip')) assert(user_profile is not None) self.assertEqual(user_profile.email, 'nonexisting@acme.com') self.assertEqual(user_profile.full_name, 'NonExisting') diff --git a/zerver/views/auth.py b/zerver/views/auth.py index 63b81421fe..151e455df5 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -644,10 +644,10 @@ def api_dev_get_emails(request): def api_fetch_api_key(request, username=REQ(), password=REQ()): # type: (HttpRequest, str, str) -> HttpResponse return_data = {} # type: Dict[str, bool] + subdomain = get_subdomain(request) + realm = get_realm(subdomain) if username == "google-oauth2-token": # This code path is auth for the legacy Android app - subdomain = get_subdomain(request) - realm = get_realm(subdomain) user_profile = authenticate(google_oauth2_token=password, realm=realm, return_data=return_data) @@ -659,7 +659,7 @@ def api_fetch_api_key(request, username=REQ(), password=REQ()): user_profile = authenticate(username=username, password=password, - realm_subdomain=get_subdomain(request), + realm=realm, return_data=return_data) if return_data.get("inactive_user"): return json_error(_("Your account has been disabled."), @@ -756,9 +756,11 @@ def api_get_server_settings(request): @has_request_variables def json_fetch_api_key(request, user_profile, password=REQ(default='')): # type: (HttpRequest, UserProfile, str) -> HttpResponse + subdomain = get_subdomain(request) + realm = get_realm(subdomain) if password_auth_enabled(user_profile.realm): if not authenticate(username=user_profile.email, password=password, - realm_subdomain=get_subdomain(request)): + realm=realm): return json_error(_("Your username or password is incorrect.")) return json_success({"api_key": user_profile.api_key}) diff --git a/zerver/views/registration.py b/zerver/views/registration.py index aa15988c1e..ed53088e1f 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -191,7 +191,7 @@ def accounts_register(request): auth_result = authenticate(request, username=email, password=password, - realm_subdomain=realm.subdomain, + realm=realm, return_data=return_data) if auth_result is None: # TODO: This probably isn't going to give a diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index 630ba9399d..c22819d9ec 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -92,7 +92,8 @@ def json_change_settings(request, user_profile, if new_password != "" or confirm_password != "": if new_password != confirm_password: return json_error(_("New password must match confirmation password!")) - if not authenticate(username=user_profile.email, password=old_password): + if not authenticate(username=user_profile.email, password=old_password, + realm=user_profile.realm): return json_error(_("Wrong password!")) do_change_password(user_profile, new_password) # In Django 1.10, password changes invalidates sessions, see diff --git a/zproject/backends.py b/zproject/backends.py index b25bd3fb2b..9afa225a8d 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -335,7 +335,7 @@ class EmailAuthBackend(ZulipAuthMixin): """ def authenticate(self, username: Optional[str]=None, password: Optional[str]=None, - realm_subdomain: Optional[str]=None, + realm: Optional[Realm]=None, return_data: Optional[Dict[str, Any]]=None) -> Optional[UserProfile]: """ Authenticate a user based on email address as the user name. """ if username is None or password is None: @@ -343,20 +343,22 @@ class EmailAuthBackend(ZulipAuthMixin): # specify which backend to use when not using # EmailAuthBackend, username and password should always be set. raise AssertionError("Invalid call to authenticate for EmailAuthBackend") + if realm is None: + return None user_profile = common_get_active_user_by_email(username, return_data=return_data) if user_profile is None: return None - if not password_auth_enabled(user_profile.realm): + if not password_auth_enabled(realm): if return_data is not None: return_data['password_auth_disabled'] = True return None - if not email_auth_enabled(user_profile.realm): + if not email_auth_enabled(realm): if return_data is not None: return_data['email_auth_disabled'] = True return None if user_profile.check_password(password): - if not user_matches_subdomain(realm_subdomain, user_profile): + if not user_matches_subdomain(realm.subdomain, user_profile): if return_data is not None: return_data["invalid_subdomain"] = True return None @@ -460,21 +462,22 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend): class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): REALM_IS_NONE_ERROR = 1 - def authenticate(self, username: str, password: str, realm_subdomain: Optional[Text]=None, + def authenticate(self, username: str, password: str, realm: Optional[Realm]=None, return_data: Optional[Dict[str, Any]]=None) -> Optional[UserProfile]: + if realm is None: + return None + self._realm = realm + try: - self._realm = get_realm(realm_subdomain) username = self.django_to_ldap_username(username) user_profile = ZulipLDAPAuthBackendBase.authenticate(self, username=username, password=password) if user_profile is None: return None - if not user_matches_subdomain(realm_subdomain, user_profile): + if not user_matches_subdomain(realm.subdomain, user_profile): return None return user_profile - except Realm.DoesNotExist: - return None # nocoverage # TODO: this may no longer be possible except ZulipLDAPException: return None # nocoverage # TODO: this may no longer be possible @@ -518,7 +521,7 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): # Just like ZulipLDAPAuthBackend, but doesn't let you log in. class ZulipLDAPUserPopulator(ZulipLDAPAuthBackendBase): - def authenticate(self, username: str, password: str, realm_subdomain: Optional[Text]=None, + def authenticate(self, username: str, password: str, realm: Optional[Realm]=None, return_data: Optional[Dict[str, Any]]=None) -> None: return None