Expanding on Jira’s LDAP Integration

April 8th, 2008 by Mike Leave a reply »

I recently had the opportunity to do a fresh installation of Atlassian Jira and was faced again with how to integrate Jira with LDAP (in this case Active Directory). Jira does support simple authentication integration with LDAP via OSUser but requires that each LDAP user exist in Jira first. So if an LDAP user needs access to Jira, I have to go to Jira and create a new corresponding user record with the same user id as LDAP (sAMAccountName in this case). Ideally I would like Jira to synchronize LDAP users based on some query filter.

Luckily Jira has a perfect way to address this “opportunity”: Services. Jira Services are asynchronous scheduled units of work configured to run within Jira. I created a new service using JNDI called LDAPUserSyncService that can periodically query LDAP user objects based on configurable filter and create new Jira users for new users it finds in LDAP. I also added the ability to put a subset of users in a group called “internal-users” for users whose email matched a particular domain.

Its pretty simple. When the service runs, it queries LDAP, iterates through each user checking if the user already exists in LDAP, if not it created the user and puts it in the jira-users group and then checks if the email address contains myco.com (fictitious domain for this example) and if so put the user in the internal group as well. I just realized as I’m writing this though that I didn’t do anything to handle paging or results if the number of users is greater than the max returned by LDAP. Oh well, maybe another time…

public class LDAPUserSyncService extends AbstractService {

    private static org.apache.log4j.Category log =
            org.apache.log4j.Category.getInstance(LDAPUserSyncService.class);

    public static String INITCTX = "com.sun.jndi.ldap.LdapCtxFactory";
    public static String GROUP_INTERNAL =  "Internal-Users";
    public static String LDAP_USER_ID_ATTRIBUTE = "sAMAccountName";

    public void run() {
        try {
            log.debug("Running com.myco.jira.service.LDAPUserSyncService");
            String LDAP_HOST = getProperty("LDAP_HOST");
            String LDAP_USER = getProperty("LDAP_USER");
            String LDAP_PASSWORD = getProperty("LDAP_PASSWORD");
            String LDAP_SEARCHBASE = getProperty("LDAP_SEARCHBASE");
            String LDAP_FILTER = getProperty("LDAP_FILTER");   

            Hashtable env = new Hashtable();
            env.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX);
            env.put(Context.PROVIDER_URL, LDAP_HOST);
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.SECURITY_PRINCIPAL, LDAP_USER);
            env.put(Context.SECURITY_CREDENTIALS, LDAP_PASSWORD);

            DirContext ctx = new InitialDirContext(env);

            SearchControls constraints = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);

            NamingEnumeration results = ctx.search(LDAP_SEARCHBASE, LDAP_FILTER, constraints);

            while(results != null && results.hasMore()) {
                SearchResult sr = (SearchResult) results.next();
                Attributes attrs = sr.getAttributes();
                log.debug("Processing " + attrs.get("dn"));

                // need sn, givenName, mail
                String first = (attrs.get("givenName") != null) ? (String) attrs.get("givenName").get() : null;
                String last = (attrs.get("sn") != null) ? (String) attrs.get("sn").get() : null;
                String mail = (attrs.get("mail") != null) ? (String) attrs.get("mail").get() : null;
                String userId = (attrs.get(LDAP_USER_ID_ATTRIBUTE) != null) ? (String) attrs.get(LDAP_USER_ID_ATTRIBUTE).get() : null;

                if(first != null && last != null && mail != null && userId != null) {

                    // check if user is in Jira
                    if(!userExists(userId.toLowerCase())) {

                        // create user, if a myco company user then add to internal group
                        if(createJiraUser(userId, first, last, mail) != null) {
                            if(mail.contains("myco.com"))
                                addUserGroup(userId, GROUP_INTERNAL);
                        } else {
                            log.error("LDAP User " + userId + " could not be created");
                        }
                    }
                }
            }
        } catch (NamingException nex) {
            log.error("Caught exception trying to Synch LDAP Users: " + nex.toString());
        } catch (ObjectConfigurationException oce) {
            log.error("Caught exception trying to Synch LDAP Users.  Configuration setup failed: " + oce.toString());
        }
    }

    private User createJiraUser(String userId, String fname, String lname, String email) {

        UserManager userMgr = UserManager.getInstance();
        User osUser = null;
        try {
            osUser = userMgr.createUser(userId.toLowerCase());
            osUser.setFullName(fname + " " + lname);
            osUser.setEmail(email);

            Group jiraUserGroup = userMgr.getGroup("jira-users");
            osUser.addToGroup(jiraUserGroup);
            osUser.store();
            log.debug("Successfully added LDAP user "+ userId);
        } catch (ImmutableException e) {
            log.error("Error creating User in Jira: " + userId, e);
        } catch (DuplicateEntityException e) {
            log.error("Error creating User in Jira: " + userId, e);
        } catch (EntityNotFoundException e) {
            log.error("Could not find group jira-users.  Error creating User in Jira: " + userId, e);
        }

        // to be sure the user was created, return the reloaded user from Jira
        return osUser;

    }

    private boolean userExists(String userId) {
        UserManager userMgr = UserManager.getInstance();
        boolean exists = false;
        try {
            if(userMgr.getUser(userId) != null)
                exists = true;
        } catch (EntityNotFoundException e) {
            exists = false;
        }

        return exists;
    }

    private void addUserGroup(String userId, String groupName){
        UserManager userMgr = UserManager.getInstance();

        try {
            User osUser = userMgr.getUser(userId);

            Group group = userMgr.getGroup(groupName);
            osUser.addToGroup(group);
            osUser.store();
            log.debug("Successfully added LDAP user " + userId + " to group: " + groupName);
        } catch (ImmutableException e) {
            log.error("Error adding User to acis-users group: " + userId, e);
        } catch (EntityNotFoundException e) {
            log.error("Could not find group acis-users.  Error adding User to acis-users group:  " + userId, e);
        }
    }

    public ObjectConfiguration getObjectConfiguration() throws ObjectConfigurationException {
        return getObjectConfiguration("LDAPUSERSYNCSERVICE", "com/myco/jira/service/LDAPUserSyncService.xml", null);
    }
}

To configure a service within Jira navigate to Services in the System section of the Administration interface:

Here I’ve added the service you can see the parameters that can be customized for the service:

The parameters are configured in an XML file that is referenced by the getObjectConfiguration() method in the service code above:

<debugservice id="LDAPUserSyncService">
    <description>
    </description>
    <properties>

        <property>
            <key>LDAP_HOST</key>
            <name>LDAP Host (format: ldap://myco.com:389)</name>
            <type>string</type>
        </property>
        <property>
            <key>LDAP_USER</key>
            <name>LDAP User DN</name>
            <type>string</type>
        </property>
                <property>
            <key>LDAP_PASSWORD</key>
            <name>LDAP Password)</name>
            <type>string</type>
        </property>
        <property>
            <key>LDAP_SEARCHBASE</key>
            <name>LDAP Search Base DN</name>
            <type>string</type>
        </property>
        <property>
            <key>LDAP_FILTER</key>
            <name>LDAP Search Filter</name>
            <type>string</type>
        </property>

    </properties>
</debugservice>

And that’s it. New users are automatically created in Jira within 30 minutes of being created in LDAP. Ooo Rah.

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

13 comments

  1. eduardo says:

    Great Tutorial! There is a lack of information about Jira development, I’m specially interested in writing plugins and extensions.

  2. sam says:

    Could you help me to solve compiling problem?
    I failed to compile the file you list above because of no reference to AbstractService.

    Cheers
    Sam

  3. Mike says:

    Sam, you need the atlassian-jira-3.xx.jar from the plugin development kit in your classpath. Here’s the path from the 3.11 version:

    jira-development-kit-3.12\lib\atlassian-jira\jars\atlassian-jira-3.11.jar
    com.atlassian.jira.service.AbstractService

    Mike

  4. Mike says:

    Whoops make the that 3.12 version. Looks like the 3.11 jar is packed in there though.

  5. Nabsim says:

    Hi there i have some problems with it.. where i put the code, and the other parameters to getObjectConfiguration not clear for me

  6. Vasya says:

    Any chance to get this plugin compiled? I spent 4 hours trying to compile it, but I’m completely unfamiliar with java development and tools. Running javac over file doesnt help, I just get 47 errors, tried to use mavin, but with no luck either..

  7. Sanchir says:

    hello, what is Search Attribute ?

  8. Siggi says:

    Sounds very interesting and helpful, having trouble with this LDAP integration.
    Could you please post the whole source. or at least give the import statements in a comment.
    My java knowledge is not exactly top notch, but I’ll be able to compile it if I get the dependencies correct :)

  9. Daniel says:

    Hi Mike,
    is there any download for a compiled plugin?

  10. Sascha says:

    Hi,

    could you explain wht to do for jira 3.13, please?
    I cannot compile this script without errors.
    (I’m using eclipse with the development kit)

    Thx for your help.

    Sascha

  11. Bryce Day says:

    Hello, great post – thanks.
    Thought you may also want to know that we have created a JIRA to LDAP Connector that allows full sync of JIRA users with multiple LDAP repositories.

    The following link for more information:
    http://www.catchlimited.com/index.php?page=catch-limited

    Bryce

  12. Ramesh says:

    Cache the JNDI context (ServiceLocator pattern) if not every invocation does the lookup no?

Leave a Reply

Powered by Web Design Company Plugins

Switch to our mobile site