Using LDAP Groups With Subversion's Authz File

Background

When building a Subversion server, people usually go for the setup that requires the least amount of administrative overhead. For many, especially in the enterprise where Active Directory and OpenLDAP rule, this means hooking up Apache to a directory server to authenticate users via LDAP. The reason for such a setup is that you can use the same credentials that log you into your computer, and other internal resources usually, to access Subversion. One less set of credentials for the user to remember and one less user data store for the administrator to maintain. Initially, this scenario is without any real disadvantage. But once you want to start using your groups defined in your directory with Subversion's authorization (authz) mechanism, you find one of the shortcomings of such a configuration.

The Problem

Subversion's authz architecture requires your group definitions to be defined within the authz file. Subversion's authz architecture is also unaware of third-party data stores for users/groups. This means that if you do not define your group within the authz file, Subversion will not know whether a user is a member of said group and will ultimately tell you that you do not have access to a resource. That being said, the current problem is that with this server configuration, either you cannot harness group models defined in your directory server or you have to manually synchronize your group models from your directory server to Subversion's authz file. There are many downsides to doing things this way:

  • It's time consuming
  • It's easy to forget that changes have been made and need to be mirrored
  • It's very easy to make mistakes when doing it yourself
  • ...

So while using LDAP for Subversion authentication is a dream, things are not so nice when it comes to reusing your group models for authz that are defined in your directory server.

The Solution

Well, it just so happens that I have a solution. One that is repeatable, loss-less and can be easily setup using the same information you used to configure Apache to authenticate your Subversion users. The solution is the "LDAP Groups to Subversion Authz Groups Bridge" script. (Note: This script, its license and a duplicate of this documentation are attached to the bottom of this article.)

The "LDAP Groups to Subversion Authz Groups Bridge" script is written in Python and, as mentioned before, does its work in a loss-less fashion. This means that while this script will take on the trouble of taking your groups models defined in your directory server and reproducing them in a Subversion authz file, the script will not prohibit you from creating group definitions within the authz file that are not defined in your directory server. Tired of all of the background? Ready to see the script in action? Let's go.

The Implementation

Implementing the "LDAP Groups to Subversion Authz Groups Bridge" is actually simple. If you've already configured Apache to authenticate your Subversion users, which we will not be covering, you can honestly copy/paste pieces of the Apache configuration into the script. Before we go through an example though, there are a few prerequisites that you need to have taken care of before the script will run:

Now that we have those things out of the way, let's look at the help output of the script, just to get our bearings:

  Usage: sync_ldap_groups_to_svn_authz.py [options]
 
  Options:
    -h, --help            show this help message and exit
    -d BIND_DN, --bind-dn=BIND_DN
                          The DN of the user to bind to the directory with
    -p BIND_PASSWORD, --bind-password=BIND_PASSWORD
                          The password for the user specified with the --bind-dn
    -l URL, --url=URL     The url (scheme://hostname:port) for the directory
                          server
    -b BASE_DN, --base-dn=BASE_DN
                          The DN at which to perform the recursive search
    -g GROUP_QUERY, --group-query=GROUP_QUERY
                          The query/filter used to identify group objects.
                          [Default: objectClass=group]
    -m GROUP_MEMBER_ATTRIBUTE, --group-member-attribute=GROUP_MEMBER_ATTRIBUTE
                          The attribute of the group object that stores the
                          group memberships.  [Default: member]
    -u USER_QUERY, --user-query=USER_QUERY
                          The query/filter used to identify user objects.
                          [Default: objectClass=user]
    -i USERID_ATTRIBUTE, --userid_attribute=USERID_ATTRIBUTE
                          The attribute of the user object that stores the
                          userid to be used in the authz file.  [Default: cn]
    -z AUTHZ_PATH, --authz-path=AUTHZ_PATH
                          The path to the authz file to update/create
    -q, --quiet           Suppress logging information

Anything that has a "[Default:" string in its corresponding documentation is "optional" and can usually be omitted when running against most directory servers. The things that are not optional are things that you can get from your Apache configuration. Now that we have our bearings, let's see an example.

Let's pretend that I have a directory structure like this:

  • Release Managers (CN=Release Managers,OU=Groups,DC=subversion,DC=thoughtspark,DC=org)
    • Release Manager One (CN=Release Manager One,OU=Users,DC=subversion,DC=thoughtspark,DC=org)
  • Developers (CN=Developers,OU=Groups,DC=subversion,DC=thoughtspark,DC=org)
    • Release Managers (CN=Release Managers,OU=Nested Groups,OU=Groups,DC=subversion,DC=thoughtspark,DC=org)
    • Developer One (CN=Developer One,OU=Users,DC=subversion,DC=thoughtspark,DC=org)
    • Developer Two (CN=Developer Two,OU=Users,DC=subversion,DC=thoughtspark,DC=org)
    • Developer Three (CN=Developer Three,OU=Users,DC=subversion,DC=thoughtspark,DC=org
  • Release Managers (CN=Release Managers,OU=Nested Groups,OU=Groups,DC=subversion,DC=thoughtspark,DC=org)
    • Release Manager Two (CN=Release Manager Two,OU=Users,DC=subversion,DC=thoughtspark,DC=org)
  • Administrators (CN=Administrators,CN=Roles,DC=subversion,DC=thoughtspark,DC=org)
    • Jeremy Whitlock (CN=Jeremy Whitlock,OU=Users,DC=subversion,DC=thoughtspark,DC=org)

(Yes...I have two groups with the same name but in different locations. This will help showcase how the "LDAP Groups to Subversion Authz Groups Bridge" handles this situation.) I now want to run the script:

  python sync_ldap_groups_to_svn_authz.py \
    -d "CN=Jeremy Whitlock,OU=Users,DC=subversion,DC=thoughtspark,DC=org" \
    -l "ldap://localhost:389" \
    -b "DC=subversion,DC=thoughtspark,DC=org" > svn_auth.txt

You'll notice I didn't specify a password. In this event, the script will prompt you, securely, for a password. After the script runs, as long as you didn't run with the "-q" flag, you should see output like this:

  Successfully bound to ldap://localhost:389
  4 groups found.

Okay, things ran smoothly. (If you notice any "[WARNING]" output, just look at the message along side the warning to see why.) So what does the file look like?

  [groups]
 
  ### Start generated content: LDAP Groups to Subversion Authz Groups Bridge (2009/01/19 23:17:07) ###
  ReleaseManagers = Release Manager One
  Developers = @ReleaseManagers1, Developer Three, Developer Two, Developer One
  ReleaseManagers1 = Release Manager Two
  Administrators = Jeremy Whitlock
 
  ################################################################################
  ###########   LDAP Groups to Subversion Authz Groups Bridge (Legend)  ##########
  ################################################################################
  ### ReleaseManagers = CN=Release Managers,OU=Groups,DC=subversion,DC=thoughtspark,DC=org
  ### Developers = CN=Developers,OU=Groups,DC=subversion,DC=thoughtspark,DC=org
  ### ReleaseManagers1 = CN=Release Managers,OU=Nested Groups,OU=Groups,DC=subversion,DC=thoughtspark,DC=org
  ### Administrators = CN=Administrators,CN=Roles,DC=subversion,DC=thoughtspark,DC=org
  ###############################################################################
 
  ### End generated content: LDAP Groups to Subversion Authz Groups Bridge ###

Here is a list of features, or things to realize, based on the output of this script:

  • The "[groups]" section is created but only if necessary
  • All generated content is within known "markers" for loss-less regeneration
  • The header tells you when the file was last synchronized
  • Nested groups, of any level, are supported
  • Groups with the same name, but different locations, are supported
  • An easy-to-read legend is created for reference reasons

Now that we've seen a very simple example, let's see one more example of how we might take an Apache configuration and turn that into a call to the "LDAP Groups to Subversion Authz Groups Bridge".

Below we have a snippet of the important parts of an Apache configuration using LDAP for Subversion authentication:

  ...
  # The distinguished name to bind to the directory server
  AuthLDAPBindDN "CN=Jeremy Whitlock,OU=Users,DC=subversion,DC=thoughtspark,DC=org"
 
  # The password for the user above
  AuthLDAPBindPassword "myP455w0rd"
 
  # The LDAP query url
  AuthLDAPURL "ldap://localhost:389/DC=subversion,DC=thoughtspark,DC=org?sAMAccountName?sub?(objectClass=user)"
  ...

With the above information, Apache could connect to my theoretical Active Directory server and look for user objects. The way Apache identifies user objects is by the "objectClass" attribute being set to "user". Once a user is found, the "sAMAccountName" attribute is queried to see if it matches the user logging in. Once the user is found, it is authenticated. That being said, let's parse this information and turn it into a successful call to the "LDAP Groups to Subversion Authz Groups Bridge":

  python sync_ldap_groups_to_svn_authz.py \
    -d "CN=Jeremy Whitlock,OU=Users,DC=subversion,DC=thoughtspark,DC=org" \
    -p "myP455w0rd" \
    -l "ldap://localhost:389" \
    -b "DC=subversion,DC=thoughtspark,DC=org" \
    -i "sAMAccountName" > svn_auth.txt

The new things you see in this call that differ from the first call is that we are now looking for the "sAMAccountName" attribute for the username instead of the "cn" attribute. You also see that we can pass the password as a command line argument.

Summary

So now that we've seen how the script is ran, what is necessary to get it running and even a complete example, it's now up to you to get this thing into your Subversion infrastructure. Immediate ideas are to automate this with your operating system's task scheduler and/or create a web interface to kick this script off on an as-needed basis. Regardless of how you use it, the "LDAP Groups to Subversion Authz Groups Bridge" should make the error-prone, tedious and easy-to-forget process of manually synchronizing your LDAP group models to Subversion's authz file much, much easier. It might even get so easy you forget that Subversion doesn't natively support LDAP groups.

Change History

  • 2009-01-22 - There was a bug when you didn't specify the -z flag and when the query returned no groups. Version 1.0.1 has been produced as a result.
  • 2009-04-15 - There was a bug introduced when reformatting the code that broke the nested groups support. Version 1.0.2 has been produced as a result.
AttachmentSize
sync_ldap_groups_to_svn_authz-1.0.2.tar.gz8.18 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Error... I'm getting this

Error...

I'm getting this odd sequence of errors. Any ideas?

python sync_ldap_groups_to_svn_authz.py
-d "CN=testuser,OU=Admin - Service Accounts,dc=mydomain,dc=com"
-p "Pa$$w0rd"
-l "ldap://dc01.mydomain.com:389"
-b "DC=mydomain,DC=com"

Successfully bound to ldap://dc01.mydomain.com:389...
Error performing search: {'info': '00000000: LdapErr: DSID-0C090627, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, vece', 'desc': 'Operations error'}

That is wild. I've not seen

That is wild. I've not seen it. The only thing I can think of is maybe you're pointing to a valid directory server but it cannot be searched or is not in control of the scope of the data you're searching. For example, this can happen when using Active Directory and you are doing a domain/forest wide search and you didn't use at Global Catalog server. If you are pointing to a Global Catalog, you need to use port 3268. Let me know if that solves anything.

Hello all, I found a

Hello all,

I found a particular issue with the line "os.rename(tmp_authz_path, authz_path)" where it gave an "OSError". The current system I run this script on has several different file systems and I believe that might have caused the issue.
I simply added a try... catch clause and used the shutil module and it worked fine.
here is the actual change I made.

try:
os.rename(tmp_authz_path, authz_path)
except OSError:
import shutil
shutil.copy(tmp_authz_path, authz_path)

Antoine

Thanks for the tip. I hope

Thanks for the tip. I hope the rest of the script is treating you fine.

Hi jeremy GREAT JOB! I thank

Hi jeremy
GREAT JOB!
I thank you very much for your posts on svn and ldap. Very helpful.

I made an italian post based on what you wrote. You can check it here
http://www.squadrainformatica.com/it/blog/svn_apache_acl_e_ldap_howto

This script seems to be very

This script seems to be very helpful
thank you ...

I just have one question ...
Would this script play friendly if I used ldaps://:636/ ??????????

I would think so. Might

I would think so. Might want to drop the ":636" from the url, since ldaps implies this port to be used.

mr whitlock, found an issue

mr whitlock,

found an issue with this script on a windows SVN/apache install, mod_authz_svn sometimes validates the userid returned from the ldap layer as full lowercase. causes an issue when AD is passing them up as mixed case. changing members.append(attrs[userid_attribute][0]) to be members.append(attrs[userid_attribute][0].lower()) resolved the issue.

might wanna make it a runtime option ;)

--Alex

Alex, Thanks for using

Alex,
Thanks for using the script and for reporting issues. I'm definitely going to take this into consideration but probably as a command line option, as you've suggested offline. The reason for this is this script returns exactly what is in the directory server, unchanged as it should. If you're getting mixed case from AD, the user's "real" username is mixed case and the user should be using the proper username. For someone to have to craft an authz that matches all variations of the users' names, it would be unwieldy. Regardless, I'm happy with your suggestion. Let me think a little to figure out the best way to add this enhancement.

Take care,

Jeremy

Hello. I did try this script

Hello. I did try this script with an openldap installation and it didn't work out. The error I get is:

$> python sync_ldap_groups_to_svn_authz.py -d"cn=manager,dc=some,dc=special,dc=location" -p"myPassword" -l"ldap://some.directory.server:389" -b"dc=some,dc=special,dc=location" -m"memberUid" -g"objectClass=posixGroup" -u"objectClass=posixAccount" -i"uid"
Successfully bound to ldap://localhost:389...
16 groups found.
[WARNING]: user1 object was not found...
[WARNING]: user2 object was not found...
[WARNING]: user3 object was not found...
(...)
[groups]
### Start generated content: LDAP Groups to Subversion Authz Groups Bridge (2009/04/13 11:25:52) ###
SomeGroup1 =
SomeGroup2 =
################################################################################
########### LDAP Groups to Subversion Authz Groups Bridge (Legend) ##########
################################################################################
### SomeGroup1 = cn=SomeGroup1,ou=Groups,dc=some,dc=special,dc=location
### SomeGroup2 = cn=SomeGroup2,ou=Groups,dc=some,dc=special,dc=location
################################################################################
### End generated content: LDAP Groups to Subversion Authz Groups Bridge ###

And, as you can see, my groups are "empty", but that is not the case...

The problem is probably related to the way that OpenLDAP group membership is available: the memberUid attribute is in the group and it is not a DN (it is just the uid of the user)

So this is why the script doesn't seem to work... As I don't really understand nor program python, it is quite difficult for me to solve... May you help me out?

Best
JP

This is the first report of

This is the first report of things not working for OpenLDAP. When I built this script, I looked at some OpenLDAP documentation and based on what I read, it should work. I actually just did some simple googling and found this:

http://www.zytrax.com/books/ldap/ch5/step2.html

It seems to show that the membership attribute of a group, in this case the 'member' attribute, does store the DN of the user. If it didn't, the member could be ambiguous so I don't see how any group membership attribute would do anything else other than storing the DN of the member. Just in case, I found another reference:

http://www.openldap.org/faq/data/cache/52.html

Are you sure you are using the proper attribute?

The links you found are just

The links you found are just what you were looking for. Underneath the hoods there is this reason:

(Taken from the URL: http://mds.mandriva.org/ticket/136, for exemple, but there are more like those)


The syntax you refer to (use full DNs as group memberships) is defined in RFC2307bis (not published). With that syntax, a group looks like this:
dn: cn=group,ou=group,dc=example,dc=com
cn: group
objectClass: groupOfNames
objectClass: posixGroup
member: uid=john,ou=people,dc=example,dc=com
gidNumber: 12345

There are a few problems in using this today: - smbldap-tools doesn't support it - posixGroup is defined in RFC2307 as a structural class, which conflicts with groupOfNames. So you really need to use the RFC2307bis schema, and not the RFC2307 one (also called nis.schema) - nss_ldap, when used in this mode, does nested group lookups, which can quickly explode if you have many members in a group (see http: //bugzilla.padl.com/show_bug.cgi?id=319): each member will trigger another ldap lookup.

Note that you shouldn't just change the contents of memberUid to be a DN: the memberUid attribute was not designed to hold DNs.

For us, I believe the biggest obstacle now is smbldap-tools.

And we use smbldap-tools at the company... And the RFC2307. It does seem we're right and we probably should have a "member" full DN and not a memberUID, but while these tools are not completely refactored, may we create some kind of "workaround" on your script to enable RCF compliance on Unix NIS Schemas?

Well, I didn't Google for

Well, I didn't Google for terms that make your case wrong. I just googled for "OpenLDAP group member attribute" and started finding many references that said that a group membership attribute held DNs. If there is an enhancement that can be made, I am definitely interested. I guess the problem is figuring out how to tell the script that it's not a DN being returned and there needs to be another query to return the object reference for the uid. Until then, I'll continue looking up LDAP Group RFCs to find the latest that isn't obsoleted and see if there are changes that need to be made.

Looking at more recent RFCs,

Looking at more recent RFCs, like RFC 4519, shows a DNs being used to identify group members. (http://tools.ietf.org/html/rfc4519#page-11) I think this is the latest RFC, I'm not sure. To my defense, I ran my script successfully, and so did a few clients, on Active Directory and LDAP for certain. I thought we had a Novell directory user but I cannot remember. We'll figure it out.

Thank you very much. I can

Thank you very much. I can see you really dug onto it and found me some additional information. Nonetheless, I'ld like to use your script with this current structure, as changing it is quite a challenge for now. It seems to me that it is even easier to do if you have just the memberUid defined, as the lookup to the ldap group will immediatly return the uid of the user, and the mapping is trivial, probably...

As I stated previously, my deepest problem now is just "python" code... If it were C or Java, ok... but python, I really don't understand (still).

Could you help me out there?

I can help. What do you

I can help. What do you want this to do?

There was a bug reported in

There was a bug reported in the support for nested groups today. I have fixed this, tested it and have created a new download.

Thank you so much for your

Thank you so much for your help!

The only difference from what you have now to what I really need is (and still keep what you have) is:
1. Define a switch on the command line for telling the script if the memberAttribute is a DN or not (--member-attribute-is-dn or -c) with values "yes/no"
2. Define a switch on the command line for telling the script how to find the user object based on the member attribute (--user-ldap-filter or -f) with an ldap filter syntax (e.g. : &((objectClass=user)(uid=%memberUid)) )

And then, dump as usually... Does this fit ok with your current script?

Best regards

Hi, I'm having the exact

Hi,

I'm having the exact same problem as JPereira. Members of a group are stored in memberUid fields. A field just contains a uid, not a complete DN.

dn: cn=somegrp,ou=Group,dc=dept,dc=organization,dc=be
objectClass: posixGroup
objectClass: top
cn: somegrp
gidNumber: 11107
memberUid: alice
memberUid: bob

dn: uid=alice,ou=People,dc=dept,dc=organization,dc=be
uid: alice
cn: alice doe
telephoneNumber: 27744
roomNumber: 2034A
givenName: Alice
sn: Doe
mail: alice@example.org
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
loginShell: /bin/bash
uidNumber: 10147
gidNumber: 11019

This is a very crude

This is a very crude solution for our problem, but of course now the other situation isn't supported.

You could either detect that 'member' is no DN, an switch between methods. Or, add parameters to switch the behaviour.

DIFF:
109d108
<
168,170c167
< if verbose:
< print("Query: DN: "+base_dn+", filter: "+"(&("+userid_attribute+"="+member+")("+user_query+"))")
< user = get_ldap_search_resultset(base_dn, "(&("+userid_attribute+"="+member+")("+user_query+"))", ldapobject)
---
> user = get_ldap_search_resultset(member, user_query, ldapobject)

JPeriera and stevenodb,

JPeriera and stevenodb,
I'm currently working on a way to allow such a thing. I'll take both of your suggestions into account and put together some solution. Sorry for this not working the first time but we all know supporting everyone and everything with a 1.0 is rarely achieved. ;)

Take care,

Jeremy

This script seems to work

This script seems to work great but I'm having one issue.. When I run the script its putting the generated groups info is being put at the end of the authz file. Even when I move the groups information around in the file, in subsequent runs the script moves the generated info to the end. This wouldn't be an issue except the authz parser seems to care where the groups info is, ie it has to be the first section of the file.

I had errors running the

I had errors running the script against our active directory installation because active directory only returns a maximum of 1000 results for a query and we had more entries. based in this mailing list entry:
http://mail.python.org/pipermail/python-list/2008-October/683968.html

I have written a patch:
--- sync_ldap_groups_to_svn_authz_orig.py lun jul 6 10:03:49 2009
+++ sync_ldap_groups_to_svn_authz.py lun jul 6 10:07:37 2009
@@ -26,6 +26,7 @@

try:
import ldap
+ from ldap.controls import SimplePagedResultsControl
except ImportError:
print("Unable to locate the 'ldap' module. Please install python-ldap. " \
"(http://python-ldap.sourceforge.net)")
@@ -132,16 +133,33 @@
def get_ldap_search_resultset(base_dn, group_query, ldapobject):
"""This function will return a query result set."""
result_set = []
- result_id = ldapobject.search(base_dn, ldap.SCOPE_SUBTREE, group_query)
-
+ page_size = 500
+ lc = SimplePagedResultsControl(ldap.LDAP_CONTROL_PAGE_OID,True,(page_size,''))
+ result_id = ldapobject.search_ext(base_dn, ldap.SCOPE_SUBTREE, group_query, serverctrls=[lc])
+
while 1:
- result_type, result_data = ldapobject.result(result_id, 0)
-
- if (result_type == ldap.RES_SEARCH_ENTRY):
- result_set.append(result_data)
- elif (result_type == ldap.RES_SEARCH_RESULT):
- break
-
+ while 1:
+ result_type, result_data, rmsgid, serverctrls = ldapobject.result3(result_id, 0)
+ if (result_type == ldap.RES_SEARCH_ENTRY):
+ result_set.append(result_data)
+ elif (result_type == ldap.RES_SEARCH_RESULT):
+ break
+
+ pctrls = [
+ c
+ for c in serverctrls
+ if c.controlType == ldap.LDAP_CONTROL_PAGE_OID
+ ]
+ if pctrls:
+ est, cookie = pctrls[0].controlValue
+ if cookie:
+ lc.controlValue = (page_size, cookie)
+ result_id = ldapobject.search_ext(base_dn, ldap.SCOPE_SUBTREE, group_query, serverctrls=[lc])
+ else:
+ break
+ else:
+ print "Warning: Server ignores RFC 2696 control."
+ break
return result_set

# get_ldap_search_resultset()

the script seems to be working now, please take into consideration to merge it

best regards

That sucks. I've never

That sucks. I've never noticed this before. I mean, I do know the groups are being put at the end but I didn't think the groups section had to be first section. Let me do some research and I'll update.

Excellent find. I'll review

Excellent find. I'll review the patch and I'll update when I've made a decision.

I've a Domain with far more

I've a Domain with far more groups than I want added to Subversion, so I added a -f/--filter regex option to your code. Here is the patch (I think it's pretty self-explanatory...):

53a54,56
> # This is the regex of groups that we wish to import
> group_filter_regex = None
>
123c126,128
<       groups.append(entry)
---
>       groupname = entry[1]['cn'][0]
>       if re.match(re_group_filter,groupname):
>         groups.append(entry)
392a398
>   global group_filter_regex
405a412
>   group_filter_regex = options.group_filter_regex
423a431,433
>   parser.add_option("-f", "--filter", dest="group_filter_regex",
>                     default=".*",
>                     help="The regex applied to groups to include")
466a477,478
>   if (group_filter_regex == None):
>     return False
492a505,507
>   if (group_filter_regex == None):
>     unset_properties += ['group_filter_regex']
>
517a533,540
>   # validate the group_filter_regex
>   global re_group_filter
>   try:
>     re_group_filter = re.compile(group_filter_regex,re.IGNORECASE)
>   except re.error, error_message:
>     print("'%s' is an invalid regular expression. Error: %s " %(group_filter_regex, error_message))
>     sys.exit(1)
>

Script Version in

Script Version in tarball?

Hi,

My first run at using this turned up some group related errors.
I noticed that the script in the 1.0.2 tarball has a 1.0.1 version.
Is it possible the script did not get updated in the tarball, or was the version not changed?

Thank you very much for working on this,

Dave

Hi, I found this script when

Hi,
I found this script when I was googling for subversion and LDAP group support. As there are no native support, I plan to use this script to keep the LDAP and svn groups in sync.
First of all, we use OpenLDAP and we have to keep our groups in the posixGroup objectclass, so we have the same problem as JPereira and stevenodb. Thanks to stevenodb, I patched the script and now I can get the groups from OpenLDAP.

But, running the script with the -z flag, to regenerate the authz file, throw the following exception:

Traceback (most recent call last):
  File "sync_ldap_groups_to_svn_authz.py", line 558, in ?
    main()
  File "sync_ldap_groups_to_svn_authz.py", line 553, in main
    print_group_model(groups, memberships)
  File "sync_ldap_groups_to_svn_authz.py", line 348, in print_group_model
    os.rename(tmp_authz_path, authz_path)
OSError: [Errno 18] Invalid cross-device link

Googling around for this problem (I'm not a python developer), I found that the problem seems to be the fact, that my /tmp are on another partition. If I change the os.rename from line 348 to shutil.move (and putting the import shutil at the beginning) the script is working nicely.

It would be nice, if you can take this issue into account, and fix it in the next release.

Keep up the good work,
Levi

hello. I tried used your

hello. I tried used your script with active directory. Seems like 3 times of 4 I'm getting

"Successfully bound to ldap://ldap.domain.com:389...
Error performing search: {'info': '00002024: LdapErr: DSID-0C060560, comment: No other operations may be performed on the connection while a bind is outstanding., data 0, v1771', 'desc': 'Server is busy'}"

And the server is not overloaded at all. Maybe a little wait should be added bettwen binding and querying?

Also, all the groups that have '-' in names end up missing it.

Thanks for the insight Levi.

Thanks for the insight Levi. I'll get cracking on a new release shortly.

I'll look into the things

I'll look into the things you mentioned Burn. I appreciate the feedback.

Bug on Windows: You needed

Bug on Windows:
You needed to insert os.close(tmp_fd) to have the script releasing the file pointer before doing os.remove as Win cannot handle that situation otherwise.

Regards

Thanks for the tip. I had

Thanks for the tip. I had fixed this already. Maybe I didn't push it live. I'll hopefully have an updated script soon.

Hi Jeremy, When I run the

Hi Jeremy,
When I run the script I get this error:

Traceback (most recent call last):
File "sync_ldap_groups_to_svn_authz.py", line 555, in ?
main()
File "sync_ldap_groups_to_svn_authz.py", line 545, in main
memberships = create_group_model(groups, ldapobject)[1]
File "sync_ldap_groups_to_svn_authz.py", line 186, in create_group_model
mg[0][0][0]))
TypeError: cannot concatenate 'str' and 'NoneType' objects

To fix the "cannot

To fix the "cannot concatenate 'str' and 'NoneType' objects" error, try replacing the line

members.append("GROUP:" + get_dict_key_from_value(groupmap,
                                                                mg[0][0][0]))

with the following code section. This isn't really a fix, but at least it continues running and simply warns you there was a problem and prints out the offending entry.

              try:
                members.append("GROUP:" + get_dict_key_from_value(groupmap,
                                                                mg[0][0][0]))
              except TypeError:
                  print("[WARNING]: %s error..." % mg[0])

Some ldap handling

Some ldap handling improvements. I would consistently get errors about outstanding lookups, etc when trying to run the script. By changing the ldapobject.bind to ldapobject.bind_s, these problems went away.

ldapobject.bind_s(bind_dn, bind_password)

Some other improvements:
In the "search_for_groups" function, change the return groups to the following:

groups.sort()
return groups

Additionally, you may want to clean up the ldap object's connection correctly which I have modified with the following.

  try:
    ldapobject = bind()
 
    try:
      groups = search_for_groups(ldapobject)
    except ldap.LDAPError, error_message:
      print("Error performing search: %s " % error_message)
      sys.exit(1)
 
    if groups and len(groups) == 0:
      print("There were no groups found with the group_query you supplied.")
      sys.exit(0)
 
    memberships = create_group_model(groups, ldapobject)[1]
 
    print_group_model(groups, memberships)
 
  except ldap.LDAPError, error_message:
    print("Could not connect to %s. Error: %s " % (url, error_message))
    sys.exit(1)
 
  finally:
    ldapobject.unbind()

Hi, nice script very

Hi, nice script very useful

but came across the following probs

1) does not support LDAPS over SSL, solution:

def bind():
"""This function will bind to the LDAP instance and return an ldapobject."""

+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,'/path/to/ca.crt')

2) It removes dashes (-) from the group names for some odd reason, solution: have not came across one yet

any suggestions?

thanks

sorry, forgot to attach the

sorry, forgot to attach the diff to the previous post solution 1. Still altho no idea about solution 2

--- sync_ldap_groups_to_svn_authz_orig.py 2009-04-15 19:39:23.000000000 +0100
+++ sync_ldap_groups_to_svn_authz.py 2009-10-29 18:39:48.000000000 +0000
@@ -71,6 +71,10 @@
# [Example: /opt/svn/svn_authz.txt]
authz_path = None

+# This is the fully-qualified path to the CA Cert file for LDAPS.
+# [Example: /etc/svn/ca.crt]
+ca_cert_path = None
+
################################################################################
# Runtime Options
################################################################################
@@ -96,6 +100,8 @@
def bind():
"""This function will bind to the LDAP instance and return an ldapobject."""

+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_cert_path)
+
ldapobject = ldap.initialize(url)

ldapobject.bind(bind_dn, bind_password)
@@ -389,6 +395,7 @@
global user_query
global userid_attribute
global authz_path
+ global ca_cert_path
global verbose

(options, args) = parser.parse_args(args=None, values=None)
@@ -402,6 +409,7 @@
user_query = options.user_query
userid_attribute = options.userid_attribute
authz_path = options.authz_path
+ ca_cert_path = options.ca_cert_path
verbose = options.verbose

# load_cli_properties()
@@ -411,6 +419,8 @@
usage = "usage: %prog [options]"
parser = OptionParser(usage=usage, description=application_description)

+ parser.add_option("-c", "--ca-cert", dest="ca_cert_path",
+ help="The location of the CA Certificate file")
parser.add_option("-d", "--bind-dn", dest="bind_dn",
help="The DN of the user to bind to the directory with")
parser.add_option("-p", "--bind-password", dest="bind_password",

Re problem 2: I found the

Re problem 2:

I found the solution.

--- sync_ldap_groups_to_svn_authz.py.original Mon Nov 2 13:11:25 2009
+++ sync_ldap_groups_to_svn_authz.py Mon Nov 2 13:34:30 2009
@@ -241,7 +241,7 @@

def simplify_name(name):
"""Creates an authz simple group name."""
- return re.sub("\W", "", name)
+ return name

# simplify_name()

probably the whole function could be completely nuked.

but encountered some problems with authz file formatting... it tends to remove the groups definitions and stick them in the END of the file... rather than in place they used to be previously, will investigate and let you know.

regards,
Mike

My plan is to digest all of

My plan is to digest all of the suggestions here and release a new version. The code is old and probably should be rewritten but I've just not had the time. (Old as in I don't usually use that Python style anymore.)

Please DO release a new

Please DO release a new version! Now's a great time!

Thanks for script, I

Thanks for script,
I couldn't run, and got this error
Successfully bound to ldap://dc01.mydomain.com:389...
Error performing search: {'info': '00000000: LdapErr: DSID-0C090627, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, vece', 'desc': 'Operations error'}

I found the solution here
http://stackoverflow.com/questions/140439/authenticating-against-active-...

namely, I added set_option in you def bind() and script ran smoothly
def bind():
ldapobject = ldap.initialize(url)
ldapobject.set_option(ldap.OPT_REFERRALS, 0)
ldapobject.bind(bind_dn, bind_password)