Edgewall Software

Ticket #535 (closed enhancement: fixed)

Opened 4 years ago

Last modified 22 months ago

Group management and LDAP

Reported by: gvincent Owned by: jonas
Priority: normal Milestone: 0.9.1
Component: general Version: devel
Severity: normal Keywords: LDAP ACL group
Cc: maze@…, techref@…, danbeck-trac@…, myroslav@…

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

Changed 4 years ago by dju`

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

Changed 4 years ago by daniel

  • milestone changed from 0.8 to 2.0

Changed 4 years ago by daniel

  • status changed from new to closed
  • resolution set to wontfix

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.

Changed 4 years ago by anonymous

  • status changed from closed to reopened
  • resolution wontfix deleted
  • milestone changed from 2.0 to 0.8

reopening

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

Changed 4 years ago by cmlenz

  • keywords acces removed
  • severity changed from major to enhancement
  • milestone 0.8 deleted

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.)

Changed 4 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)

Changed 4 years ago by anonymous

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

Changed 4 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. :)

Changed 3 years ago by Florent Guillaume <fg@…>

  • cc fg@… added

Changed 3 years ago by anonymous

  • cc maze@… added

Changed 3 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.

Changed 3 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

Changed 3 years ago by techref@…

  • cc techref@… added

also interested in updates

Changed 3 years ago by anonymous

  • cc danbeck-trac@… added

Changed 3 years ago by anonymous

  • cc danbeck-trac@… added; danbeck-trac@… removed

Changed 3 years ago by eblot

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

Changed 3 years ago by eblot

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

Changed 3 years ago by Myroslav Opyr <myroslav@…>

  • cc myroslav@… added

Changed 3 years ago by eblot

  • status changed from reopened to closed
  • summary changed from group management and LDAP to Group management and LDAP
  • resolution set to fixed
  • milestone set to 0.9.1

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.

Changed 3 years ago by anonymous

  • cc fg@… removed

Changed 2 years ago by anonymous

  • cc gunnar@… removed

Changed 22 months ago by anonymous

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

Add/Change #535 (Group management and LDAP)

Author



Change Properties
<Author field>
Action
as closed
Next status will be 'reopened'
 
Note: See TracTickets for help on using tickets.