- Author:
- trebor74hr
- Posted:
- March 26, 2009
- Language:
- Python
- Version:
- 1.0
- Score:
- 1 (after 1 ratings)
This is based on snippet 501, with some corrections:
- if user doesn't exist and AD.authenticate passes, then create new user - don't store password - prevent default django auth backend authentication
- if user exists and AD.authenticate passes - django User object is updated
- various error handling
- fixes (some mentioned in original snippet)
- some settings removed from settings to backend module
- other changes (ADUser class, re-indent, logging etc.)
- ignores problem with search_ext_s (DSID-0C090627)
- caching connection - when invalid then re-connect and try again
Note that there is also ldaps:// (SSL version) django auth backend on snippet 901.
Possible improvements:
- define new settings param - use secured - then LDAPS (snippet 901)
- define new settings extra ldap options - e.g. protocol version
- fetch more data from AD - fill in user data - maybe to make this configurable to be able to update user.get_profile() data too (some settings that has mapping AD-data -> User/UserProfile data)
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | # ----------------------------------------
# ActiveDirectoryBackend
# ----------------------------------------
#
# Created new snippet:
# http://www.djangosnippets.org/snippets/edit/1397/
# based on:
# http://www.djangosnippets.org/snippets/501/
# similar snippet (ldaps:// - secured) is on:
# http://www.djangosnippets.org/snippets/901/
#
# ---------- settings.py -------------------
#
# # Active directory auth backend setup
# # on win32 check env.vars:
# # USERDNSDOMAIN=EXAMPLE.COM
# # USERDOMAIN=EXAMPLE
# AD_DNS_NAME = 'domaindnsname.local'
# AD_NT4_DOMAIN = 'DOMAINNAME' # This is the NT4/Samba domain name
# AD_SEARCH_DN = 'dc=domaindnsname,dc=local'
# AD_LDAP_PORT = 389
#
# AUTHENTICATION_BACKENDS = (
# 'company.django.auth.ActiveDirectoryBackend',
# # if you want to have default django backend too
# 'django.contrib.auth.backends.ModelBackend',
# )
#
import ldap
import logging
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
from django.conf import settings
logger = logging.getLogger()
# -------------------------------------------
# general function - belongs to utils
def get_exc_str(bClear=False):
import sys, traceback
x=sys.exc_info()
if not x[0]:
return "No py exception"
out="%s/%s/%s" % (str(x[0]), str(traceback.extract_tb(x[2])), str(x[1]))
if bClear:
sys.exc_clear()
return out
# --------------------------------------------
class ADUser(object):
# class level, makes "operational error" problem by search occurs less
ldap_connection = None
AD_SEARCH_FIELDS = ['mail','givenName','sn','sAMAccountName']
@classmethod
def get_ldap_url(cls):
return 'ldap://%s:%s' % (settings.AD_DNS_NAME, settings.AD_LDAP_PORT)
def __init__(self, username):
self.username = username
self.user_bind_name = "%s@%s" % (self.username, settings.AD_NT4_DOMAIN)
self.is_bound = False
self.has_data = False
self.first_name = None
self.last_name = None
self.email = None
def connect(self, password):
had_connection = ADUser.ldap_connection is not None
ret = self._connect(password)
# WORKAROUND: for invalid connection
if not ret and had_connection and ADUser.ldap_connection is None:
logger.warning("AD reset connection - invalid connection, try again with new connection")
ret = self._connect(password)
return ret
def _connect(self, password):
if not password:
return False # Disallowing null or blank string as password
try:
if ADUser.ldap_connection is None:
logger.info("AD auth backend ldap connecting")
ADUser.ldap_connection = ldap.initialize(self.get_ldap_url())
assert self.ldap_connection==ADUser.ldap_connection # python won't do that ;)
self.ldap_connection.simple_bind_s(self.user_bind_name,password)
self.is_bound = True
except Exception, e:
if str(e.message).find("connection invalid")>=0:
logger.warning("AD reset connection - it looks like invalid: %s (%s)" % (str(e), get_exc_str()))
ADUser.ldap_connection = None
else:
logger.error("AD auth backend ldap - probably bad credentials: %s (%s)" % (str(e), get_exc_str()))
return False
return True
def disconnect(self):
if self.is_bound:
logger.info("AD auth backend ldap unbind")
self.ldap_connection.unbind_s()
self.is_bound=False
def get_data(self):
try:
assert self.ldap_connection
# NOTE: Something goes wrong in my case - ignoring this until solved :(
# {'info': '00000000: LdapErr: DSID-0C090627, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, vece', 'desc': 'Operations error'}
res = self.ldap_connection.search_ext_s(settings.AD_SEARCH_DN,
ldap.SCOPE_SUBTREE,
"sAMAccountName=%s" % self.username,
self.AD_SEARCH_FIELDS)
self.disconnect()
if not res:
logger.error("AD auth ldap backend error by searching %s. No result." % settings.AD_SEARCH_DN)
return False
assert len(res)>=1, "Result should contain at least one element: %s" % res
result = res[0][1]
except Exception, e:
self.disconnect()
logger.error("AD auth backend error by fetching ldap data: %s (%s)" % (str(e), get_exc_str()))
return False
try:
self.first_name = None
if result.has_key('givenName'):
self.first_name = result['givenName'][0]
self.last_name = None
if result.has_key('sn'):
self.last_name = result['sn'][0]
self.email = None
if result.has_key('mail'):
self.email = result['mail'][0]
self.has_data = True
except Exception, e:
logger.error("AD auth backend error by reading fetched data: %s (%s)" % (str(e), get_exc_str()))
return False
return True
def __del__(self):
try:
self.disconnect()
except Exception, e:
logger.error("AD auth backend error when disconnecting: %s (%s)" % (str(e), get_exc_str()))
return False
def __str__(self):
return "AdUser(<%s>, connected=%s, is_bound=%s, has_data=%s)" % (self.username, self.ldap_connection is not None, self.is_bound, self.has_data)
# -------------------------------------------
class ActiveDirectoryBackend(ModelBackend):
def authenticate(self,username=None,password=None):
logger.info("AD auth backend for %s" % username)
aduser = ADUser(username)
if not aduser.connect(password):
return None
user = None
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(username=username, is_staff = False, is_superuser = False)
if not aduser.get_data():
logger.warning("AD auth backend failed when reading data for %s. User detail data won't be updated in User model." % username)
else:
# NOTE: update user data exchange to User model
assert user.username==aduser.username
user.first_name=aduser.first_name
user.last_name=aduser.last_name
user.email=aduser.email
#user.set_password(password)
logger.warning("AD auth backend overwriting auth.User data with data from ldap for %s." % username)
user.save()
logger.info("AD auth backend check passed for %s" % username)
return user
# NOTE: no need to implement get_user - ModelBackend.get_user is all I need
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 9 months ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 9 months, 1 week ago
- Serializer factory with Django Rest Framework by julio 1 year, 4 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 4 months ago
- Help text hyperlinks by sa2812 1 year, 5 months ago
Comments
I believe ActiveDirectoryBackend should inherit from django.contrib.auth.backends.ModelBackend, or else permissions will not work.
#
Thanks, now is based on ModelBackend. Other things I did is:
#
where does it store the log file?
#
try it with
ldap.set_option(ldap.OPT_REFERRALS, 0)
right after importing the ldap module.
This will let you override the backend auth.User data with data from ldap.
#
Please login first before commenting.