Login

Authentication Against Active Directory (LDAP) over SSL

Author:
mary
Posted:
July 21, 2008
Language:
Python
Version:
.96
Score:
4 (after 4 ratings)

I had some trouble getting other peoples code to work for AD support, so I wrote my own which authenticates against LDAP and will also use SSL and cert if required. It will also verify that an autheticated user has specific group membership before authorizing.

This will also debug to a file, which is really helpful when trying to figure out problems.

One thing that really got me when getting python-ldap to work was that you must have "ldap.set_option(ldap.OPT_REFERRALS,0)" set or any ldap search will not work.

Also, this will add group permissions to a user.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
In settings:
AD_DNS_NAME='your-ldap-server.com'
# If using non-SSL use these
#AD_LDAP_PORT=389
#AD_LDAP_URL='ldap://%s:%s' % 
(AD_DNS_NAME,AD_LDAP_PORT)
# If using SSL use these:
AD_LDAP_PORT=636
AD_LDAP_URL='ldaps://%s:%s' % (AD_DNS_NAME,AD_LDAP_PORT)
AD_SEARCH_DN='dc=mygroup,dc=net,dc=com'
AD_NT4_DOMAIN='YOURDOMAIN'
AD_SEARCH_FIELDS= ['mail','givenName','sn','sAMAccountName','memberOf']
AD_MEMBERSHIP_REQ=['Group_Required','Alternative_Group']
AD_CERT_FILE='/path/to/your/cert.txt'
AUTHENTICATION_BACKENDS = ('reviewboard.accounts.backends.ActiveDirectoryGroupMembershipSSLBackend',
'django.contrib.auth.backends.ModelBackend')
AD_DEBUG=True
AD_DEBUG_FILE='/path/to/writable/log/file/ldap.debug'

In accounts/backends.py:

class ActiveDirectoryGroupMembershipSSLBackend:

  def authenticate(self,username=None,password=None):
      try:
         if len(password) == 0:
            return None
         ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,settings.AD_CERT_FILE)
         l = ldap.initialize(settings.AD_LDAP_URL)
         l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
         binddn = "%s@%s" % (username,settings.AD_NT4_DOMAIN)
         l.simple_bind_s(binddn,password)
         l.unbind_s()
         return self.get_or_create_user(username,password)

      except ImportError:
         pass
      except ldap.INVALID_CREDENTIALS:
         pass

  def get_or_create_user(self, username, password):
      try:
         user = User.objects.get(username=username)
      except User.DoesNotExist:

         try:
            # debug info
            debug=0
            if len(settings.AD_DEBUG_FILE) > 0:
               if settings.AD_DEBUG:
                   debug=open(settings.AD_DEBUG_FILE,'w')
                   print >>debug, "create user %s" % username

            ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,settings.AD_CERT_FILE)
            ldap.set_option(ldap.OPT_REFERRALS,0) # DO NOT TURN THIS OFF OR SEARCH WON'T WORK!      
            # initialize
            if debug:
               print >>debug, 'ldap.initialize...'
            l = ldap.initialize(settings.AD_LDAP_URL)
            l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)

            # bind
            if debug:
               print >>debug, 'bind...'
            binddn = "%s@%s" % (username,settings.AD_NT4_DOMAIN)
            l.bind_s(binddn,password)

            # search
            if debug:
               print >>debug, 'search...'
            result = l.search_ext_s(settings.AD_SEARCH_DN,ldap.SCOPE_SUBTREE,"sAMAccountName=%s" % username,settings.AD_SEARCH_FIELDS)[0][1]
            if debug:
               print >>debug, result

            # Validate that they are a member of review board group
            if result.has_key('memberOf'):
                membership = result['memberOf']
            else:
                membership = None
            if debug:
               print >>debug, "required:%s" % settings.AD_MEMBERSHIP_REQ
            bValid=0
            for req_group in settings.AD_MEMBERSHIP_REQ:
               if debug:
                  print >>debug, "Check for %s group..." % req_group
               for group in membership:
                   group_str="CN=%s," % req_group
                   if group.find(group_str) >= 0:
                        if debug:
                           print >>debug, "User authorized: group_str membership found!"
                        bValid=1
                        break
            if bValid == 0:
               if debug:
                   print >>debug, "User not authorized, correct group membership not found!"
               return None

            # get email
            if result.has_key('mail'):
                mail = result['mail'][0]
            else:
                mail = None
            if debug:
                print >>debug, "mail=%s" % mail
            # get surname
            if result.has_key('sn'):
                last_name = result['sn'][0]
            else:
                last_name = None
            if debug:
                print >>debug, "sn=%s" % last_name

            # get display name
            if result.has_key('givenName'):
                first_name = result['givenName'][0]
            else:
                first_name = None
            if debug:
               print >>debug, "first_name=%s" % first_name

            l.unbind_s()

            user = User(username=username,first_name=first_name,last_name=last_name,email=mail)

         except Exception, e:
            if debug:
               print >>debug, "exception caught!"
               print >>debug, e
            return None

         user.is_staff = False
         user.is_superuser = False
         user.set_password('ldap authenticated')
         user.save()

         # add user to default group
         group=Group.objects.get(pk=1)
         if debug:
             print >>debug, group
         if debug:
             print >>debug, "add %s to group %s" % (username,group)
         user.groups.add(group)
         user.save()
         if debug:
             print >>debug, "successful group add"

         if debug:
            debug.close()


      return user

  def get_user(self, user_id):
      try:
        return User.objects.get(pk=user_id)
      except User.DoesNotExist:
        return None

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 3 weeks ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 10 months, 1 week ago
  5. Help text hyperlinks by sa2812 11 months ago

Comments

jashugan (on October 26, 2008):

Two questions about the certificate you used:

  1. Was it the root certificate for your AD Domain?
  2. Was it base 64 encoded?

#

oz123 (on May 3, 2013):

Just to note, this snippet is very useful, but you really need to make sure that you have the groups in your django db. Otherwise you will see errors like:

DoesNotExist: Group matching query does not exist.

So, I patched the script to catch these errors with:

      # Adds all groups user is member of
      for group in userInfo['idGroup']:
          try:
              g = Group.objects.get(name=group)
          except Group.DoesNotExist:
              Group.objects.get_or_create(name=group)

#

RossRogers (on December 21, 2015):

Beware to change line 133 user.set_password('ldap authenticated') to a random number like:

from Crypto.Random import random
[...]
user.set_password('%x'%random.randint(0,2**256))

Otherwise, anyone can log in as a user with password "ldap authenticated".

#

Please login first before commenting.