Monday, November 24, 2008

Atlassian Crowd Custom Directory

Today I chose to share with you my experience with implementing custom directory connector to Atlassian Crowd. There is a rather straight forward interface defined on Atlassian site which is not really hard to implement. What I would like to write about is a small number of tricks that helped me to achieve the desired results.
By the way, if you take a look at the Atlassian documentation you will notice that implementation among others should extend DirectoryEntity which is not completely true, for me it was more than enough to implement the RemoteDirectory interface.

No roles

Although notion of roles existed in my company's directory service still they were Windows specific and completely irrelevant to Atlassian products. I needed simple stub implementations of all the role related methods that would not break Crowd's work flow.

It was a good idea to return an empty list on findRoleMemberships(String principalName) method invocation, as throwing an exception would result in exception each time one would try to get principal's information on Crowd's site. Another method searchRoles(SearchContext context) was also better off returning an empty list instead of throwing an exception.

Legacy data

We had been using JIRA, Confluence, and Bamboo for several years and naturally user and group information hadn't been synchronized with the central directory. Our original intention was to integrate Atlassian tools as smoothly as possible, so for every tool I used a stack of two directories one of which was always a snapshot of the user information at the integration stage. Crowd was clever enough to merge data from both of them and it worked especially well for the users that chose same log-in names as in the company's directory, their group membership from the both directories was merged.
Note: order in which directories are listed actually matters, to use passwords from the central directory I had to put the custom directory first in the list.

Read-only

According to my company's safety rules I had to implement a read-only connector. So I chose to throw the UnsupportedOperationException exception when user attempted to update any information concerning principal, group or role. Crowd behaved very well whenever the exception was thrown.

Modifiable internal directory

I also wanted to allow administrators, from JIRA for example, to modify information in Crowd's internal directory. So my policy was following, throw ObjectNotFoundException exception if user and/or group didn't exist in the central directory, methods concerned were: addPrincipalToGroup, removeGroup, removeGroup, removePrincipalFromGroup, updateGroup, updatePrincipal, updatePrincipalCredential. In the other case methods defaulted to throwing UnsupportedOperationException exception. All this led to the next work flow (updateGroup is taken as an example):
  • Invocation of updateGroup of the custom directory (CD).
  • Group doesn't exist in CD, throw the ObjectNotFoundException.
  • Crowd proceeds to the next directory, which is the Internal Directory (ID).
  • ID processes the request graciously.
To achieve the aforementioned functionality with groups I simply called this.findGroupByName(groupName) and if it didn't throw an exception the method threw the UnsupportedOperationException. To check principal existence it was enough to call this.findPrincipalByName(name).

Here's an example:

public void addPrincipalToGroup(String name, String groupName)
 throws ObjectNotFoundException
{
 this.findGroupByName(unsubscribedGroup);
 throw new UnsupportedOperationException(CANNOT_EDIT_DIRECTORY);
}
In contrary to the given example if the group actually existed in the central directory UnsupportedOperationException was thrown which forced Crowd to stop processing the request. This perfectly corresponded to my wishes.

Conclusion

As the result I managed to keep the existing data in editable state merged with the central directory.
What I have described in this post doesn't go outside of scope of abstract implementation of RemoteDirectory which I called ReadOnlyRemoteDirectory. Someday I will describe another trick that helped me minimize the group retrieval delay.

5 comments:

Anonymous said...

Hi Ivan,

Very nice writeup!

One thing: are you aware of the ability to change directory permissions on a per-application basis? You may be able to achieve many of the same goals without having to write code. Crowd Documentation.

Unknown said...

Hello Doflynn,

Thank you for the comment.

You're right, it definitely helps solve the read-only problem. But, I didn't want a default behavior from Crowd, my policy was to allow editing users and groups as long as they didn't exist in the external directory.

The policy may not be the best, but that is what I wanted back then. Now we're actually thinking of mirroring the external directory into Crowd's internal one, as it would help us with the issue raising when people leave the organization and their credentials are removed.

I'll post it here when I've done it.

Anonymous said...

I do't suppose you could share the full version of your code for the ReadOnlyRemoteDirectory jar?

Unknown said...

I'm not sure if I can do this. Anyway, Atlassian changed the interfaces in the new version of Crowd.

Imran Aziz said...

Hello Ivan, thanks for the wonderful post, I am looking to implement a read only custom directory connector for our company, however finding it hard to get a good working code, will it be possible to either send a sample code from your connector to me or attach it to this post ? Thanks a lot