#pragma section-numbers on '''!HowTo: an Active Directory authentication example - by NicoZanferrari, February 2010''' <> = 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! = 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 [[http://www.windowsnetworking.com/articles_tutorials/Resultant-Set-Policy-Queries-GPRESULT.html|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. = 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: {{attachment: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 [[http://technet.microsoft.com/it-it/sysinternals/bb963907(en-us).aspx|Active Directory Explorer]] and [[http://directory.apache.org/studio/|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. = 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: {{{#!python from MoinMoin.auth.ldap_login import LDAPAuth ldap_authenticator1 = LDAPAuth( # the values shown below are the DEFAULT values (you may remove them if you are happy with them), # the examples shown in the comments are typical for Active Directory (AD) or OpenLDAP. server_uri='ldap://10.2.3.4', # ldap / active directory server URI - you can use the server name # use ldaps://server:636 url for ldaps, # use ldap://server for ldap without tls (and set start_tls to 0), # use ldap://server for ldap with tls (and set start_tls to 1 or 2). # we will use the username and password we got from the user: #bind_dn = '%(username)s@example.org' # DN we use for first bind (AD) #bind_pw = '%(password)s' # password we use for first bind # or we can bind anonymously (if that is supported by your directory). # In any case, bind_dn and bind_pw must be defined. bind_dn = '%(username)s@production.com', # DN we use for first bind (AD) bind_pw = '%(password)s', # password we use for first bind base_dn='CN=Users,DC=production,DC=com', # base DN we use for searching #base_dn = 'ou=SOMEUNIT,dc=example,dc=org' scope=2, # scope of the search we do (2 == ldap.SCOPE_SUBTREE) referrals=0, # LDAP REFERRALS (0 needed for AD) search_filter='(sAMAccountName=%(username)s)', # ldap filter used for searching: #search_filter = '(sAMAccountName=%(username)s)' # (AD) #search_filter = '(uid=%(username)s)' # (OpenLDAP) # you can also do more complex filtering like: # "(&(cn=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))" # some attribute names we use to extract information from LDAP (if not None, # if None, the attribute won't be extracted from LDAP): givenname_attribute='givenName', # often 'givenName' - ldap attribute we get the first name from surname_attribute='sn', # often 'sn' - ldap attribute we get the family name from aliasname_attribute='displayName', # often 'displayName' - ldap attribute we get the aliasname from email_attribute='mail', # often 'mail' - ldap attribute we get the email address from email_callback=None, # callback function called to make up email address coding='utf-8', # coding used for ldap queries and result values timeout=10, # how long we wait for the ldap server [s] start_tls=0, # usage of Transport Layer Security 0 = No, 1 = Try, 2 = Required tls_require_cert=0, # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs) bind_once=True, # set to True to only do one bind - useful if configured to bind as the user on the first attempt autocreate=True, # set to True to automatically create/update user profiles ) auth = [ldap_authenticator1, ] # this is a list, you may have multiple ldap authenticators # as well as other authenticators use_email = True cookie_lifetime = (0, 4) # no anon user sessions, 4h session lifetime for logged-in users # customize user preferences (optional, see MoinMoin/config/multiconfig for internal defaults) # you maybe want to use user_checkbox_remove, user_checkbox_defaults, user_form_defaults, # user_form_disable, user_form_remove. }}} {{{#!wiki note '''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). = 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. == ... 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. {{{#!highlight python def get_username(ldap_dict): username = ldap_dict.get('username') if username: username.capitalize() return username class Config(FarmConfig): # [...] ldap_authenticator1 = LDAPAuth( # [...] name_callback=get_username ) }}} (only tested with OpenLDAP) MichaelGutmann Note by -- AlexanderAgibalov <> - 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) }}} = 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 http'''s''' 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.