Hey folks,
Posting back with a report on efforts to integrate LDAP with PANDA. This a long one, so here's the TL;DR:
LDAP works seamlessly with PANDA and is relatively easy to install if you're experienced with LDAP in general.
LONG VERSION
I'm happy to report that integrating LDAP with PANDA worked like a charm, simply by leveraging Django's native authentication functionality. This is a testament to Chris and Brian's code design -- I was shocked at how little we had to modify to get this working.
So, how did we do it?
At a very high-level, you need to use or create an alternative
Django Authentication Backend and hook it into your settings file. If you're lucky enough to have OpenLDAP, then much of the hard work has been done for you by
django-auth-ldap. This module wraps the lower-level
python-ldap library and makes LDAP integration pretty much entirely a configuration issue.
Unfortunately, I was unable to use django-auth-ldap because it doesn't seem to play nice with ActiveDirectory (my corporate AD setup requires a username to have a
REALM prefix (e.g. APFOO\stumgoren). I couldn't find a way to support this requirement in django-auth-ldap without seriously hacking up the source, so instead I wrote a custom auth backend that does precisely what we need. This may sound a lot more scary than it was. The Django docs, django-auth-ldap source code, and below Snippets provide solid guidance and sample code on how to wire up a custom auth backend:
I won't go into all the details, but generally speaking, you need to:
- Configure and initialize an ldap connection
- Perform an LDAP bind operation with the authenticating user's credentials
- Perform an LDAP search with the bound connection, fetching basic user info such as first and last name, email, etc.
- Use the search results, if any, to create/update an instance of panda.models.UserProxy
Once you've written a backend that performs the above steps, just activate it in your settings:
### settings.py or local_settings.py ####
# Possible other LDAP configs, especially if using django-auth-ldap
AUTHENTICATION_BACKENDS = (
'panda.auth.ActiveDirectoryBackend',# or, perhaps, 'django_auth_ldap.backend.LDAPBackend',
# Enable Model login as well, so that we can log in using default panda superuser after bootstrap
'django.contrib.auth.backends.ModelBackend',
)
Note above that we included the default ModelBackend auth, which allows us to login using the default panda superuser (this requires installing the test_users.json fixture). Django will try auth backends in the order they're listed in this setting, so if LDAP fails, it falls back to ModelBackend. You will
definitely *not* want to include this backend if you want to restrict auth to LDAP only.
We only included it in order to log in after bootstrapping and assign superuser/staff perms in the Django admin to ourselves. Security-minded folks will chide us for being lazy in the bad way (we are), and point out that you could just as easily do this in the django shell/dbshell. Anyhow, we're planning to de-activate this auth backend after soft launch, but it's a feature to be aware of if you need support for multiple authentication backends.
Other than adding a custom auth backend, we only lightly modified a few PANDA templates to avoid confusing end users. For example, we removed the "forgot password" feature on the login page and the password reset feature on the User profile page.
Overall, this process was pretty painless and the actual dev work fairly limited. The biggest hurdle for me was learning about LDAP and some of its idiosyncrasies, especially with respect to ActiveDirectory and our in-house configurations. A few gotchas I worked through are listed below, so hopefully they save you some time and pain. Also, I'd strongly recommend chatting with your IT folks up front to find out how to configure LDAP, rather than fumbling your way through it as I did using a tool called
ldapsearch. A few key questions to ask:
- Are we using OpenLDAP or ActiveDirectory?
- Is anonymous bind supported?
- Do we use StartTLS or SSL for security?
Below are a few additional notes and gotchas:
- Email notifications - PANDA's email notifications "just worked" as expected after we wired up our custom Auth Backend. Specifically, user activation emails were *not* sent after initial login, whereas data upload emails *were* sent. I suspect the activation email was not triggered because our auth backend set a default "dummy" password on UserProxy at creation time, but I can't recall off-hand without going back to the code (Chris, feel free to set me straight there and let me know if there potential land mines).
- Referral chasing - This is a topic related to LDAP's inner workings, but suffice it to say that it will be a big source of headaches if your Python code does not handle LDAP referrals correctly. The general advice seems to be to disable referal chasing, which in our case resolved issues we were having with ldap search requests indefinitely hanging. Of course, I'd consult with your IT dept before making any decisions about this. If you do want to disable referral chasing, you have to configure the ldap connection *before* you bind:
l = ldap.initialize(settings.AUTH_LDAP_URL)
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
l.set_option(ldap.OPT_REFERRALS, 0) # DISABLE REFERRALS
try:
l.simple_bind_s(bind_dn, password)
# etc.
- libsasl development headers - If you're working on Ubuntu 12.04 and serving the app from a virtualenv, you should install libsasl development headers in order to successfully build python-ldap: sudo apt-get install libsasl2-dev.
Those are the major notes/gotchas from our experience. Hopefully it helps others who want to integrate LDAP. Of course, don't be shy with questions or pointing out something that may be a hidden land mine in our setup. Meantime, thanks again to Chris and Brian for such elegant design.
Regards,
Serdar