It took me a long time to figure this out, I didn’t want it to end up locked in my head, so here’s a rough guide on how I managed to get a pylons 0.9.7 project to do LDAP authentication. All of the below is done in config/middleware.py.
The key points are:
- “import ldap” (for the obvious reason) and “from paste.auth.basic import AuthBasicHandler”
- Wrap the stacked WSGI ‘app’ object in the AuthBasicHandler you just imported:
app = AuthBasicHandler(app, 'The value you want to appear on the browser dialog box', yourAuthFunctionHere)
- Define an auth function “yourAuthFunctionHere(requestEnvironment, username, password)”. Some tricky bits:
- If this function returns “True”, the request will proceed and there will be a new key in the Request object named ‘REMOTE_USER’ that contains the passed username.
- If this function returns “False”, the user will be prompted again until it succeeds. (I should probably find a way to limit these so people cannot just keep guessing.)
- Python’s LDAP has some strange behaviours:
- Do ldap.initialize outside the auth function or you will swamp the LDAP server with bind requests
- Use synchronous bind (‘bind_s’ as opposed to ‘bind’) – asynchronous bind in the WSGI middleware layer caused some really bizarre behaviour here, including segfaults
- Trap ldap.INVALID_CREDENTIALS and return False when it is raised, or watch all invalid logins crash the server process
- bind (and bind_s) return a tuple, if the first item in the tuple is int(97), the bind worked – otherwise, they might have bound but as an anonymous user, and we need to fail
- the second item in that tuple is a list of messages from the server – I’m not handling those at all today, becuase in the sole case we’re interested in (97, or “auth”), the messages appear to be blank in our environment – there’s no reason to believe this is consistent, and I should probably research this further at some point to provide meaningful feedback to the user on failed auth.
You can’t touch the session from within the WSGI middleware layer
All of this was done because a new server in our datacenter doesn’t have packages for the old build of Apache that we used to configure our LDAP auth back in the day. I’m actually quite pleased at how the new system works, and am glad to be rid of that Apache+PHP millstone that’s been lurking as a dependancy in all of our Pylons projects simply for the LDAP authentication solution.