Ticket #535 (closed enhancement: fixed)
Opened 8 years ago
Last modified 3 months ago
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@… | ||
| Release Notes: | |||
| API 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
Change History
comment:1 Changed 8 years ago by dju`
comment:2 Changed 8 years ago by daniel
- Milestone changed from 0.8 to 2.0
comment:3 Changed 8 years ago by daniel
- Resolution set to wontfix
- Status changed from new to 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 Changed 8 years ago by anonymous
- Milestone changed from 2.0 to 0.8
- Resolution wontfix deleted
- Status changed from closed to reopened
reopening
http://lists.edgewall.com/archive/trac/msg00349.html
I guess the issue here is authenticating groups not useres.
comment:5 Changed 7 years ago by cmlenz
- Keywords acces removed
- Milestone 0.8 deleted
- Severity changed from major to 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 Changed 7 years ago by eblotml@…
- Version changed from 0.7.1 to 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.py has 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 Changed 7 years ago by anonymous
Updated code for 0.9 pre (1366) is available from here:
http://anciens.enib.fr/trac/public/wiki/TracLdap
comment:8 Changed 7 years ago by anonymous
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 Changed 7 years ago by Florent Guillaume <fg@…>
- Cc fg@… added
comment:10 Changed 7 years ago by anonymous
- Cc maze@… added
comment:11 Changed 7 years ago by Gunnar Wagenknecht <gunnar@…>
- Cc gunnar@… 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 Changed 7 years ago by eblot
I've been working on it. I hope to propose a working solution by the end of august or september '05
comment:14 Changed 6 years ago by anonymous
- Cc danbeck-trac@… added
comment:15 Changed 6 years ago by anonymous
- Cc danbeck-trac@… added; danbeck-trac@… removed
comment:16 Changed 6 years ago by eblot
I put a very basic implementation for LDAP group here:
http://anciens.enib.fr/trac/public/wiki/TracLdap
comment:17 Changed 6 years ago by eblot
LdapPlugin, featuring some kind of LDAP ACLs and group management, is now available on Trac Hacks.
comment:18 Changed 6 years ago by Myroslav Opyr <myroslav@…>
- Cc myroslav@… added
comment:19 Changed 6 years ago by eblot
- Milestone set to 0.9.1
- Resolution set to fixed
- Status changed from reopened to closed
- Summary changed from group management and LDAP to 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 Changed 6 years ago by anonymous
- Cc fg@… removed
comment:21 Changed 6 years ago by anonymous
- Cc gunnar@… removed
comment:22 follow-ups: ↓ 24 ↓ 25 Changed 5 years ago by anonymous
I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.
comment:23 Changed 6 months ago by lkraav <leho@…>
- Cc leho@… added
comment:24 in reply to: ↑ 22 Changed 3 months ago by anonymous
- Owner changed from jonas to anonymous
Replying to anonymous:
I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.
comment:25 in reply to: ↑ 22 Changed 3 months ago by kunjbhai@…
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 Changed 3 months ago by lkraav <leho@…>
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>.