Modify

Opened 15 years ago

Last modified 6 years ago

#6268 new defect

LdapPlugin fails with MS Active Directory, lacks email / full name

Reported by: Zachary Bedell Owned by: Emmanuel Blot
Priority: normal Component: LdapPlugin
Severity: normal Keywords: ldap, activedirectory, windows, group, permissions
Cc: Trac Release: 0.11

Description

I've run into a number of issues using the LdapPlugin against MS Active Directory. The plugin assumed that the Common Name (CN) attribute of a principal's Distinguished Name (DN) was always equivalent to its login name and that you could convert from DN to login name with simple string manipulation.

Alas, this is not (remotely) the case with our directory as various users may have as their CN their login name, their full given name, some variation on their given name (nick names), and in some cases completely meaningless identifiers. A mess, no doubt; but out of my control.

As far as my understanding of LDAP goes, assuming login == CN is somewhat simplistic and given to failure. I've modified the plugin to connect to the directory whenever a CN/DN/login translation is needed. I've also attempted to cache the conversions where practical in order to reduce the number of queries against the directory as much as possible. I suspect additional caching improvements are possible.

Attached to this ticket is a replaced api.py file which implements these changes. I'd ordinarily attach a patch, but the changes were extensive enough to render a patch relatively pointless.

Also included in this version is additional logic to extract the user's email and full name from the appropriate directory fields and insert them into the Trac session. The idea of piggybacking on the web filter system is from AccountLdapPlugin with some minor tweaks.

To use this version, you'll need to take the rest of the scaffolding from the trunk build of he LdapPlugin-0.11 and replace the ldaplugin/api.py file with the attached one.

Configuration settings like the following are working for our ActiveDirectory, but YMMV:

[ldap]
enable=true
use_tls=false
host=your.server
port=389
# Note: Must be the full DN, sAMAccount name or email-like domain login will NOT work
bind_user=CN=LDAP Search User,OU=Windows Systems,...
bind_passwd=pass
# Use authenticated bind
group_bind = true

# Adjust DN's to taste
basedn=OU=RTP,...
group_rdn = OU=Programming,...
user_rdn = OU=users

# Group settings - use the Windows login name for the group (sAMAccountname)
groupname = group
groupattr = sAMAccountName
groupmember = member
groupmemberisdn = true

# Use Windows login name for user
uidattr = sAMAccountName

# NEW FOR PATCH:
# Attributes on the user object from which full name and email address will be read
user_fullname_attr = displayName
user_email_attr = mail

# Our tree is readonly, but you might want to enable this
manage_groups = false

Attachments (1)

api.py (28.7 KB) - added by Zachary Bedell 15 years ago.
Updated version

Download all attachments as: .zip

Change History (10)

Changed 15 years ago by Zachary Bedell

Attachment: api.py added

Updated version

comment:1 Changed 15 years ago by anonymous

I replaced LdapPlugin api.py with attached file. User can successfully login trac system. But there is a warning: Warning: <acct_mgr.web_ui.MessageWrapper object at 0xb7a3d12c>

And error: WIKI_VIEW privileges are required to perform this operation on WikiStart

All trac links are forbidden. I think it was caused by loginned usr not gotten permission.

comment:2 Changed 14 years ago by aelmahmoudy@…

Hello,

I am using OpenLDAP under Ubuntu 10.04, to get your api.py patch working I had to do the following modification: replace objectClass=user with objectClass=posixAccount , I think this should be configurable like the 'groupname' parameter.

comment:3 Changed 14 years ago by aelmahmoudy@…

I have a couple of other issues:

  1. The email gets recorded in the trac database, so if I change the email address of the user in the LDAP (that is probably a very rare use case), then it won't get updated in trac, if that user used trac before his email changed in LDAP.
  1. I use unicode characters for the name, that causes this error in trac "UnicodeDecodeError: 'ascii' codec can't decode byte 0xd8 in position 0: ordinal not in range(128)

", hence I had to comment the line:

req.sessionname? = name

This is the error traceback:

Trac detected an internal error:

Most recent call last:

  • File "/usr/lib/python2.6/dist-packages/trac/web/main.py", line 450, in _dispatch_request Code fragment:
    1. try:
    2. if not env and env_error:
    3. raise HTTPInternalError(env_error)
    4. try:
    5. dispatcher = RequestDispatcher(env)
    6. dispatcher.dispatch(req)
    7. except RequestDone:
    8. pass
    9. resp = req._response or []
    10. except HTTPException, e:
    Local variables: Name Value after [u' except RequestDone:', u' pass', u' resp = ... before [u' try:', u' if not env and env_error:', u' raise ... dispatcher <trac.web.main.RequestDispatcher object at 0xa92773cc> e UnicodeDecodeError('ascii', '\xd8\xa3\xd8\xad\xd9\x85\xd8\xaf ... env <trac.env.Environment object at 0xa9eb406c> env_error None exc_info (<type 'exceptions.UnicodeDecodeError'>, UnicodeDecodeError('ascii', ... filename '/usr/lib/python2.6/dist-packages/trac/web/main.py' frames [{'function': '_dispatch_request', 'lines_before': [u' try:', u' ... has_admin True line u' dispatcher.dispatch(req)' lineno 449 message u"UnicodeDecodeError: 'ascii' codec can't decode byte 0xd8 in position 0: ... req <Request "GET u'/'"> resp [] tb <traceback object at 0xa92d4504> tb_hide None traceback u'Traceback (most recent call last):\n File ...
  • File "/usr/lib/python2.6/dist-packages/trac/web/main.py", line 217, in dispatch Code fragment:
    1. # Give the session a chance to persist changes
    2. req.session.save()
    3. req.display(template, content_type or 'text/html')
    4. else: # Genshi
    5. template, data, content_type = \
    6. self._post_process_request(req, *resp)
    7. if 'hdfdump' in req.args:
    8. req.perm.require('TRAC_ADMIN')
    9. # debugging helper - no need to render first
    10. from pprint import pprint
    11. out = StringIO()
    Local variables: Name Value chosen_handler <trac.wiki.web_ui.WikiModule object at 0xa92772ac> chrome <trac.web.chrome.Chrome object at 0x221ae8cc> e UnicodeDecodeError('ascii', '\xd8\xa3\xd8\xad\xd9\x85\xd8\xaf ... err (<type 'exceptions.UnicodeDecodeError'>, UnicodeDecodeError('ascii', ... handler <trac.ticket.report.ReportModule object at 0xa9277c2c> req <Request "GET u'/'"> resp ('wiki_view.html', {'templates': [], 'dateinfo': <function dateinfo at ... self <trac.web.main.RequestDispatcher object at 0xa92773cc>
  • File "/usr/lib/python2.6/dist-packages/trac/web/main.py", line 309, in _post_process_request Code fragment:
    1. # Trac 0.10, only filters with same arity gets passed real values.
    2. # Errors will call all filters with None arguments,
    3. # and results will not be not saved.
    4. extra_arg_count = arity(f.post_process_request) - 2
    5. if extra_arg_count == nbargs:
    6. resp = f.post_process_request(req, *resp)
    7. elif nbargs == 0:
    8. f.post_process_request(req, *(None,)*extra_arg_count)
    9. return resp
    Local variables: Name Value args ('wiki_view.html', {'templates': [], 'dateinfo': <function dateinfo at ... extra_arg_count 3 f <ldapplugin.api.LdapPermissionGroupProvider object at 0xa927714c> nbargs 3 req <Request "GET u'/'"> resp ('wiki_view.html', {'templates': [], 'dateinfo': <function dateinfo at ... self <trac.web.main.RequestDispatcher object at 0xa92773cc>
  • File "/usr/local/lib/python2.6/dist-packages/LdapPlugin-0.6.0dev-py2.6.egg/ldapplugin/api.py", line 87, in post_process_request Code fragment:
    1. # Info isn't already in the session
    2. uid = req.remote_user.lower()
    3. if uid in self._cache:
    4. # We have something in cache, so let's stick it in the session.
    5. lut, groups, mail, name = self._cache[uid]
    6. req.sessionname? = name
    7. req.sessionemail? = mail 89.
    8. return template, data, content_type
    9. # IPermissionProvider interface
    Local variables: Name Value content_type None data {'templates': [], 'dateinfo': <function dateinfo at 0xa930ae64>, ... groups [] lut 1277296367.4117129 mail 'aelmahmoudy@…' name '\xd8\xa3\xd8\xad\xd9\x85\xd8\xaf ... req <Request "GET u'/'"> self <ldapplugin.api.LdapPermissionGroupProvider object at 0xa927714c> template 'wiki_view.html' uid 'aelmahmoudy'
  • File "/usr/lib/python2.6/dist-packages/trac/web/session.py", line 46, in setitem Code fragment:
    1. self.get_session(sid, authenticated=True)
    2. else:
    3. self.authenticated = False
    4. def setitem(self, key, value):
    5. dict.setitem(self, key, unicode(value))
    6. def get_session(self, sid, authenticated=False):
    7. self.env.log.debug('Retrieving session for ID %r', sid)
    8. db = self.env.get_db_cnx()
    Local variables: Name Value key 'name' self {u'timeline.daysback': u'30', u'timeline.author': u, u'name': u, ... value '\xd8\xa3\xd8\xad\xd9\x85\xd8\xaf ...

File "/usr/lib/python2.6/dist-packages/trac/web/main.py", line 450, in _dispatch_request

dispatcher.dispatch(req)

File "/usr/lib/python2.6/dist-packages/trac/web/main.py", line 217, in dispatch

self._post_process_request(req, *resp)

File "/usr/lib/python2.6/dist-packages/trac/web/main.py", line 309, in _post_process_request

resp = f.post_process_request(req, *resp)

File "/usr/local/lib/python2.6/dist-packages/LdapPlugin-0.6.0dev-py2.6.egg/ldapplugin/api.py", line 87, in post_process_request

req.sessionname? = name

File "/usr/lib/python2.6/dist-packages/trac/web/session.py", line 46, in setitem

dict.setitem(self, key, unicode(value))

comment:4 Changed 14 years ago by aelmahmoudy@…

Hello,

I fixed the unicode error by replacing

req.sessionname? = name

with:

from trac.util.text import to_unicode; req.sessionname? = to_unicode(name)

comment:5 Changed 14 years ago by DennisR

Trying to get this to work with Trac 0.12.1, and openldap. Compiles fine and all that.

Looks like it's working, however it doesn't appear to be populating the "Full name" and "Email" under Preferences. I should note that this is the only functionality I'm looking for, just fullname and email from ldap.

I edited api.py and enabled some debugging. I can see it pulling in the correct information, it's just not populating it.

2011-01-06 16:11:29,006 Trac[session] DEBUG: Retrieving session for ID u'dennisr'
2011-01-06 16:11:29,032 Trac[api] DEBUG: Searching for user dennisr with filter (uid=dennisr) at basedn ou=people,dc=wi,dc=mit,dc=edu
2011-01-06 16:11:29,034 Trac[api] DEBUG: {'mail': ['dennisr@wi.mit.edu'], 'displayName': ['Dennis Ristuccia']}

and ldap settings from our trac.ini:

[ldap]
# enable LDAP support for Trac
enable = true
# enable TLS support
use_tls = false
# LDAP directory host
host = 18.157.1.65
# LDAP directory port (default port for LDAPS/TLS connections is 636)
port = 389
# BaseDN
basedn = dc=wi,dc=mit,dc=edu
# Relative DN for users (defaults to none)
user_rdn = ou=People
# Relative DN for group of names (defaults to none)
group_rdn =
# objectclass for groups
groupname =
# dn entry in a groupname
#groupmember =
# attribute name for a group
groupattr = cn
# attribute name for a user
uidattr = uid
# attribute name to store trac permission
#permattr =
# filter to search for dn with 'permattr' attributes
#permfilter =
# time, in seconds, before a cached entry is purged out of the local cache.
cache_ttl = 900
# maximum number of entries in the cache
cache_size = 100
# whether to perform an authenticated bind for group resolution
group_bind = false
# whether to perform an authenticated bind for permision store operations
store_bind = false
# user for authenticated connection to the LDAP directory
#bind_user = dennisr
# password for authenticated connection
#bind_passwd = 
# global permissions (vs. per-environment permissions)
global_perms = false
# group permissions are managed as addition/removal to the LDAP directory groups
manage_groups = false
# whether a group member contains the full dn or a simple uid
groupmemberisdn = true
# NEW FOR PATCH:
# Attributes on the user object from which full name and email address will be read
user_fullname_attr = displayName                                                   
user_email_attr = mail
# Our tree is readonly, but you might want to enable this
manage_groups = false

comment:6 Changed 13 years ago by falkb

I replaced api.py with the one of this ticket, but it just doesn't work and ends up in this error:

Traceback (most recent call last):
  File "build\bdist.win32\egg\trac\web\main.py", line 511, in _dispatch_request
    dispatcher.dispatch(req)
  File "build\bdist.win32\egg\trac\web\main.py", line 237, in dispatch
    resp = chosen_handler.process_request(req)
  File "build\bdist.win32\egg\trac\admin\web_ui.py", line 80, in process_request
    panels, providers = self._get_panels(req)
  File "build\bdist.win32\egg\trac\admin\web_ui.py", line 163, in _get_panels
    p = list(provider.get_admin_panels(req) or [])
  File "build\bdist.win32\egg\tickettemplate\ttadmin.py", line 137, in get_admin_panels
    if 'TT_ADMIN' in req.perm:
  File "build\bdist.win32\egg\trac\perm.py", line 553, in has_permission
    return self._has_permission(action, resource)
  File "build\bdist.win32\egg\trac\perm.py", line 567, in _has_permission
    check_permission(action, perm.username, resource, perm)
  File "build\bdist.win32\egg\trac\perm.py", line 454, in check_permission
    perm)
  File "build\bdist.win32\egg\trac\perm.py", line 286, in check_permission
    get_user_permissions(username)
  File "build\bdist.win32\egg\trac\perm.py", line 372, in get_user_permissions
    for perm in self.store.get_user_permissions(username) or []:
  File "build\bdist.win32\egg\trac\perm.py", line 173, in get_user_permissions
    subjects.update(provider.get_permission_groups(username) or [])
  File "build\bdist.win32\egg\ldapplugin\api.py", line 125, in get_permission_groups
    self._ldap = LdapConnection(self.env.log, self.config, bind, **self._ldapcfg)

comment:7 in reply to:  6 ; Changed 13 years ago by falkb

Replying to falkb: copy'n'paste error. I forgot the last line:

...
  File "build\bdist.win32\egg\ldapplugin\api.py", line 125, in get_permission_groups
    self._ldap = LdapConnection(self.env.log, self.config, bind, **self._ldapcfg)
TypeError: __init__() keywords must be strings

comment:8 in reply to:  7 Changed 13 years ago by falkb

Replying to falkb:

Replying to falkb: copy'n'paste error. I forgot the last line:

...
  File "build\bdist.win32\egg\ldapplugin\api.py", line 125, in get_permission_groups
    self._ldap = LdapConnection(self.env.log, self.config, bind, **self._ldapcfg)
TypeError: __init__() keywords must be strings

Umm... later I read there is already #6183 fixing that error!

After applying attachment:ticket:6183:v070trac012.diff I got another runtime error but that could be fixed by adding entries for user_fullname_attr and user_email_attr to trac.ini.

Next on login I got another error grumbling about an unicode issue with German umlauts. Don't know what to do then but as workaround it helps to use user_fullname_attr = initials instead of user_fullname_attr = displayName .

Now everything seems to work fine here... at least the first 15 minutes... ;)

comment:9 Changed 6 years ago by figaro

Keywords: active directory removed

Modify Ticket

Change Properties
Set your email in Preferences
Action
as new The owner will remain Emmanuel Blot.

Add Comment


E-mail address and name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.