Repository: zulip/zulip

Issue:
Hiding email adresses with ldap caused everyone to be locked out
I am sorry to bother you again but I managed to lock myself and everyone out.

I am running current master branch due to needing realm admin perms based on ldap user groups.

I flipped some switches in the Admin Section and now no one can log in. I am not sure what setting exactly caused this but I remember to hide email adresses from other users. When I ran (and run) ldap sync I get this result:

```
2019-12-15 16:50:48.895 INFO [zulip.sync_ldap_user_data] Starting update.
2019-12-15 16:50:48.954 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user10@<domain>
2019-12-15 16:50:48.994 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user15@<domain>
2019-12-15 16:50:49.034 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user16@<domain>
2019-12-15 16:50:49.067 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user17@<domain>
2019-12-15 16:50:49.103 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user18@<domain>
2019-12-15 16:50:49.138 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user19@<domain>
2019-12-15 16:50:49.172 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user11@<domain>
2019-12-15 16:50:49.206 INFO [zulip.sync_ldap_user_data] Deactivated non-matching user: user12@<domain>
(and a bunch more)
```

Where <domain> is the main domain of the instance. The usernames are obviously not userXY. I am using cn and email from ldap  as log in. query_ldap stil works, I can retrieve the Full name and email from the ldap using either cn or email.


Discussion:
Ideally I would like to fix this with psql. But I guess this is also some kind of bug that should not happen.

Resolving patch:
diff --git a/zproject/backends.py b/zproject/backends.py
--- a/zproject/backends.py
+++ b/zproject/backends.py
@@ -682,13 +682,13 @@ def get_or_build_user(self, username: str,
             if user_disabled_in_ldap:
                 if user.is_active:
                     logging.info("Deactivating user %s because they are disabled in LDAP." %
-                                 (user.email,))
+                                 (user.delivery_email,))
                     do_deactivate_user(user)
                 # Do an early return to avoid trying to sync additional data.
                 return (user, built)
             elif not user.is_active:
                 logging.info("Reactivating user %s because they are not disabled in LDAP." %
-                             (user.email,))
+                             (user.delivery_email,))
                 do_reactivate_user(user)
 
         self.sync_avatar_from_ldap(user, ldap_user)
@@ -717,14 +717,14 @@ def catch_ldap_error(signal: Signal, **kwargs: Any) -> None:
 def sync_user_from_ldap(user_profile: UserProfile, logger: logging.Logger) -> bool:
     backend = ZulipLDAPUserPopulator()
     try:
-        ldap_username = backend.django_to_ldap_username(user_profile.email)
+        ldap_username = backend.django_to_ldap_username(user_profile.delivery_email)
     except ZulipLDAPExceptionNoMatchingLDAPUser:
         if settings.LDAP_DEACTIVATE_NON_MATCHING_USERS:
             do_deactivate_user(user_profile)
-            logger.info("Deactivated non-matching user: %s" % (user_profile.email,))
+            logger.info("Deactivated non-matching user: %s" % (user_profile.delivery_email,))
             return True
         elif user_profile.is_active:
-            logger.warning("Did not find %s in LDAP." % (user_profile.email,))
+            logger.warning("Did not find %s in LDAP." % (user_profile.delivery_email,))
         return False
 
     # What one would expect to see like to do here is just a call to
@@ -744,7 +744,7 @@ def sync_user_from_ldap(user_profile: UserProfile, logger: logging.Logger) -> bo
     # making this flow possible in a more directly supported fashion.
     updated_user = ZulipLDAPUser(backend, ldap_username, realm=user_profile.realm).populate_user()
     if updated_user:
-        logger.info("Updated %s." % (user_profile.email,))
+        logger.info("Updated %s." % (user_profile.delivery_email,))
         return True
 
     raise PopulateUserLDAPError("populate_user unexpectedly returned {}".format(updated_user))


Test patch:
diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py
--- a/zerver/tests/test_auth_backends.py
+++ b/zerver/tests/test_auth_backends.py
@@ -28,6 +28,7 @@
     do_invite_users,
     do_reactivate_realm,
     do_reactivate_user,
+    do_set_realm_property,
     ensure_stream,
     validate_email,
 )
@@ -2986,6 +2987,18 @@ def test_update_full_name(self) -> None:
         hamlet = self.example_user('hamlet')
         self.assertEqual(hamlet.full_name, 'New Name')
 
+    def test_update_with_hidden_emails(self) -> None:
+        hamlet = self.example_user('hamlet')
+        realm = get_realm("zulip")
+        do_set_realm_property(realm, 'email_address_visibility', Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
+        hamlet.refresh_from_db()
+
+        self.change_ldap_user_attr('hamlet', 'cn', 'New Name')
+        self.perform_ldap_sync(hamlet)
+
+        hamlet.refresh_from_db()
+        self.assertEqual(hamlet.full_name, 'New Name')
+
     def test_update_split_full_name(self) -> None:
         self.change_ldap_user_attr('hamlet', 'cn', 'Name')
         self.change_ldap_user_attr('hamlet', 'sn', 'Full')