Authentication using LDAP
Please note that LDAP authentication is implemented in MoinMoin since version 1.6. Instructions are located at the HelpOnAuthentication page |
I (NickPhillips) really need Moin to authenticate users using LDAP.
The "pluggable" authentication in Moin 1.5 makes this reasonably easy, except for one minor point.
The way our LDAP auth needs to work is as follows:
- Connect to LDAP server
- Bind anonymously
- Search for supplied username, make sure there's only one match
- Re-bind using supplied username and password
- If bind using supplied username and password is successful, user is authenticated.
Note: I removed some outdated code, please see the hg repo (or latest moin release) for the latest code. -- ThomasWaldmann 2006-12-23 02:51:11
Improvements
First, I'd like to be caching the LDAP connection between requests. This could be especially useful when using mod_python. I'm new to Python and mod_python, but if they work similarly to mod_perl (which I am familiar with), then this shouldn't be too hard. The question is just "where do I want to store this damn LDAP object?"
Second, I really don't want users to end up having LDAP usernames as WikiNames. I need to come up with a way of translating one to the other, so that the LDAP username is never displayed on the wiki except in the login box. I'm not sure how the UserPreferences form should handle this.
Why you want 2 different names for the wiki user name and login name? note that the wiki name can be any name, there is not special reason to use WikiName, and of course it does not work for many languages which does not have capitals.
Because the login names we have are ugly and make it hard to see who is who. They're intended purely as a unique id for authentication, and I'd like to use a name that's intended as a human-readable name for places that it's intended it should be read by humans I don't mean that I want to force the use of caps, just that I want to be able to map from a name that's not intended for humans to one that is. And vice-versa. The mapping could be a simple case of applying some algorithm to the login to get the WikiName, or a further LDAP lookup, or a lookup in a flat file, or some arbitrary database...
It seems to me like this could easily be done "pluggably", by plugging in a module that's required to provide two functions -- one to translate a WikiName to a login name, and one the other way. In the default case, each would just return its argument.
As a short term solution you may want to put the loginname into name field and the cleartext name into aliasname field. alias is only there for cosmetical purpose and gets shown in a mouseover bubble on RecentChanges e.g.
That's what I'm doing. Except that I don't yet have a way of automatically setting the aliasname.
Please take a look in the Code of ldap-feature in the bugzilla software. There you can define which ldap field is used as bugzilla username and mail. It is also possible to make an authenticated ldap search. That is important for me and all other how want to bind moinmoin with Microsoft ActiveDirectory ldap, because MS ADS don't allow unauthenticated ldap searchs!
It wouldn't be too hard to allow an authenticated bind for the search, but I think you may confusing things when you talk about mail. This module is for authentication, and any other information lookups should be performed in a separate module -- what if I want to use LDAP for auth but MySQL for getting email address, aliasname etc.?
News 2006-12-22
I updated the ldap auth code in the 1.5 branch (see CHANGES for details). If you run a ldap setup, please help testing this and report about success/failure below.
- I've installed 1.5.7 with ldap authentication. I've used the ldap configuration example and I've adapted it to my site. Some comments I have:
If ldap_start_tls is false, other configuration parameters (ldap_tls_cacertdir, ldap_tls_cacertfile ...) shouldn't be required and the code must be changed maybe this way: in line 365 instead of if ldap.TLS_AVAIL: it should be if starttls and ldap.TLS_AVAIL:
- No. There are two methods requiring the certs: TLS and SSL. SSL needs them even if TLS is not being used.
- Even if I set ldap_start_tls = 0, I get the following error: {{{[Mon Jun 11 16:40:04 2007] got name=ray5147 login=True logout=False
- No. There are two methods requiring the certs: TLS and SSL. SSL needs them even if TLS is not being used.
[Mon Jun 11 16:40:04 2007] LDAP: Setting misc. options... [Mon Jun 11 16:40:04 2007] LDAP: caught an exception, traceback follows... [Mon Jun 11 16:40:04 2007] Traceback (most recent call last):
- File "/usr/lib/python2.3/site-packages/MoinMoin/auth.py", line 365, in ldap_login
- if ldap.TLS_AVAIL:
AttributeError: 'module' object has no attribute 'TLS_AVAIL'}}}
- I believe there should be an additional check done in the code.
Just use import ldap ; ldap.TLS_AVAIL = 0
- If so, ¿does this mean we need to define ldap_tls_cacertdir, ldap_tls_cacertfile ..., even I we just need SSL access, and we use selfsigned certificates? Please, let me know which are the minimum options needed. ¿Could we accept any certificate even if we haven't got the corresponding CA certificate?
- Maybe it is due to I have defined ldap_tls_cacertdir, ldap_tls_cacertfile ... as a void string, and I use the SSL method, but the first time I try to login everything goes well, but the second try generate a "segment violation" error. This is my log file
[Tue Feb 20 13:01:54 2007] got name=None login=False logout=False [Tue Feb 20 13:02:00 2007] got name=miusuario login=True logout=False [Tue Feb 20 13:02:00 2007] LDAP: Setting misc. options... [Tue Feb 20 13:02:00 2007] LDAP: Trying to initialize ldaps://mihost.midominio.pri. [Tue Feb 20 13:02:00 2007] LDAP: Connected to LDAP server ldaps://mihost.midominio.pri. [Tue Feb 20 13:02:00 2007] LDAP: Bound with binddn [Tue Feb 20 13:02:00 2007] LDAP: Searching (uid=miusuario) [Tue Feb 20 13:02:00 2007] LDAP: dn:uid=miusuario,ou=people,dc=midominio,dc=pri [Tue Feb 20 13:02:00 2007] mail: ['miusuario@midominio.pri'] [Tue Feb 20 13:02:00 2007] givenName: ['Mi Usuario'] [Tue Feb 20 13:02:00 2007] displayName: ['Mi Usuario de trabajo'] [Tue Feb 20 13:02:00 2007] sn: ['de trabajo'] [Tue Feb 20 13:02:00 2007] LDAP: DN found is uid=miusuario,ou=people,dc=midominio,dc=pri, trying to bind with pw [Tue Feb 20 13:02:00 2007] LDAP: Bound with dn uid=miusuario,ou=people,dc=midominio,dc=pri (username: miusuario) [Tue Feb 20 13:02:00 2007] LDAP: creating userprefs with name misuario, email miusuario@midominio.pri alias Mi Usuario de trabajo [Tue Feb 20 13:02:02 2007] got name=None login=False logout=True [Tue Feb 20 13:02:03 2007] got name=None login=False logout=False [Tue Feb 20 13:02:13 2007] got name=miusuario login=True logout=False [Tue Feb 20 13:02:13 2007] LDAP: Setting misc. options... [Tue Feb 20 13:02:13 2007] LDAP: Trying to initialize ldaps://mihost.midominio.pri. [Tue Feb 20 13:02:13 2007] LDAP: Connected to LDAP server ldaps://mihost.midominio.pri.
Then I get this error message on the console/etc/init.d/moin: line 1: 10985 Violación de segmento /var/lib/moin/bin/moin.fcg >>/var/log/moin.log 2>&1
And the moin.fcg has died. I'm using apache2 with fastcgi and moin.fcg is run as an external application. I've tried it several times with the same results. Without SSL (ldap://mihost.midominio.pri) everything goes well. - Python code doesn't do segmentation violations, this must be some C code (maybe some library problem?).
- The SSL/TLS stuff isn't really tested. I didn't have segfaults, but all sorts of other weird error msgs and I couldn't tell if it was due to misconfigured server or misbehaved client. Someone with some "proven to work" ldaps or ldap/tls server needs to test it.
You only need to configure the tls stuff needed, the rest may stay at 0 or ''. The python-ldap stuff should be able to use server certs as well as client certs and I just added options for all.
The example should say that ldap_binddn and ldap_bindpw must be defined, and if you want anonymous bind, they must be a void string: ldap_binddn='' and ldap_bindpw=''
- I added a sample for anon bind and a hint that they must be defined.
It seems that ldap authentication doesn't fallback to other authentication mechanism, at least, Moin can't recognize my local created users (they were created when ldap authentication was not activated). Could it be possible to control this behaviour with a cofigurable option? For example ldap_fallback_authentication = True.
- The concrete use case for which ldap auth was implemented didn't want that fallback. But adding an option is a good idea for those who want it.
Test script
This can be used to debug ldap auth problems, especially if your LDAP/AD setup is rather complex:
it seems that this script isn't functional (Debian, Python 2.4). There is at least a problem with users_passwords definition (lines 26-30) and its use.
I have changed this:
users_passwords = [ ('username':'user1', 'password':'correctpass1'), ('username':'user1', 'password':''), # check whoami output for this! ('username':'user1', 'password':'wrongpass1'), ]
and (line 34)
for user in users_passwords:
(line 49)
l.simple_bind_s(first_dn, user['password'])
But they are still problems i haven't solved.
1 #!/opt/python/bin/python
2 # -*- coding: iso-8859-1 -*-
3 """
4 This script can be used for some basic LDAP / Active Directory Testing.
5
6 You need to configure the variables below this comment to match your setup.
7
8 If it does not authenticate your users correctly, you maybe also want to look
9 at the code below and find out why it does not work.
10
11 Please contribute changes back to us.
12
13 MoinMoin development at http://moinmoin.wikiwikiweb.de/
14 """
15
16 server_uri = 'ldap://ldap.example.org/'
17 #server_uri = 'ldaps://ldap.example.org/'
18
19 # if bind_user and bind_pw is both '' it does an anonymous bind
20 bind_user = ''
21 bind_pw = ''
22
23 base_dn = 'dc=example,dc=org'
24 filter_str = '(uid=%s)' # check if this is correct for you!
25
26 users_passwords = [
27 ('user1', 'correctpass1'),
28 ('user1', ''), # check whoami output for this!
29 ('user1', 'wrongpass1'),
30 ]
31
32 import ldap
33
34 for user, password in users_passwords:
35 # This is only required if you are using a self signed cert.
36 # Probably turn it off for production code.
37 if server_uri.startswith('ldaps:'):
38 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
39
40 # ActiveDirectory? Do this, otherwise, leave it out.
41 ldap.set_option(ldap.OPT_REFERRALS, 0)
42
43 print "Initializing connection to %s ..." % server_uri
44 l = ldap.initialize(server_uri)
45 print "LDAP protocol version %d" % l.protocol_version
46 #l.protocol_version = ldap.VERSION3
47
48 print "Binding to directory using bind user %r (and configured password) ..." % bind_user
49 l.bind_s(bind_user, bind_pw)
50
51 search_filter = filter_str % user
52 print "Searching under base dn %s for %s ..." % (base_dn, search_filter)
53 lusers = l.search_s(base_dn, ldap.SCOPE_SUBTREE, search_filter)
54 results = len(lusers)
55 print "Results: %d" % results
56 if results:
57 for dn, ldap_dict in lusers:
58 print " %s" % dn
59 first_dn = lusers[0][0]
60 print "Trying to authenticate with first found dn %s (and configured password) ..." % first_dn
61 try:
62 l.bind_s(first_dn, password)
63 print "Succcessfully bound - whoami says: %s" % l.whoami_s()
64 except ldap.INVALID_CREDENTIALS, err:
65 print "LDAP Error: %s" % err
66 print "Unbinding from directory ..."
67 l.unbind()
68 print "-"*70