Repository: zulip/zulip

Issue:
500 server error on LDAP login
I'm not sure if this is related to server configuration, as I've not successfully gotten LDAP login to work yet. But `manage.py query_ldap` is fine, so I thought that should mean my Zulip configuration is correct. 

When I login using the user's email and pw, Zulip contacts the directory server but then I get an error page. If I provide an incorrect pw I get the expected message, so it appears to be authenticating. 

The server is Open Directory on OS X Server 10.11.4. It's not currently configured for SSL (cert issues.)

```
Jun 15 19:44:43 myserver kdc[5536]: AS-REQ diradmin@myserver.com from 127.0.0.1:57971 for krbtgt/myserver.com@myserver.com
Jun 15 19:44:43 myserver sandboxd[150] ([5536]): kdc(5536) deny file-read-data /private/etc/krb5.conf
Jun 15 19:44:43 myserver kdc[5536]: AS-REQ diradmin@myserver.com from 127.0.0.1:57971 for krbtgt/myserver.com@myserver.com
Jun 15 19:44:43 myserver kdc[5536]: Client sent patypes: REQ-ENC-PA-REP
Jun 15 19:44:43 myserver kdc[5536]: Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ
Jun 15 19:44:43 myserver kdc[5536]: AS-REQ diradmin@myserver.com from 127.0.0.1:62639 for krbtgt/myserver.com@myserver.com
Jun 15 19:44:43 --- last message repeated 1 time ---
Jun 15 19:44:43 myserver kdc[5536]: Client sent patypes: ENC-TS, REQ-ENC-PA-REP
Jun 15 19:44:43 myserver kdc[5536]: ENC-TS pre-authentication succeeded -- diradmin@myserver.com
Jun 15 19:44:43 myserver kdc[5536]: DSUpdateLoginStatus: Unable to synchronize login time for diradmin: 77009 
Jun 15 19:44:43 myserver kdc[5536]: Client supported enctypes: aes256-cts-hmac-sha1-96, aes128-cts-hmac-sha1-96, des3-cbc-sha1, arcfour-hmac-md5, using aes256-cts-hmac-sha1-96/aes256-cts-hmac-sha1-96
Jun 15 19:44:43 myserver kdc[5536]: Requested flags: forwardable
Jun 15 19:44:43 myserver kdc[5536]: AS-REQ zulipuser@myserver.com from 127.0.0.1:57431 for krbtgt/myserver.com@myserver.com
Jun 15 19:44:43 --- last message repeated 1 time ---
Jun 15 19:44:43 myserver kdc[5536]: Client sent patypes: REQ-ENC-PA-REP
Jun 15 19:44:43 myserver kdc[5536]: Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ
Jun 15 19:44:43 myserver kdc[5536]: AS-REQ zulipuser@myserver.com from 127.0.0.1:58873 for krbtgt/myserver.com@myserver.com
Jun 15 19:44:43 --- last message repeated 1 time ---
Jun 15 19:44:43 myserver kdc[5536]: Client sent patypes: ENC-TS, REQ-ENC-PA-REP
Jun 15 19:44:43 myserver kdc[5536]: ENC-TS pre-authentication succeeded -- zulipuser@myserver.com
Jun 15 19:44:44 myserver kdc[5536]: DSUpdateLoginStatus: Unable to synchronize login time for zulipuser: 77009 
Jun 15 19:44:44 myserver kdc[5536]: Client supported enctypes: aes256-cts-hmac-sha1-96, aes128-cts-hmac-sha1-96, des3-cbc-sha1, arcfour-hmac-md5, using aes256-cts-hmac-sha1-96/aes256-cts-hmac-sha1-96
Jun 15 19:44:44 myserver kdc[5536]: Requested flags: forwardable
```
```
zulip@minmi:~/deployments/current$ ./manage.py query_ldap zulipuser@mydomain.com
full_name: [u'zulipuser']
```

```
# URI of your LDAP server. If set, LDAP is used to prepopulate a user's name in                     
# Zulip. Example: "ldaps://ldap.example.com"                                                        
AUTH_LDAP_SERVER_URI = "ldap://mydomain.com"

# This DN will be used to bind to your server. If unset, anonymous                                  
# binds are performed.  If set, you need to specify the password as                                 
# 'auth_ldap_bind_password' in zulip-secrets.conf.                                                  
AUTH_LDAP_BIND_DN = "uid=diradmin,cn=users,dc=mydomain,dc=com"

# Specify the search base and the property to filter on that corresponds to the                     
# username.                                                                                         
AUTH_LDAP_USER_SEARCH = LDAPSearch("cn=users,dc=mydomain,dc=com",
                                   ldap.SCOPE_SUBTREE, "(uid=%(user)s)")

# If the value of a user's "uid" (or similar) property is not their email                           
# address, specify the domain to append here.                                                       
LDAP_APPEND_DOMAIN = "mydomain.com"
```
```
2017-06-16 02:56:29,703 ERROR    Internal Server Error: /accounts/login/
Traceback (most recent call last):
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/core/handlers/exception.py", line 42, in inner
    response = get_response(request)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
    response = self._get_response(request)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "./zerver/views/auth.py", line 445, in login_page
    extra_context=extra_context, **kwargs)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/contrib/auth/views.py", line 47, in inner
    return func(*args, **kwargs)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/views/decorators/debug.py", line 76, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/views/decorators/cache.py", line 57, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/contrib/auth/views.py", line 81, in login
    if form.is_valid():
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/forms/forms.py", line 169, in is_valid
    return self.is_bound and not self.errors
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/forms/forms.py", line 161, in errors
    self.full_clean()
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/forms/forms.py", line 371, in full_clean
    self._clean_form()
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/forms/forms.py", line 398, in _clean_form
    cleaned_data = self.clean()
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/contrib/auth/forms.py", line 191, in clean
    self.user_cache = authenticate(username=username, password=password)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django/contrib/auth/__init__.py", line 74, in authenticate
    user = backend.authenticate(**credentials)
  File "./zproject/backends.py", line 422, in authenticate
    user_profile = ZulipLDAPAuthBackendBase.authenticate(self, username, password)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 169, in authenticate
    user = ldap_user.authenticate(password)
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 344, in authenticate
    self._get_or_create_user()
  File "/home/zulip/deployments/2017-06-05-14-27-45/zulip-venv/lib/python2.7/site-packages/django_auth_ldap/backend.py", line 553, in _get_or_create_user
    self._user, created = self.backend.get_or_create_user(username, self)
  File "./zproject/backends.py", line 444, in get_or_create_user
    if self._realm.deactivated:
AttributeError: 'NoneType' object has no attribute 'deactivated'
```
```
Request info:
- path: /accounts/login/
- POST: {u'username': [u'********************'], u'csrfmiddlewaretoken': [u'**********'], u'password': [u'**********'], u'button': [u'********************']}
- REMOTE_ADDR: "[u'1.2.3.4']"
- QUERY_STRING: "[u'next=']"
- SERVER_NAME: "[u'']"
```


Discussion:
I haven't been able to get my OD server to use a real ssl cert, so I'm still configured with ldap://. I don't know if that is related to this exception.

When I debug it, it's like my realm doesn't exist. If I comment out the offending lines in get_or_create_user, it just fails further on trying to get some other attribute of realm. 

This install was initially set up with email auth, then I configured LDAP. The login page has the correct realm name, and I can still login with the existing email auth user (if I leave email auth enabled.) Should I be able to add LDAP later?
@umairwaheed FYI; I feel like even if this is configured wrong, we shouldn't be 500ing.
Hello @zulip/server-authentication members, this issue was labeled with the **area: authentication** label, so you may want to check it out!

Resolving patch:
diff --git a/zproject/backends.py b/zproject/backends.py
--- a/zproject/backends.py
+++ b/zproject/backends.py
@@ -440,6 +440,8 @@ def get_or_create_user(self, username, ldap_user):
                 raise ZulipLDAPException("LDAP Authentication is not enabled")
             return user_profile, False
         except UserProfile.DoesNotExist:
+            if self._realm is None:
+                raise ZulipLDAPException("Realm is None")
             # No need to check for an inactive user since they don't exist yet
             if self._realm.deactivated:
                 raise ZulipLDAPException("Realm has been deactivated")


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
@@ -1776,8 +1776,8 @@ def test_login_failure_due_to_wrong_password(self):
                 LDAP_APPEND_DOMAIN='zulip.com',
                 AUTH_LDAP_BIND_PASSWORD='',
                 AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
-                user = self.backend.authenticate(self.example_email("hamlet"), 'wrong')
-                self.assertIs(user, None)
+            user = self.backend.authenticate(self.example_email("hamlet"), 'wrong')
+            self.assertIs(user, None)
 
     @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
     def test_login_failure_due_to_nonexistent_user(self):
@@ -1791,8 +1791,8 @@ def test_login_failure_due_to_nonexistent_user(self):
                 LDAP_APPEND_DOMAIN='zulip.com',
                 AUTH_LDAP_BIND_PASSWORD='',
                 AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
-                user = self.backend.authenticate('nonexistent@zulip.com', 'testing')
-                self.assertIs(user, None)
+            user = self.backend.authenticate('nonexistent@zulip.com', 'testing')
+            self.assertIs(user, None)
 
     @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
     def test_ldap_permissions(self):
@@ -1876,6 +1876,21 @@ class _LDAPUser(object):
             with self.assertRaisesRegex(Exception, 'Realm has been deactivated'):
                 backend.get_or_create_user(email, _LDAPUser())
 
+    @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
+    def test_get_or_create_user_when_realm_is_none(self):
+        # type: () -> None
+        class _LDAPUser(object):
+            attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
+
+        ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
+
+        with self.settings(AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map):
+            backend = self.backend
+            email = 'nonexisting@zulip.com'
+            backend._realm = None
+            with self.assertRaisesRegex(Exception, 'Realm is None'):
+                backend.get_or_create_user(email, _LDAPUser())
+
     @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
     def test_django_to_ldap_username_when_domain_does_not_match(self):
         # type: () -> None