HowTo: an Active Directory authentication example - by NicoZanferrari, February 2010

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:

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!

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:

lat.png

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 ;)

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.

   1 def get_username(ldap_dict):
   2     username = ldap_dict.get('username')
   3     if username: username.capitalize()
   4     return username
   5 
   6 class Config(FarmConfig):
   7 
   8       # [...]
   9 
  10       ldap_authenticator1 = LDAPAuth(
  11 
  12         # [...]
  13         name_callback=get_username
  14       )

(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

MoinMoin: HowTo/UbuntuAD (last edited 2015-07-24 11:02:11 by AlexanderAgibalov)