If you benefit from web2py hope you feel encouraged to pay it forward by contributing back to society in whatever form you choose!

so this is my take on how to get ldap (well AD in my case) to work with group (CN) validation. I had to modify ldap_auth and the tools files inorder to get the out come that I needed. I'm sure there are other ways of doing this, but this way worked for and maybe someone else.

 

* disclaimer, this works for me and need some tweeking if your needs are not the same as mine

so in tools.py I needed to change the section for login() that does the login_methods if the user is not in the db

 

so here is the whole else statement (around line 1738)

                else:
                    # user not in db
                    if not self.settings.alternate_requires_registration:
                        # we're allowed to auto-register users from external systems
                        for login_method in self.settings.login_methods:
                            #check to see if the function is the ldap_auth and skip it so that the results
                            #we get back from the ldpa_auth funct can be parsed out
                            #otherwise do whatever is the default for the given auth method
                            if hasattr(login_method,'__call__'):
                                if hasattr(login_method,'__name__') and 'ldap_auth_aux' in login_method.__name__:
                                    results = login_method(request.vars[username], request.vars[passfield])
                                    if isinstance(results, bool):
                                        if not results:
                                            break
                                    else:
                                        if results.has_key('first_name'):
                                            form.vars.first_name = results['first_name']
                                        if results.has_key('last_name'):
                                            form.vars.last_name = results['last_name']
                                        if results.has_key('email'):
                                            form.vars.email = results['email']
                                    user = self.get_or_create_user(form.vars)
                                    break
                                else:
                                    if login_method != self and \
                                        login_method(request.vars[username],
                                                     request.vars[passfield]):
                                        if not self in self.settings.login_methods:
                                            # do not store password in db
                                            form.vars[passfield] = None
                                        user = self.get_or_create_user(form.vars)
                                        break
                                    

 

so what I did was:

*check to see if the login_method is the ldap_auth function

** if it is then send the function the username and password arguments and save response to variable

**if the response is a bool and not a dict then we know that authentication failed

**otherwise take the dict and save the k,v to form vars so that the get_or_create funct cna populate the db with: first and last name, username, email

*if the funct is not the lda_auth one then proceed to do the original logic

 

in the db.py file we need to add the file, I give credit to http://www.web2pyslices.com/slice/show/1468/how-to-set-up-web2py-ldap-with-windows-active-directory for the original guidence and settings

 

auth.define_tables(username=True)
auth.settings.create_user_groups=False

# all we need is login
auth.settings.actions_disabled=['register','change_password','request_reset_password','retrieve_username'] #,'profile']

# you don't have to remember me
auth.settings.remember_me_form = False

# ldap authentication and not save password on web2py
from gluon.contrib.login_methods.ldap_auth import ldap_auth
auth.settings.login_methods = [auth,ldap_auth(mode='ad',
   server=<IP of your AD server>,
   base_dn=<base dn to search>, group=<CN group to search>, ou=<OU to filter on>)]

 

 

I had to modify the ldap_auth.py file so that I can search/filter on group

 

I updated the ldap_auth() to include two additional args

 

def ldap_auth(server='ldap', port=None,
            base_dn='ou=users,dc=domain,dc=com',
            mode='uid', secure=False, cert_path=None, bind_dn=None, bind_pw=None, filterstr='objectClass=*',
              group='',ou=''):

 

 

then I updated the ldap_auth_aux funct to take the additional args as well and included a results dict

 

    results = {}
    def ldap_auth_aux(username,
                      password,
                      ldap_server=server,
                      ldap_port=port,
                      ldap_basedn=base_dn,
                      ldap_mode=mode,
                      ldap_binddn=bind_dn,
                      ldap_bindpw=bind_pw,
                      secure=secure,
                      cert_path=cert_path,
                      filterstr=filterstr,
                      group=group,
                      ou=ou):

 

 

I needed to comment out this section since it didn't out of the box

 

##                if not isinstance(result, dict): 
##                    # result should be a dict in the form {'sAMAccountName': [username_bare]} 
##                    return False

##                result = con.search_ext_s(
##                     ldap_basedn, ldap.SCOPE_SUBTREE,
##                     "(&(sAMAccountName=%s)(%s))" % (username_bare, filterstr), ["sAMAccountName"])[0][1]

##                if not isinstance(result, dict): 
##                     # result should be a dict in the form {'sAMAccountName': [username_bare]} 
##                     return False 

 

 

here is where I do the filter action if the username and password works binds successfully to AD

 

                # this will throw an index error if the account is not found
                # in the ldap_basedn
                result = con.search_ext_s(
                    ldap_basedn, ldap.SCOPE_SUBTREE,
                    #"(&(sAMAccountName=%s)(%s))" % (username_bare, filterstr), ["sAMAccountName"])[0][1]
                    "(&(sAMAccountName=%s)(%s))" % (username_bare, filterstr),["sAMAccountName","mail", "sn", "givenName", 'memberOf'])
                search_path = group+','+ou+','+ldap_basedn
                if search_path.upper() in (path.upper() for path in result[0][1]['memberOf']):
                    results['email'] = result[0][1]['mail'][0]
                    results['first_name'] = result[0][1]['givenName'][0]
                    results['last_name'] = result[0][1]['sn'][0]
                    results['username'] = result[0][1]['sAMAccountName'][0]
                else:
                    return False

 

then down at the bottom of the file I change the object that gets passed back if everything is successful

 

            con.unbind()
            #return True
            return results

Related slices

Comments (2)

  • Login to post



  • 0
    derek-wilson-10759 12 years ago

    I think it's great, and I'd gladly use it if it was easily integrated. I wish that the ldap could automatically read in the first and last names as well as any other information that is needed, since I'd rather present a list of first and last names to someone than their login ID (which could be just a bunch of numbers, or in my case, a jumble of letters and numbers). Nobody is going to remember that my Id  is "asdloquahdf12" (especially if there are a lot of others with similar starting and ending characters), but if they see Wilson, Derek - they'll know it's me. So, this is great, but it's changing a lot of core files, and not in a way that it would get accepted into the core product, since it changes the return type of the auth event.

    replies (1)
    • yozik 11 years ago

      I totally agree, plus I don't really expect it to get integrated into the base code, maybe more of an idea of what is wanted.

      but until there is AD group filtering, and all relevant fields in auth table are populated from ldap results, I will still need to use the work around that I came up with. At a minimum it would be nice to have the auth table filled with first name, last name, email address.

      Jeremy


  • 0
    derek-wilson-10759 12 years ago

    This might work, I get the feeling though that it's wrongly done. Not trying to discount your work, but it doesn't quite fit the current design.

    Is it possible you could create your own 'onaccept' method that will populate the auth.user.* fields with the correct data?

    replies (1)
    • yozik 12 years ago

      Well like I noted it works for me. Yes it's probably the wrong way to do things, but with the lack of AD/LDAP options and authenticating against them, I had to hack what I had. The current ldap auth may work as-is but I needed to get more data back from the ldap query than the stock implementation provides.

      Thinkng about it, I could do an additional ldap query once the stock ldap authentication returns.


Hosting graciously provided by:
Python Anywhere