Edgewall Software
Modify

Opened 20 years ago

Closed 19 years ago

Last modified 13 years ago

#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 dju`, 20 years ago

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

comment:2 by daniel, 20 years ago

Milestone: 0.82.0

comment:3 by daniel, 20 years ago

Resolution: wontfix
Status: newclosed

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:

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 anonymous, 20 years ago

Milestone: 2.00.8
Resolution: wontfix
Status: closedreopened

reopening

http://lists.edgewall.com/archive/trac/msg00349.html I guess the issue here is authenticating groups not useres.

comment:5 by Christopher Lenz, 20 years ago

Keywords: acces removed
Milestone: 0.8
Severity: majorenhancement

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 eblotml@…, 20 years ago

Version: 0.7.1devel

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 by anonymous, 20 years ago

Updated code for 0.9 pre (1366) is available from here: http://anciens.enib.fr/trac/public/wiki/TracLdap

comment:8 by anonymous, 20 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 Florent Guillaume <fg@…>, 20 years ago

Cc: fg@… added

comment:10 by anonymous, 19 years ago

Cc: maze@… added

comment:11 by Gunnar Wagenknecht <gunnar@…>, 19 years ago

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 by Emmanuel Blot, 19 years ago

I've been working on it. I hope to propose a working solution by the end of august or september '05

comment:13 by techref@…, 19 years ago

Cc: techref@… added

also interested in updates

comment:14 by anonymous, 19 years ago

Cc: danbeck-trac@… added

comment:15 by anonymous, 19 years ago

Cc: danbeck-trac@… added; danbeck-trac@… removed

comment:16 by Emmanuel Blot, 19 years ago

I put a very basic implementation for LDAP group here:
http://anciens.enib.fr/trac/public/wiki/TracLdap

comment:17 by Emmanuel Blot, 19 years ago

LdapPlugin, featuring some kind of LDAP ACLs and group management, is now available on Trac Hacks.

comment:18 by Myroslav Opyr <myroslav@…>, 19 years ago

Cc: myroslav@… added

comment:19 by Emmanuel Blot, 19 years ago

Milestone: 0.9.1
Resolution: fixed
Status: reopenedclosed
Summary: group management and LDAPGroup 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 anonymous, 19 years ago

Cc: fg@… removed

comment:21 by anonymous, 18 years ago

Cc: gunnar@… removed

comment:22 by anonymous, 18 years ago

I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.

comment:23 by lkraav <leho@…>, 13 years ago

Cc: leho@… added

in reply to:  22 comment:24 by anonymous, 13 years ago

Owner: changed from Jonas Borgström to anonymous

Replying to anonymous:

I think having built-in LDAP authentication for Standalone 'tracd' was a good idea.

in reply to:  22 comment:25 by kunjbhai@…, 13 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 lkraav <leho@…>, 13 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.

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain anonymous.
The resolution will be deleted. Next status will be 'reopened'.
to The owner will be changed from anonymous to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.