#535 closed enhancement (fixed)
Group management and LDAP
| Reported by: | gvincent | Owned by: | anonymous |
|---|---|---|---|
| Priority: | normal | Milestone: | 0.9.1 |
| Component: | general | Version: | devel |
| Severity: | normal | Keywords: | LDAP ACL group |
| Cc: | maze@…, techref@…, danbeck-trac@…, myroslav@…, leho@… | Branch: | |
| Release Notes: | |||
| API Changes: | |||
| Internal Changes: | |||
Description
I haven't found any ticket about it, but I've got a problem with installing properly Trac actually : I would like to connect users via LDAP (more than 2000 users), for an Intranet. My Only problem is that actually the configuration of each new user must be done by hand, and this isn't a valuable solution for my use of Trac.
Is there any plan to implement group management in Trac ACL (and especially by LDAP ;) ?
Attachments (0)
Change History (26)
comment:1 by , 21 years ago
comment:2 by , 21 years ago
| Milestone: | 0.8 → 2.0 |
|---|
comment:3 by , 21 years ago
| Resolution: | → wontfix |
|---|---|
| Status: | new → closed |
As you might've realized already, this is not a Trac issue.
The best way to accomplish this is indeed to use Apache's LDAP authentication like dju' hinted at.
For Apache LDAP documentation see:
- http://httpd.apache.org/docs-2.0/mod/mod_auth_ldap.html
- http://httpd.apache.org/docs-2.0/mod/mod_ldap.html
For further questions, emailing the MailingList might help you get in touch with other users who've dealt with Trac and LDAP.
comment:4 by , 21 years ago
| Milestone: | 2.0 → 0.8 |
|---|---|
| Resolution: | wontfix |
| Status: | closed → reopened |
reopening
http://lists.edgewall.com/archive/trac/msg00349.html I guess the issue here is authenticating groups not useres.
comment:5 by , 21 years ago
| Keywords: | acces removed |
|---|---|
| Milestone: | 0.8 |
| Severity: | major → enhancement |
Correct URL is http://lists.edgewall.com/archive/trac/2004-June/000349.html
Anyway, this is an enhancement request, and I don't think it's going to happen for 0.8 (unless someone steps up and provides a good solution.)
comment:6 by , 21 years ago
| Version: | 0.7.1 → devel |
|---|
Here is a basic patch to support LDAP group within Trac
MODIFIED/CREATED FILES
scripts/tracd
modified version to support LDAP authentication. It's mainly for demonstration purposes.
Be careful: I did not took time to write a secure authentication: LDAP authentication here is based on 'Basic' HTTP authentication. In other words, user password is sent to the server with no encryption: password is Base64 encoded, that is, plain text. You should not use this over an
insecure network, etc. 8')
On my server, I do not use tracd, but Apache2 with mod_ldap and trac. I've updated tracd if you want to give a try with LDAP without the Apache2 / Ldap setup overhead.
trac/core.py
I wrote LDAP authentication so that it can easily adapted to an existing LDAP installation. Therefore, all LDAP parameters are optionally defined in the trac.ini config file. In order to acces .ini file, perm.py needs to get a reference on the 'Environment' instance. I've modified core.py so that it instanciates a PermissionCache instance
with the current environment.
trac/db_default.py
I've added the default LDAP value to this file, so that trad-admin creates a new trac.ini file with default LDAP parameters. You need to edit trac.ini file so that it matches your LDAP server configuration
trac/perm.py
PermissionCache has been modified so that it optionally support LDAP (if and only if ldap_auth is set to 'true' in trac.ini). If LDAP is enabled, PermissionCache retrieved the LDAP configuration parameters, and instanciates a Ldap connection with the server
trac/ldapgrp.py
Finally, the LDAP authentication class. It performs authentication (it binds the LDAP connection with the supplied user/password of the remote user), and retrieves the list of groups the user belongs to.
NOTES:
- Patch is based on Trac revision r915 (four days old). It won't work with official Trac release (0.7.1), as
perm.pyhas been slighty changed.
- Please note that I'm quite a newbie with Python, so there are probably a lot of improvements to be done. Security should be improved to (in order to use challenge mechanism instead of plain text password, etc.) I do not really care on this point since all access is done through HTTPS on our server, and LDAP server is on the same host. If you care about security issues, you should definitely fix up this code.
- I kept the '
@' sign to distinguish real username from group name. Since 0.7.1, Trac developers have introduced groupnames (which are not different from real username). I prefer to keep the usual syntax for groups; however, if you want to follow Trac rules, it should not be difficult to remove the '@' stuff
PATCH:
Tested with Linux 2.4.22, Open LDAP 2.1.30
Index: scripts/tracd
===================================================================
--- scripts/tracd (revision 915)
+++ scripts/tracd (working copy)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python
# -*- coding: iso8859-1 -*-
#
# Copyright (C) 2003, 2004 Edgewall Software
@@ -35,6 +35,7 @@
import urllib
import urllib2
import mimetypes
+import base64
import SocketServer
import BaseHTTPServer
@@ -42,6 +43,7 @@
import trac.core
import trac.util
+import trac.ldapgrp
from trac import Session
from trac.Href import Href
from trac import auth, siteconfig
@@ -129,7 +131,40 @@
self.active_nonces.remove(auth['nonce'])
return auth['username']
+class LdapAuth:
+ def __init__(self, hostport, basedn, realm):
+ host, port = hostport.split(':', 2)
+ self.host = host
+ if None != port:
+ self.port = int(port)
+ else:
+ self.port = 389
+ self.realm = realm
+ self.basedn = basedn
+ def send_auth_request(self, req, stale='false'):
+ req.send_response(401)
+ req.send_header('WWW-Authenticate',
+ 'Basic realm="%s"' % (self.realm))
+ req.end_headers()
+
+ def do_auth(self, req):
+ if not 'Authorization' in req.headers or \
+ req.headers['Authorization'][:5] != 'Basic':
+ self.send_auth_request(req)
+ return None
+ auth64 = urllib2.parse_http_list(req.headers['Authorization'][6:])[0]
+ auth = base64.decodestring(auth64)
+ username, passwd = auth.split(':', 2)
+ lc = trac.ldapgrp.Connection(host=self.host, port=self.port, basedn=self.basedn)
+ rc = lc.open(username,passwd)
+ lc.close()
+ if not rc:
+ self.send_auth_request(req)
+ return None
+ return username
+
+
class TracHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
pass
@@ -260,6 +295,7 @@
print 'usage: %s [options] <projenv> [projenv] ...' % sys.argv[0]
print '\nOptions:\n'
print '-a --auth [project],[htdigest_file],[realm]'
+ print '-l --ldapauth [project],[ldaphost:port],[realm],[basedn]'
print '-p --port [port]\t\tPort number to use (default: 80)'
print '-b --hostname [hostname]\tIP to bind to (default: \'\')'
print
@@ -272,8 +308,8 @@
auths = {}
projects = {}
try:
- opts, args = getopt.getopt(sys.argv[1:], "a:p:b:",
- ["auth=", "port=", "hostname="])
+ opts, args = getopt.getopt(sys.argv[1:], "a:l:p:b",
+ ["auth=", "ldapauth=", "port=", "hostname="])
except getopt.GetoptError:
usage()
for o, a in opts:
@@ -283,6 +319,12 @@
usage()
p, h, r = info
auths[p] = DigestAuth(h, r)
+ if o in ("-l", "--ldapauth"):
+ info = a.split(',', 3)
+ if len(info) != 4:
+ usage()
+ p, h, r, b = info
+ auths[p] = LdapAuth(h,b,r)
if o in ("-p", "--port"):
port = int(a)
elif o in ("-b", "--hostname"):
Index: trac/core.py
===================================================================
--- trac/core.py (revision 915)
+++ trac/core.py (working copy)
@@ -162,7 +162,7 @@
module.req = req
module._name = mode
module.db = db
- module.perm = perm.PermissionCache(module.db, req.authname)
+ module.perm = perm.PermissionCache(module.db, module.env, req.authname)
module.perm.add_to_hdf(req.hdf)
module.authzperm = None
Index: trac/db_default.py
===================================================================
--- trac/db_default.py (revision 915)
+++ trac/db_default.py (working copy)
@@ -445,5 +445,13 @@
('notification', 'smtp_from', 'trac@localhost'),
('notification', 'smtp_replyto', 'trac@localhost'),
('timeline', 'changeset_show_files', 'false'),
- ('timeline', 'changeset_files_count', 3))
+ ('timeline', 'changeset_files_count', 3),
+ ('ldapauth', 'ldap_auth', 'false'),
+ ('ldapauth', 'ldap_host', 'localhost'),
+ ('ldapauth', 'ldap_port', 389),
+ ('ldapauth', 'ldap_basedn', 'dc=example,dc=com'),
+ ('ldapauth', 'ldap_groupname', 'groupofnames'),
+ ('ldapauth', 'ldap_groupuid', 'cn'),
+ ('ldapauth', 'ldap_groupmember', 'member'),
+ ('ldapauth', 'ldap_uid', 'uid'))
Index: trac/ldapgrp.py
===================================================================
--- trac/ldapgrp.py (revision 0)
+++ trac/ldapgrp.py (revision 0)
@@ -0,0 +1,98 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2003, 2004 Edgewall Software
+# Copyright (C) 2003, 2004 Jonas Borgström <jonas@edgewall.com>
+#
+# Trac is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Trac is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Emmanuel Blot <emmanuel.blot@free.fr>
+
+import ldap
+import re
+
+class Connection:
+ def __init__(self, **ldap):
+ self.host = 'localhost'
+ self.port = 389
+ self.basedn = ''
+ self.groupname = 'groupofnames'
+ self.groupuid = 'cn'
+ self.groupmember = 'member'
+ self.uid = 'uid'
+ if ldap.has_key('host'): self.host = ldap['host']
+ if ldap.has_key('port'): self.port = ldap['port']
+ if ldap.has_key('basedn'): self.basedn = ldap['basedn']
+ if ldap.has_key('groupname'): self.groupname = ldap['groupname']
+ if ldap.has_key('groupuid'): self.groupuid = ldap['groupuid']
+ if ldap.has_key('groupmember'): self.groupmember = ldap['groupmember']
+ if ldap.has_key('uid'): self.uid = ldap['uid']
+
+ def basedn(self):
+ return self.basedn;
+
+ def open(self, uid=None, password=None):
+ try:
+ self.__ds = ldap.open(self.host, self.port)
+ self.__ds.protocol_version = ldap.VERSION3
+ if uid is not None:
+ if ( not uid.startswith('%s=' % self.uid) ):
+ uid = '%s=%s' % (self.uid, uid)
+ self.__ds.simple_bind_s(uid + ',' + self.basedn, password)
+ self.__login = uid
+ return True
+ except ldap.LDAPError, e:
+ return False
+
+ def close(self):
+ self.__ds.unbind_s()
+ self.__login = ''
+
+ def isOwner(self, uid):
+ return self.__login == uid
+
+ def search(self, filter, attributes=None):
+ try:
+ sr = self.__ds.search_s(self.basedn, ldap.SCOPE_SUBTREE, filter, attributes)
+ return sr
+
+ except ldap.LDAPError, e:
+ return False;
+
+ def compare(self, dn, attribute, value):
+ try:
+ cr = self.__ds.compare_s(dn + "," + self.basedn, attribute, value)
+ return cr
+
+ except ldap.LDAPError, e:
+ return False
+
+ def enumerate_groups(self):
+ attributes = ['dn']
+ sr = self.search('objectclass=' + self.groupname, attributes)
+ groups = ['none']
+ if sr:
+ for (dn, attrs) in sr:
+ regex = re.compile('^(\w+)=(\w+)')
+ m = regex.search(dn)
+ if m:
+ groups.append(m.group(2))
+ return groups
+
+ def is_in_group(self, uid, group):
+ dn = self.groupuid + "=" + group
+ value = self.uid + "=" + uid + "," + self.basedn
+ cr = self.compare(dn, self.groupmember, value)
+ return cr
+
Index: trac/perm.py
===================================================================
--- trac/perm.py (revision 915)
+++ trac/perm.py (working copy)
@@ -104,7 +104,7 @@
'authenticated': Permissions granted to this user will apply to
any authenticated (logged in with HTTP_AUTH) user.
"""
- def __init__(self, db, username):
+ def __init__(self, db, env, username):
self.perm_cache = {}
cursor = db.cursor()
cursor.execute ("SELECT username, action FROM permission")
@@ -114,6 +114,26 @@
users = ['anonymous']
if username != 'anonymous':
users += [username, 'authenticated']
+
+ useldap = False
+ if env.get_config('ldapauth', 'ldap_auth', '') == 'true':
+ useldap = True
+ import ldapgrp
+ if useldap and (username[0] !='@'):
+ lc = ldapgrp.Connection(host=env.get_config('ldapauth', 'ldap_host', 'localhost'),
+ port=int(env.get_config('ldapauth', 'ldap_port', '389')),
+ basedn=env.get_config('ldapauth', 'ldap_basedn', ''),
+ groupname=env.get_config('ldapauth', 'ldap_groupname', 'groupofnames'),
+ groupuid=env.get_config('ldapauth', 'ldap_groupuid', 'cn'),
+ groupmember=env.get_config('ldapauth', 'ldap_groupmember', 'member'),
+ uid=env.get_config('ldapauth', 'ldap_uid', 'uid'))
+ if lc.open():
+ groups = lc.enumerate_groups()
+ for group in groups:
+ if lc.is_in_group(username, group):
+ users.append('@' + group)
+ lc.close()
+
while 1:
num_users = len(users)
num_perms = len(perms)
comment:7 by , 21 years ago
Updated code for 0.9 pre (1366) is available from here: http://anciens.enib.fr/trac/public/wiki/TracLdap
comment:8 by , 21 years ago
Are there any plans to merge this into the trunk? I'm trying to stay updated with that branch, and I'm no python expert, so handling them myself isn't possible. I humbly beg for this. :)
comment:9 by , 21 years ago
| Cc: | added |
|---|
comment:10 by , 20 years ago
| Cc: | added |
|---|
comment:11 by , 20 years ago
| Cc: | added |
|---|
I'm also interested in a deeper integration of LDAP (or Active Directory) into Trac. All relevant user information (email address, full name) is available there.
comment:12 by , 20 years ago
I've been working on it. I hope to propose a working solution by the end of august or september '05
comment:14 by , 20 years ago
| Cc: | added |
|---|
comment:15 by , 20 years ago
| Cc: | added; removed |
|---|
comment:16 by , 20 years ago
I put a very basic implementation for LDAP group here:
http://anciens.enib.fr/trac/public/wiki/TracLdap
comment:17 by , 20 years ago
LdapPlugin, featuring some kind of LDAP ACLs and group management, is now available on Trac Hacks.
comment:18 by , 20 years ago
| Cc: | added |
|---|
comment:19 by , 20 years ago
| Milestone: | → 0.9.1 |
|---|---|
| Resolution: | → fixed |
| Status: | reopened → closed |
| Summary: | group management and LDAP → Group management and LDAP |
I'm closing this bug, as LDAP extension is not a piece of the core Trac engine. To request new features, report bugs or participate in the LDAP plugin development, please follow the http://trac-hacks.swapoff.org link.
Closing the bug as 'fixed', as the requested features are now available for 0.9.1 within the current implementation of the LDAP plugin.
comment:20 by , 20 years ago
| Cc: | removed |
|---|
comment:21 by , 19 years ago
| Cc: | removed |
|---|
follow-ups: 24 25 comment:22 by , 19 years ago
I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.
comment:23 by , 14 years ago
| Cc: | added |
|---|
comment:24 by , 14 years ago
| Owner: | changed from to |
|---|
Replying to anonymous:
I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.
comment:25 by , 14 years ago
Replying to anonymous:
I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.
How does the standalone server use LDAP? I could not find any documentation for this. Also, would the authentication be secure as the documentation says that standalone server uses clear text and does not have https support.
comment:26 by , 14 years ago
there is no LDAP support built into tracd. currently the closest you are going to get is th:LdapLugin. i'm about to retry getting started on #2456 which probably encompasses this as well.



i believe you have to use Apache directives to do this, like <Location /trac/ticket/> […] Require group group1 </Location>.