HowTo: an Active Directory authentication example - by NicoZanferrari, February 2010
Contents
1. Preface
This tutorial will show you how configure MoinMoin in order to authenticate users against an existing Active Directory. Active Directory (AD for short) is a widely used technology created by Microsoft that provides a variety of network services, including LDAP-like directory services and Kerberos-based authentication.
We'll use Ubuntu 9.10 Karmic Koala and MoinMoin version 1.9.1. AD level will be 2003/2008, but I think that there should be no problem if you still have a Windows 2000 domain. You can find more generic documentation on HelpOnAuthentication and inside the local snipped example /usr/local/share/wiki/config/more_samples/ldap_wikiconfig_snippet .
Suggestions and corrections are always welcome!
2. Get the informations
Before starting, you'll need the following informations:
- the name or IP of a Domain server (we'll use 10.2.3.4 in this example)
- the name of the AD container where to look for users (CN=Users,DC=production,DC=com in this example). This is also called BASE DN.
a username and its password in the AD (LdapReader and LdapPassword in this example). It's just for the initial testing, and we don't need any particular right. You can use your own username and password in the Window domain.
You've better ask these informations to your System Administrators, in order to be sure. But if they are too much busy (or maybe even unreachable), it's possible to obtain them by yourself!
Login in a Windows workstation that is part of the Domain, and open a Command Prompt. Issue a "ECHO %LOGONSERVER%" command and you'll easily get a Domain Controller name. Ping it in order to have its IP.
Issue a "GPRESULT" command and look at the result (an example can be found here). The name of the AD container is right under the "USER SETTINGS" section. In this example, the LdapReader user is identified as CN=LdapReader,CN=Users,DC=production,DC=com and hence the AD container of this user is CN=Users,DC=production,DC=com.
By default, in AD the users are under the CN=Users - but there are some problems with this configuration and many System Administrators (including me ) have changed this behavior.
3. Test the informations
In order to test the information obtained, we need some LDAP tools. Install them with a:
sudo apt-get install ldap-utils
Then we can begin the test with a command like:
ldapsearch -h 10.2.3.4 -b "cn=Users,dc=production,dc=com" -W "cn=LdapReader" mail telephonenumber -D "cn=LdapReader,cn=Users,dc=production,dc=com"
The system will ask you for the LdapReader password, and it should return the e-mail and telephone number of the LdapReader user. Test it with other real users.
If you prefer a graphical interface, there is a Linux tool called LAT (LDAP Administration Tool). Install it with the usual:
sudo apt-get install lat
and fill in the requested information like this:
Unfortunately, I've found that this tool is quite unstable on Karmic. If you prefer, you can also test the informations from Windows - I suggest the free Active Directory Explorer and Apache Directory Studio.
If everything is right, you'll be able to browse the AD structure. You have to be sure that all your users' data are below that Base DN. Otherwise, playing with the Wiki configuration will just be a waste of time.
4. Wiki configuration
Once you've tested and confirmed the needed informations, you can proceed on the wiki configuration. But first you have to install the required Python stuff with a:
sudo apt-get install python-ldap
Then edit the configuration file(s) and add something like this at the end:
1 from MoinMoin.auth.ldap_login import LDAPAuth
2 ldap_authenticator1 = LDAPAuth(
3 # the values shown below are the DEFAULT values (you may remove them if you are happy with them),
4 # the examples shown in the comments are typical for Active Directory (AD) or OpenLDAP.
5 server_uri='ldap://10.2.3.4', # ldap / active directory server URI - you can use the server name
6 # use ldaps://server:636 url for ldaps,
7 # use ldap://server for ldap without tls (and set start_tls to 0),
8 # use ldap://server for ldap with tls (and set start_tls to 1 or 2).
9 # we will use the username and password we got from the user:
10 #bind_dn = '%(username)s@example.org' # DN we use for first bind (AD)
11 #bind_pw = '%(password)s' # password we use for first bind
12 # or we can bind anonymously (if that is supported by your directory).
13 # In any case, bind_dn and bind_pw must be defined.
14 bind_dn = '%(username)s@production.com', # DN we use for first bind (AD)
15 bind_pw = '%(password)s', # password we use for first bind
16 base_dn='CN=Users,DC=production,DC=com', # base DN we use for searching
17 #base_dn = 'ou=SOMEUNIT,dc=example,dc=org'
18 scope=2, # scope of the search we do (2 == ldap.SCOPE_SUBTREE)
19 referrals=0, # LDAP REFERRALS (0 needed for AD)
20 search_filter='(sAMAccountName=%(username)s)', # ldap filter used for searching:
21 #search_filter = '(sAMAccountName=%(username)s)' # (AD)
22 #search_filter = '(uid=%(username)s)' # (OpenLDAP)
23 # you can also do more complex filtering like:
24 # "(&(cn=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))"
25 # some attribute names we use to extract information from LDAP (if not None,
26 # if None, the attribute won't be extracted from LDAP):
27 givenname_attribute='givenName', # often 'givenName' - ldap attribute we get the first name from
28 surname_attribute='sn', # often 'sn' - ldap attribute we get the family name from
29 aliasname_attribute='displayName', # often 'displayName' - ldap attribute we get the aliasname from
30 email_attribute='mail', # often 'mail' - ldap attribute we get the email address from
31 email_callback=None, # callback function called to make up email address
32 coding='utf-8', # coding used for ldap queries and result values
33 timeout=10, # how long we wait for the ldap server [s]
34 start_tls=0, # usage of Transport Layer Security 0 = No, 1 = Try, 2 = Required
35 tls_require_cert=0, # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
36 bind_once=True, # set to True to only do one bind - useful if configured to bind as the user on the first attempt
37 autocreate=True, # set to True to automatically create/update user profiles
38 )
39
40 auth = [ldap_authenticator1, ] # this is a list, you may have multiple ldap authenticators
41 # as well as other authenticators
42
43 use_email = True
44 cookie_lifetime = (0, 4) # no anon user sessions, 4h session lifetime for logged-in users
45 # customize user preferences (optional, see MoinMoin/config/multiconfig for internal defaults)
46 # you maybe want to use user_checkbox_remove, user_checkbox_defaults, user_form_defaults,
47 # user_form_disable, user_form_remove.
Note Be extremely careful with the indentation - this is a Python program!
This is the suggested example, with all the comments I've found useful. In order connect to AD, I've just used the username and password taken from the user input, but this is not mandatory (you can used the LdapReader you've tested before).
If you are confident with Active Directory object naming, be careful with the base_dn parameter in line 14: despite its syntax it's not a User Principal Name (UPN).
5. How to make usernames case-insensitive
In this example, I've used AD authentication as the only method to identify users. With the autocreate option enabled, this is an effective mode to establish single-sign-on with an existing Windows domain infrastracture.
If you are in this situation, you'll soon notice that there is a discrepancy - because user names are case insensitive in AD, but they are case sensitive in MoinMoin So, if your user John Doe login today as JohnDoe and tomorrow as Johndoe you'll create two different user profiles for the same user. If you wish to reduce this users' freedom and avoid this "proliferation problem", you can patch the file /usr/local/lib/python2.6/dist-packages/MoinMoin/auth/ldap_login.py adding line 119 like:
username = kw.get('username') if username: username=username.capitalize() # patch added for case-insensitive usernames password = kw.get('password')
In this way, usernames will be silently capitalized
Alternatively, try username.title() to convert to titlecase, just like the 'GivenAuth' option. Probably more useful e.g. if you're converting from mod-sspi+GivenAuth to ldap_login.
5.1. ... using name_callback
There is a way to solve this problem without patching ldap_login.py. You can define a callback function, that produces a username. The function gets the ldap data as a parameter and returns a string.
(only tested with OpenLDAP) MichaelGutmann
Note by -- AlexanderAgibalov 2015-07-24 11:02:11 - I'm working on the same problem now and tried your suggestion. Either there's a problem with Active Directory (because I use AD, not OpenLDAP) or with the MoinMoin version (I have 1.9.8), but the ldap_dict does not have the 'username' attribute. My initial idea was to get name from the e-mail instead (you can see this in previous revision of this page), but the problem of this method is that the user profile is read from disk by 'name'. And that 'name' equals to username as entered in the logon html form. So to make this callback work while still leaving it flexible for possible future use I had to modify both ldap_login.py and my wiki config as follows:
wiki config: def myName(ldap_dict, username): return username.lower() class Config(multiconfig.DefaultConfig): ... ldap_authenticator1 = LDAPAuth( ... name_callback=myName, ) ldap_login.py: if self.name_callback: username = self.name_callback(ldap_dict, username)
6. Tips & Suggestions
the same configuration can be used with MoinMoin 1.8.x. The only difference is with the cookie_lifetime variable, that has changed.
SECURITY: be careful that user passwords are not encrypted in the communication between the server and the client. This is not generally advisable, except for small intranet where you trust the network. In order to protect authentication data, the simpler solution is to use https for all the communication between the server and the clients.
LDAP and AD group membership checking is not yet implemented in MoinMoin, see http://moinmo.in/Groups2009.
- one can use the ldap search filter for simple cases to make moin only find some special kind of users
- authentication of users from different Windows domains in the same wiki instance is not possible.