= LDAP extensions =
[[PageOutline(1-3)]]
== Abstract ==
LDAP support with group management has been added as a Trac extension. This
extension enables the use of existing LDAP groups to grant permissions rather than
defining permissions for every single user on the system. The latest release also
permits storage of permissions (both users and groups permissions) in the LDAP
directory itself rather than in the SQL backend.
The original proposal for LDAP ACL is documented under ticket
Trac#535 on the official web site.
This plugin follows the same license as Trac.
== Requirements ==
This plugin works with
* Trac 0.10, for the 0.5.x series
* Trac 0.11, for the 0.6.x series
Although initial releases of the plugin (up to '''0.2.2''') have been written against Trac 0.9, they lack many of the latest plugin features and should be considered as ''experimental'' at best.
You need the Python LDAP module. It can be retrieved from
python-ldap.[[BR]] LdapPlugin has been
tested on a Debian Linux Sarge/Sid (2.4.x and 2.6.x) server, a Windows XP SP2 workstation,
as well as on !MacBookPro OS 10.4.8, all of them running Python 2.4 with Trac 'development' releases.
To use the egg file you need to have
setuptools, version 0.6+
installed.[[BR]]Please refer to the
TracPlugins page for
information about plugin installation.
==== Important note ====
You need to grab a recent version of Trac from the trunk to make the Ldap permission store extension work as expected.[[BR]]
As the trunk API may vary without notice, the plugin may be broken if you run it with a different release.
== Download ==
* Source code is available from http://trac-hacks.org/svn/ldapplugin.
* You can also find unit tests at the same location - under the `tests` directory -, which may help you deploy the plugin.
== Installation ==
* Build the ''egg'' file following the plugin packaging instructions
* Copy the `dist/LdapPlugin-0.y.z-py2.n.egg` file in your ''plugins'' project directory.
== Authentication ==
LdapPlugin does '''not''' perform authentication: Apache2 does, through the HTTP
protocol, as with any other Trac installation. Same authentication is available through IIS if you disable anonymous user and enable Integrated Windows Authentication on your site.[[BR]]
LdapPlugin retrieves the groups to which the authenticated user belongs and
checks the TracPermissions
against these groups, along with the regular permissions for the user.
You probably want to use Apache2 LDAP authentication as well.[[BR]]
This topic is out of scope of this document but you may find useful information
on the official Apache2 mod_ldap
web site.
=== Apache 2.0 ===
Here is an example of a typical LDAP section of an Apache2.0 configuration file:
{{{
PythonOption TracEnv "/local/var/trac/project"
PythonOption TracUriRoot "/trac/project"
AuthType Basic
AuthName "Project"
Order Allow,Deny
Allow from All
AuthLDAPURL "ldap://localhost:389/dc=example,dc=org?uid"
Require group cn=tracusers,dc=example,dc=org
}}}
=== Apache 2.2 ===
Since the mod_auth_ldap module has been superseded by the mod_authnz_ldap module for Apache 2.2, the configuration also needs a little tweaking. The above example would now look like:
{{{
PythonOption TracEnv "/local/var/trac/project"
PythonOption TracUriRoot "/trac/project"
AuthType Basic
AuthName "Project"
AuthBasicProvider ldap
Order Allow,Deny
Allow from All
AuthLDAPURL "ldap://localhost:389/dc=example,dc=org?uid"
AuthzLDAPAuthoritative on
Require ldap-group cn=tracusers,dc=example,dc=org
}}}
Note that if you just use "Require valid-user" (to allow everybody with a valid LDAP login to access trac) you must set "AuthzLDAPAuthoritative off" according to the Apache documentation.
== Configuration ==
You need to customize the `trac.ini` file of your project, then[[BR]]
1. Optionally add the path to your plugin directory.
1. Enable `ldapplugin` in `[components]` section, so that the Trac engine loads and uses this extension.
1. Create a new section `[ldap]`.
1. Configure the LDAP directives to fit your LDAP server configuration.
To enable LdapPlugin you must add this line to the `[components]` section:
{{{
[components]
ldapplugin.* = enabled
}}}
The `[ldap]` section may contain the following options (which are presented down here with their default values)
{{{
[ldap]
# enable LDAP support for Trac
enable = false
# enable TLS support
use_tls = false
# LDAP directory host
host = localhost
# LDAP directory port (default port for LDAPS/TLS connections is 636)
port = 389
# BaseDN
basedn = dc=example,dc=com
# Relative DN for users (defaults to none)
user_rdn =
# Relative DN for group of names (defaults to none)
group_rdn =
# objectclass for groups
groupname = groupofnames
# dn entry in a groupname
groupmember = member
# attribute name for a group
groupattr = cn
# attribute name for a user
uidattr = uid
# attribute name to store trac permission
permattr = tracperm
# filter to search for dn with 'permattr' attributes
permfilter = objectclass=*
# 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 =
# 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 = true
# whether a group member contains the full dn or a simple uid
groupmemberisdn = true
}}}
You probably want to define at least `enable=true` and the `basedn`[[BR]]
The meaning of the options are pretty straightforward for LDAP administrators.
A typical setup for group resolution would look like this:
{{{
[ldap]
enable = true
basedn = dc=example,dc=org
}}}
A typical setup for all LDAP support (group resolution and permission store)
would look like this:
{{{
[ldap]
enable = true
basedn = dc=example,dc=org
user_rdn = ou=people
group_rdn = ou=groups
store_bind = true
bind_user = cn=tracadmin,dc=example,dc=org
bind_passwd = mypasswd
}}}
If you get an error message like this:
{{{
File "build/bdist.linux-x86_64/egg/ldapplugin/api.py", line 106, in get_permission_groups
TypeError: __init__() keywords must be strings
}}}
you may have to patch the LdapPlugin source, see:
https://trac-hacks.org/ticket/6183
==== Note about `group_rdn` and `user_rdn` ====
Starting from release '''v0.4.0''', `group_basedn` and `user_basedn` options have been superseeded with `group_rdn` and `user_rdn`.[[BR]]
The new settings define the relative DNs respectively for the group and the user subtree, based on the common `basedn` trunk. For example:
* `ou=people,dc=example,dc=org` would require the following settings:
{{{
basedn = dc=example,dc=org
user_rdn = ou=people
}}}
* `ou=groups,dc=example,dc=org` would require the following settings:
{{{
basedn = dc=example,dc=org
group_rdn = ou=groups
}}}
== Authenticated LDAP connections ==
If the server requires an authenticated connection to retrieve group permissions,
you want to set `group_bind = true` in the `[ldap]` section and define
the credentials as follows:
{{{
[ldap]
group_bind = true
bind_user = joeuser
bind_passwd = joepassword
}}}
If the server requires an authenticated connection to modify group permissions,
you want to set `store_bind = true` in the `[ldap]` section and define
the credentials as follows:
{{{
[ldap]
store_bind = true
bind_user = joeuser
bind_passwd = joepassword
}}}
''Note'': Most LDAP servers require authenticated bind to perform any kind of
modifications. Anyway, it would be a bad idea to allow modifications from
anybody.
== Ldap permission store ==
If you wish to use the LDAP permission store feature, you need to tell Trac to
use the LDAP extension rather than the internal default permission store which
relies on the SQL backend.
Note that if you decide to store Trac permissions as a ''new'' LDAP attribute,
you will need LDAP schema management rights. Furthermore, some LDAP servers
(e.g. Active Directory) might not allow the deletion of attribute definitions.
To achieve this setting, add the following line to
the main `[trac]` section of your `trac.ini` configuration file:
{{{
[trac]
# ...
permission_store = LdapPermissionStore
}}}
You also need to enable `LdapPermissionStore` for LdapPlugin by adding:
{{{
[components]
ldapplugin.* = enabled
}}}
The extension differentiates '''group permissions''' from '''user permission'''.
This permits to use distinct objectclasses in the LDAP directory, to store
permission. For example thanks to the `groupattr` and `uidattr`
attributes, you can define group permission to LDAP entries such as
{{{
dn: cn=managers,dc=example,dc=org
objectclass: groupofnames
objectclass: trac
member: uid=chandler,dc=example,dc=org
member: uid=joey,dc=example,dc=org
tracperm: WIKI_ADMIN
tracperm: TICKET_ADMIN
}}}
and define user permission to LDAP entries such as
{{{
dn: uid=courtney,dc=example,dc=org
objectclass: user
objectclass: trac
tracperm: TICKET_VIEW
tracperm: REPORT_CREATE
tracperm: REPORT_VIEW
}}}
It is worth noting that the '''dn''' used for groups and for users may be
different, which should make things easier to add
TracPermissions into your existing LDAP directory. For addition info read these [http://webspacehosting.com web hosting faqs].
To differentiate a group name from a user name in `trac-admin`, prefix the group
name with the `@` characters. This syntax has been borrowed from Samba
and many other software dealing with group management.[[BR]]
One would grant the above permissions using the following `trac-admin` commands
{{{
permission add @managers WIKI_ADMIN
permission add @managers TICKET_ADMIN
permission add courtney TICKET_VIEW
permission add courtney REPORT_CREATE
permission add courtney REPORT_VIEW
}}}
Please note that the LDAP permission store '''never''' attemps to create a new
entry in the LDAP directory. To grant (or revoke) permissions to/from the LDAP
directory, the targetted LDAP entry should exist in the directory and the
attribute defined by the `permattr` option should be writtable for the
`store_user` user.
Please have a look at the LdapPluginTests page to get an overview of LDAP ACLs
(access control lists) that manages LDAP operations on a directory.
== Permissions ==
Once LDAP support has been activated, you can use `trac-admin` as usual to
define TracPermissions.[[BR]]
However, you can now use the existing groups defined in your LDAP directory to
assign permissions.
A LDAP group should start with the '`@`' character
Example:
{{{
Trac [/var/local/db/trac/public]> permission list
User Action
-------------------------------
@administrators TRAC_ADMIN
@betatesters WIKI_CREATE
@betatesters WIKI_MODIFY
eblot TRAC_ADMIN
anonymous BROWSER_VIEW
anonymous CHANGESET_VIEW
anonymous FILE_VIEW
anonymous LOG_VIEW
anonymous SEARCH_VIEW
anonymous TIMELINE_VIEW
anonymous WIKI_VIEW
}}}
Here, people who are declared in the 'administrator' LDAP group have the
`TRAC_ADMIN` permission, and people who are declared in the 'betatesters'
LDAP group have the `WIKI_CREATE` and `WIKI_MODIFY` permission.
You can obviously still use permissions for regular user such as ''eblot'' in
the example above.
'''Note''': Please remember that ''anonymous'' and ''authenticated'' are special users
but are considered by the permission backend just like any other regular user.[[BR]]
This means that you need to add both these special users in your LDAP directory
if you wish to assign permission to these joker entries.
The directory configuration proposed in
the test page may give you some hints about how to setup
your LDAP directory.
==== Group of names ====
There are usually two flavours to manage group permissions in LDAP:
1. The group contains a list of fully qualified dns
{{{
dn: cn=fakedoctors,ou=groups,dc=example,dc=org
cn: fakedoctors
objectClass: groupOfNames
objectClass: top
member: uid=meredith,ou=groups,dc=example,dc=org
member: uid=georges,ou=groups,dc=example,dc=org
member: uid=izzie,ou=groups,dc=example,dc=org
}}}
With such an environment, your [ldap] section would contain
{{{
[ldap]
...
group_rdn = ou=groups
groupmemberisdn = true
groupname = groupofnames
groupmember = member
}}}
1. The group contains a list of simple uids
{{{
dn: cn=fakedoctors,ou=groups,dc=example,dc=org
cn: fakedoctors
objectClass: posixGroup
objectClass: top
memberUid: uid=meredith
memberUid: uid=georges
memberUid: uid=izzie
}}}
With such an environment, your [ldap] section would contain
{{{
[ldap]
...
group_rdn = ou=groups
groupmemberisdn = false
groupname = posixgroup
groupmember = memberUid
}}}
Beware, if you use this second scheme, you should have these lines in your apache configuration:
{{{
...
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
...
}}}
=== Global vs. Environment permissions ===
Starting from release '''v0.3.0''', permissions are not defined globally (unless `global_perms` is set in the environment configuration file), but on per-environment basis.
With environment-wide permissions, it is now possible to define distinct permissions for each Trac environment (as long as their name differ) even if they access the same LDAP directory.[[BR]]
The Trac LDAP permission attribute value are prefixed with the environment name.[[BR]]
Using the previous example, assuming the environment name is named "test", permission attributes would become:
{{{
dn: uid=courtney,dc=example,dc=org
objectclass: user
objectclass: trac
tracperm: test:TICKET_VIEW
tracperm: test:REPORT_CREATE
tracperm: test:REPORT_VIEW
}}}
It is still possible to use global permissions by setting in the `[ldap]` section of the environment configuration file:
{{{
global_perms = true
}}}
When a directory contains global permission directives, those permissions apply on every Trac environment accessing the LDAP directory, whichever the `global_perms` value. However, permissions are always created using the current environment permission setting.
From the administrative point of view (`trac-admin`, WebAdmin, ...), there are no changes: permission are defined and retrieved as usual.
''Note:'' The environment ''name'' is based on the root directory of the Trac environment. This means that if you use different environment with the same name, such as:
`/var/local/trac/test` and `/var/db/test`, they are both named "test" and share the same permissions. This is a known limitation of the current implementation.
== Group management ==
Starting from release '''v0.4.1''', the LdapPlugin permission store offers two ways to store group membership:
1. Permission-based management (default setting):[[BR]]
In this configuration, the plugin mimics the original Trac membership management, but does not follow the LDAP way: group membership is defined as permission actions, which leads to manage permissions concurrently from the permission actions and the existing LDAP groups
1. Ldap group management (recommended settings):[[BR]]
In this configuration, the plugin only uses the LDAP groups to manage group membership. The plugin adds or removes group members from existing LDAP groups[[BR]]
==== Activation ====
The new group management scheme can be activated using the `manage_groups` option.
==== Example ====
The following permission command
{{{
permission add eblot @developers
}}}
would lead to a very modification in the LDAP directory
1. The ''permission-based'' setting would add a `tracperm` attribute to the user entry
{{{
# eblot, people, example.org
dn: uid=eblot,ou=people,dc=example.org
objectClass: tracuser
tracperm: @developers
...
}}}
1. The ''LDAP group'' setting would add a new `member` attribute to the group entry
{{{
# developers, groups, example.org
dn: cn=developers,ou=groups,dc=example.org
objectClass: groupOfNames
objectClass: tracgroup
member: uid=eblot,ou=people,dc=example.org
...
}}}
==== Important notes ====
1. The LDAP plugin is not able to create new groups or new users from scratch. Users and groups must already exist in the LDAP directory. It would be difficult to create a new LDAP group or a new LDAP user from Trac, as the creation of a LDAP resource usually requires properties which are not made available to the LDAP plugin.[[BR]]
The above point means that the Trac administrator should probably creates the users and the groups from outside the Trac administration console (or [trac:wiki:WebAdmin WebAdmin]). LdapPlugin is designed to integrate Trac with an existing LDAP directory, not to manage the directory.
1. Default LDAP group policy usually requires that each group contains at least one member. If the administrator tries to remove the last member of a LDAP group, the LdapPlugin may refuse to perform this action (depending on the LDAP server setup).
1. Note that LDAP group management only deals with explicit groups, ''i.e.'' any word that starts with a `@` character. You can therefore mix aliases and LDAP directory groups:
* {{{permission add eblot devteam}}} is a group alias, managed as any Trac permission
* {{{permission add devteam @developers}}} is managed as a LDAP directory group (if `manage_groups` option is enabled)
== Known limitations ==
* Only LDAP v3 protocol is supported. This extension may work with v2 protocol
as well, if the v3 specifier is removed from the code.
* Several assumptions made by the plugin proved to be unreliable in at leave one Active Directory based implementation. #6268 contains fixes to work better with AD in cases where the Common Name is not the same as the sAMAccountName.
== !ToDo list ==
* Add user detail support so that the full name and email address are
retrieved from the LDAP server. It would require a new extension point in
Trac engine, which might be called `IUserDirectory` (not before Trac 0.11 at best) - Note: A patch on #6268 implements this. It's a bit of a kludge, but it's been working without issue thus far.
* There's probably a lot of room for improvement (and debugging) ;-)
== Testing ==
The LdapPluginTests page gives some hints about how to test the Ldap extension for
Trac
== History ==
* '''v0.0''': First attempt to write a LDAP bridge for Trac based on Trac 0.8, which required some hacks into the Trac engine.
* '''v0.1''': A new implementation has started on September, 1st '05, to profit from the new TracPlugins module architecture introduced in Trac 0.9-pre.[[BR]] This implementation should bring the following improvements:
* includes a cache to dramatically reduce LDAP requests
* better handling of LDAP errors[[BR]]This extension works with Trac 0.9-pre1 and requires the setuptools, version 0.5a13
* '''v0.2''': This new release fixes up a couple of bugs and works with Trac 0.9-pre2. It requires the [http://peak.telecommunity.com/DevCenter/setuptools setuptools], version 0.6+.[[BR]]It introduces support for LDAP permission store: TracPermissions can now be stored into the LDAP directory, rather than in the SQL backend.[[BR]]Each feature (LDAP as a provider of group permissions, LDAP as a permission store) are independent and can be enabled or disabled on demand.
* '''v0.2.1''': Bug fixing
* '''v0.2.2''': Introduce support for disting DN for users and groups (implemented suggestion described in #75)
* '''v0.2.3''': Update to support the new boolean parsing introduced in the official Trac trunk (requires Trac 0.10)
* '''v0.3.0''': Introduce per-environment permissions: permissions are defined to the current environment and do not overlap with other Trac environments using the same LDAP directory, unless the `global_perms` configuration parameters is set.
* '''v0.4.0''': Major rewrite of the LdapPlugin to support Trac trunk [trac:changeset:3419 3419], including better support for groups (user dns may be part of a different subtree than group dns, such as `ou=people` vs. `ou=groups`), improved cache management, as well as many bug fixes and code clean up.
* '''v0.4.1''': Introduce a new feature: group management is done as addition and removal to the LDAP groups of names: instead of storing groups as trac permissions (as the default permission store does), the plugin is not able to add and remove members to the LDAP group of names.
* '''v0.4.2''': Fix up an important issue with the management of the caches. The plugin has also been tested with the WebAdmin plugin.
* '''v0.4.3''': Fix up two issues with authentication (an invalid user identifier was sent to the LDAP server)
* '''v0.4.4''': Enable support for posix groups (and group members w/o distinguish name)
* '''v0.5.0''': Add basic support for LDAPS/TLS connections
* '''v0.5.1''': Update the Egg configuration file and the author contact details
* '''v0.6.0''': Support for Trac 0.11, thanks to judok
== Author/Contributors ==
'''Author:''' eblot [[BR]]
'''Contributors:''' wichert#wiggy.net, nguyen.antoine#wanadoo.fr
[[TagIt(eblot,0.11,plugin)]]