diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index c978f0b8d0..7e5aebfa35 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -2125,10 +2125,11 @@ class ZulipLDAPTestCase(ZulipTestCase): self.mock_ldap = MockLDAP() self.mock_initialize.return_value = self.mock_ldap self.backend = ZulipLDAPAuthBackend() - # Internally `_realm` attribute is automatically set by the - # `authenticate()` method. But for testing the `get_or_build_user()` - # method separately, we need to set it manually. + # Internally `_realm` and `_prereg_user` attributes are automatically set + # by the `authenticate()` method. But for testing the `get_or_build_user()` + # method separately, we need to set them manually. self.backend._realm = get_realm('zulip') + self.backend._prereg_user = None def tearDown(self) -> None: self.mock_ldap.reset() diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index e6d52c0b77..f8bee601b1 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -21,7 +21,7 @@ from confirmation.models import Confirmation, create_confirmation_link, Multiuse from confirmation import settings as confirmation_settings from zerver.forms import HomepageForm, WRONG_SUBDOMAIN_ERROR, check_subdomain_available -from zerver.lib.actions import do_change_password +from zerver.lib.actions import do_change_password, get_default_streams_for_realm from zerver.lib.exceptions import CannotDeactivateLastUserError from zerver.lib.dev_ldap_directory import init_fakeldap from zerver.decorator import do_two_factor_login @@ -59,6 +59,7 @@ from zerver.lib.mobile_auth_otp import xor_hex_strings, ascii_to_hex, \ from zerver.lib.notifications import enqueue_welcome_emails, \ followup_day2_email_delay from zerver.lib.subdomains import is_root_domain_available +from zerver.lib.stream_subscription import get_stream_subscriptions_for_user from zerver.lib.test_helpers import find_key_by_email, queries_captured, \ HostRequestMock, load_subdomain_token from zerver.lib.test_classes import ( @@ -2778,6 +2779,83 @@ class UserSignUpTest(ZulipTestCase): # Name comes from the POST request, not LDAP self.assertEqual(user_profile.full_name, 'Non-LDAP Full Name') + def ldap_invite_and_signup_as(self, invite_as: int, streams: List[str]=['Denmark']) -> None: + ldap_user_attr_map = {'full_name': 'fn'} + mock_directory = { + 'uid=newuser,ou=users,dc=zulip,dc=com': { + 'userPassword': ['testing'], + 'fn': ['LDAP Name'], + } + } + init_fakeldap(mock_directory) + + subdomain = 'zulip' + email = self.nonreg_email('newuser') + password = 'testing' + + # Invite user. + self.login(self.example_email('iago')) + response = self.client_post("/json/invites", + {"invitee_emails": [self.nonreg_email('newuser')], + "stream": streams, + "invite_as": invite_as}) + self.assert_json_success(response) + self.logout() + + with self.settings( + POPULATE_PROFILE_VIA_LDAP=True, + LDAP_APPEND_DOMAIN='zulip.com', + AUTH_LDAP_BIND_PASSWORD='', + AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map, + AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): + + result = self.submit_reg_form_for_user(email, + password, + full_name="Ignore", + from_confirmation="1", + # Pass HTTP_HOST for the target subdomain + HTTP_HOST=subdomain + ".testserver") + self.assertEqual(result.status_code, 200) + + result = self.submit_reg_form_for_user(email, + password, + full_name="Ignore", + # Pass HTTP_HOST for the target subdomain + HTTP_HOST=subdomain + ".testserver") + self.assertEqual(result.status_code, 302) + + @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend', + 'zproject.backends.EmailAuthBackend')) + def test_ldap_invite_user_as_admin(self) -> None: + self.ldap_invite_and_signup_as(PreregistrationUser.INVITE_AS['REALM_ADMIN']) + user_profile = UserProfile.objects.get(email=self.nonreg_email('newuser')) + self.assertTrue(user_profile.is_realm_admin) + + @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend', + 'zproject.backends.EmailAuthBackend')) + def test_ldap_invite_user_as_guest(self) -> None: + self.ldap_invite_and_signup_as(PreregistrationUser.INVITE_AS['GUEST_USER']) + user_profile = UserProfile.objects.get(email=self.nonreg_email('newuser')) + self.assertTrue(user_profile.is_guest) + + @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend', + 'zproject.backends.EmailAuthBackend')) + def test_ldap_invite_streams(self) -> None: + stream_name = 'Rome' + realm = get_realm('zulip') + stream = get_stream(stream_name, realm) + default_streams = get_default_streams_for_realm(realm) + default_streams_name = [stream.name for stream in default_streams] + self.assertNotIn(stream_name, default_streams_name) + + # Invite user. + self.ldap_invite_and_signup_as(PreregistrationUser.INVITE_AS['REALM_ADMIN'], streams=[stream_name]) + + user_profile = UserProfile.objects.get(email=self.nonreg_email('newuser')) + self.assertTrue(user_profile.is_realm_admin) + sub = get_stream_subscriptions_for_user(user_profile).filter(recipient__type_id=stream.id) + self.assertEqual(len(sub), 1) + def test_registration_when_name_changes_are_disabled(self) -> None: """ Test `name_changes_disabled` when we are not running under LDAP. diff --git a/zerver/views/registration.py b/zerver/views/registration.py index 7ebdfd6b7b..fc94d08450 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -244,6 +244,7 @@ def accounts_register(request: HttpRequest) -> HttpResponse: username=email, password=password, realm=realm, + prereg_user=prereg_user, return_data=return_data) if auth_result is not None: # Since we'll have created a user, we now just log them in. diff --git a/zproject/backends.py b/zproject/backends.py index 6a0e448f0f..fa8233af56 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -24,8 +24,9 @@ from zerver.lib.dev_ldap_directory import init_fakeldap from zerver.lib.request import JsonableError from zerver.lib.subdomains import user_matches_subdomain, get_subdomain from zerver.lib.users import check_full_name -from zerver.models import UserProfile, Realm, get_user_profile_by_id, \ - remote_user_to_email, email_to_username, get_realm, get_user_by_delivery_email +from zerver.models import PreregistrationUser, UserProfile, Realm, get_default_stream_groups, \ + get_user_profile_by_id, remote_user_to_email, email_to_username, get_realm, \ + get_user_by_delivery_email def pad_method_dict(method_dict: Dict[str, bool]) -> Dict[str, bool]: """Pads an authentication methods dict to contain all auth backends @@ -364,10 +365,12 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): REALM_IS_NONE_ERROR = 1 def authenticate(self, username: str, password: str, realm: Optional[Realm]=None, + prereg_user: Optional[PreregistrationUser]=None, return_data: Optional[Dict[str, Any]]=None) -> Optional[UserProfile]: if realm is None: return None self._realm = realm + self._prereg_user = prereg_user if not ldap_auth_enabled(realm): return None @@ -430,7 +433,15 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase): except JsonableError as e: raise ZulipLDAPException(e.msg) - user_profile = do_create_user(username, None, self._realm, full_name, short_name) + opts = {} # type: Dict[str, Any] + if self._prereg_user: + invited_as = self._prereg_user.invited_as + opts['prereg_user'] = self._prereg_user + opts['is_realm_admin'] = invited_as == PreregistrationUser.INVITE_AS['REALM_ADMIN'] + opts['is_guest'] = invited_as == PreregistrationUser.INVITE_AS['GUEST_USER'] + opts['default_stream_groups'] = get_default_stream_groups(self._realm) + + user_profile = do_create_user(username, None, self._realm, full_name, short_name, **opts) self.sync_avatar_from_ldap(user_profile, ldap_user) return user_profile, True