Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Invoke embedded-crowd login

Craig Solinski December 6, 2011

Hi,

Our SSO code is minimal for Confluence and works perfect in all versions previous to 3.5x.

Basically it's only a few of lines of code, with only 1 line that calls a method in ConfluenceAuthenticator class:
{code}
public class ConfluenceCasAuthenticator extends ConfluenceAuthenticator {
... Code to get a CAS ticket.
Principal user = super.getUser(validation.getUserName()); # call Atlassian's ConfluenceAuthenticator passing username to establish authenticated user.
... Code to add user to confluence-users group if not already a member (alive for 3.4.9, but commented out for 3.5.13 since we expected UD directive to do it.)
request.getSession().setAttribute(LOGGED_IN_KEY, user);
request.getSession().setAttribute(LOGGED_OUT_KEY, null);
return user;
{code}

With our SSO implemented, using Confluence version 3.5.13 the User Directory processes to 'group sync on login' and 'automatically add new user into confluence-users group' are not being invoked.
These functions are specified in User Directory and work fine without our SSO. Thus I think we may just need to call one or more other methods from our existing SSO code.

Anyone know what to call?

Many Thanks,
Craig

6 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

4 votes
Answer accepted
Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 13, 2011

Here's some (untested) code that can be added to a custom authenticator to trigger post-login processing on directory with delegating LDAP authentication.

I'm going to try and chase up getting this added to the default ConfluenceAuthenticator, so it's available to custom SSO authenticators to call at-will (as all SSO authenticators will have this problem).

Imports:

import com.atlassian.confluence.user.UserAccessor;
import com.atlassian.confluence.user.crowd.EmbeddedCrowdBootstrap;
import com.atlassian.crowd.dao.application.ApplicationDAO;
import com.atlassian.crowd.embedded.api.CrowdDirectoryService;
import com.atlassian.crowd.embedded.api.CrowdService;
import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.crowd.embedded.api.DirectoryType;
import com.atlassian.crowd.event.user.UserAuthenticatedEvent;
import com.atlassian.crowd.exception.ApplicationNotFoundException;
import com.atlassian.crowd.manager.application.ApplicationService;
import com.atlassian.crowd.model.application.Application;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.spring.container.ContainerManager;
import com.atlassian.user.User;

Code Snippet:

// Fire the UserAuthenticatedEvent so that post-login processing is triggered ("copy-user-on-login", default group membership, group membership sync)
        final CrowdService crowdService = (CrowdService) ContainerManager.getComponent("crowdService");
        final CrowdDirectoryService directoryService = (CrowdDirectoryService) ContainerManager.getComponent("crowdDirectoryService");

        // Find the directory that the user belongs to.
        com.atlassian.crowd.embedded.api.User crowdUser = crowdService.getUser("TODO: get the username information from the incoming request");
        Directory directory = directoryService.findDirectoryById(crowdUser.getDirectoryId());
        if (!directory.getType().equals(DirectoryType.DELEGATING))
        {
            // post-login processing only needs to be triggered on directories configured to use delegated LDAP Auth.
            return;
        }

        // Obtain a reference to the ApplicationService and Application bean - needed in order to correctly construct the event.
        final ApplicationService applicationService = (ApplicationService) ContainerManager.getComponent("crowdApplicationService");
        final ApplicationDAO dao = (ApplicationDAO) ContainerManager.getComponent("embeddedCrowdApplicationDao");
        final Application application;
        try
        {
            application = dao.findByName(EmbeddedCrowdBootstrap.APPLICATION_NAME);
        }
        catch (ApplicationNotFoundException e)
        {
            // Do some error handling here; something is srsly wrong, for srs.
            return;
        }

        // Fire the event.
        final EventPublisher eventPublisher = (EventPublisher) ContainerManager.getComponent("eventPublisher");
        eventPublisher.publish(new UserAuthenticatedEvent(applicationService, directory, application, (com.atlassian.crowd.model.user.User) crowdUser));


Gary Weaver
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 17, 2011

I've added this into Confluence Shibboleth Authenticator. Thanks!

1 vote
Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 11, 2011

Here's some example code showing how to implement my second suggestion in my previous answer. I think this approach is much easier to achieve and less prone to brittle breakages, than trying to mimic the behaviour of the LDAP directory.

This code is taken from com.atlassian.confluence.user.ConfluenceGroupJoiningAuthenticator. You should be able to quite easily take the relevant bits of this code and apply them to your existing authenticator.

package com.atlassian.confluence.user;

import com.atlassian.crowd.embedded.api.CrowdService;
import com.atlassian.crowd.embedded.api.Group;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.crowd.exception.OperationNotPermittedException;
import com.atlassian.seraph.auth.AuthenticationContextAwareAuthenticator;
import com.atlassian.seraph.auth.AuthenticatorException;
import com.atlassian.spring.container.ContainerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This authenticator adds users to the 'confluence-users' group when they first log in.
 */
@AuthenticationContextAwareAuthenticator
public class ConfluenceGroupJoiningAuthenticator extends ConfluenceAuthenticator
{
    private static final Logger log = LoggerFactory.getLogger(ConfluenceGroupJoiningAuthenticator.class);

    public boolean login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, final String username, String password, boolean setRememberMeCookie) throws AuthenticatorException
    {
        boolean loginSucceeded = super.login(httpServletRequest, httpServletResponse, username, password, setRememberMeCookie);

        if (loginSucceeded)
        {
            User user = getCrowdService().getUser(username);
            // user cannot be null otherwise super.login would have returned false (see com.atlassian.seraph.auth.DefaultAuthenticator#login())
            postLogin(user);
        }

        return loginSucceeded;
    }

    /* default */ void postLogin(final User user)
    {
        new TransactionTemplate(getTransactionManager()).execute(new TransactionCallbackWithoutResult()
        {
            protected void doInTransactionWithoutResult(TransactionStatus status)
            {
                addUserToGroup(user, UserAccessor.GROUP_CONFLUENCE_USERS);
            }
        });
    }

    protected static boolean addUserToGroup(User user, String groupName)
    {
        final CrowdService crowdService = getCrowdService();
        try
        {
            Group group = crowdService.getGroup(groupName);
            if (group == null)
            {
                log.error("Failed to add '{}' to group '{}' because the group could not be found.", user.getName(), groupName);
                return false;
            }
            if (crowdService.isUserMemberOfGroup(user, group))
            {
                log.debug("User '{}' is already a member of group: {}", user.getName(), group.getName());
                return false;
            }

            log.debug("Adding user '{}' to group: {}", user.getName(), group.getName());
            crowdService.addUserToGroup(user, group);
            return true;
        }
        catch (OperationNotPermittedException e)
        {
            log.error("Failed to add '" + user.getName() + "' to '" + groupName + "'.", e);
            return false;
        }
    }

    protected static CrowdService getCrowdService()
    {
        return (CrowdService) ContainerManager.getComponent("crowdService");
    }

    private static PlatformTransactionManager getTransactionManager()
    {
        return (PlatformTransactionManager) ContainerManager.getComponent("transactionManager");
    }
}

Craig Solinski December 12, 2011

Thank you Joseph,

I appreciate the detail code, however I already have code to add to a single group - for example 'confluence-users'.

The functionality that is needed is too parse and apply our 'large and somewhat complex' ADS Group Filter syntax as defined in our UD that contains wildcards and/or other syntactical logic. We certainly would not want to attempt to code/maintain code for each and every LDAP Group Filter change!

Recall that as a 'large institiution', Atlassian recommeded UD type that offers 'group sync on login.

My understanding is the reason 'UD post processing' is not being execute is the method currently requires passing 'password' and when SSO is used the 'password' is NEVER sent to the app so is unavailable.

So our institution, and likely many others, either need Atlassian to make available a 'hook' that can be called post Authentication, or we each need to code and maintian the process you earlier described as: 'obtain a reference to the Directory object to which the authenticating user belongs (you should be able to do this using the ApplicationService bean) and then try to manually craft and fire the event that the post-login processing code is listening for.'

Thoughts?

Thank you kindly,

Craig

Gary Weaver
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 12, 2011

Craig,

I know you said you took a look earlier, but just to ensure you were looking at the right one, here it is: https://studio.plugins.atlassian.com/svn/SHBL/tags/2.0.1/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java

We have old code in the plugin for a LoginFilter that we don't use anymore (you can turn it on in config, but it really shouldn't be there). This is confusing, so be sure to really look at it carefully, and please let me know if you gain further insight. I doubt you will find a solution from it based on what you said, but I've not taken the time to fully understand your issue, so who knows. If that method you are speaking of really requires a password, even if there is a requirement not to store the actual password, perhaps you could use another value other than the user's password, even though that method could/would potentially be insecure as well. We don't pass the SSO's password in and don't need to. I think we pass the password in for local authN only; we don't do this, and I would worry about a risk in overlap of usernames between types of authN.

All that said- the Confluence Shibboleth Authenticator afaik supports joining multiple groups based on headers genned by the incoming mace attributes from the IdP, so we didn't use a prewritten filter to do group joining. If there is a better way to do it that you find out, please let me know. It is all kind of hacked together, but it works for us.

Best of luck,

Gary

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 13, 2011

Craig - thanks for the extra info. I apologise that I didn't infer from your previous comments that your requirements were more complex than just "adding to confluence-users".

I'm currently researching how to craft the code necessary to invoke the full post-login processing on a LDAP directory. I'll keep you posted.

1 vote
Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 7, 2011

Hey Craig,

I have some good news and some bad news. The good news is that I have already done a bit of research into this for the NTLM SSO Authenticator we use in Confluence's SharePoint Connector.

The bad news is that, sadly, the way the LDAP management is implemented in Confluence 3.5, it's quite difficult to force any "on-login" processing to happen if you are using an SSO authenticator.

The new LDAP management module has its own built-in eventing system. Post-login processing (such as group sync and default group memberships) are processed in response to a login event being generated on the User Directory to which the authenticating user belongs.

The long and short of it is that this event only gets generated on the Directory object when Confluence's UserAccessor.authenticate(username, password) method is successfully invoked. In an SSO environment, where you don't have access to the password, it's not possible to call this method (and you wouldn't want to anyway, that's the whole point of SSO, right? :-)).

So in summary, you have two options:

* Try to obtain a reference to the Directory object to which the authenticating user belongs (you should be able to do this using the ApplicationService bean) and then try to manually craft and fire the event that the post-login processing code is listening for.

* Ignore the new LDAP management features and continue performing the default group memberships in your custom authenticator as you have been for previous Confluence versions.

If you really want to go down the first path, let me know and I can give you some more details on the class names and the event that you need to try and create.

Craig Solinski December 7, 2011

Hi Joseph,

Thank you for your clear and detailed response. I wonder if the Shibboleth plugin invokes the 'UD group sync on login'. If so then I could switch to use it. But under the covers here at IU Shibboleth uses CAS which DOES NOT pass the password.

Thoughts?

Thank you very much,

Craig

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 8, 2011

I'm not terribly au fait with the Shibboleth connector (or with Shibboleth in general), so I wouldn't be able to tell you. It's open source, though, so you could take a look for yourself: https://studio.plugins.atlassian.com/source/browse/SHBL

Gary Weaver, who has committed some (most?) of the code for the authenticator, is also an answers.atlassian.com user, so he might be able to give you some more insight, too... if you can contact him. :-)

Craig Solinski December 8, 2011

Hi Joe,

Review of Shibboleth login code highly suggests it will NOT invoke 'UD' 'group sync on login'. This code extends class 'BaseLoginFilter' of which I have not worked with in the past but it definitely does NOT pass password around.

Since we are mandated to use a SSO to prevent passwords from being exposed, it seems most SSO customers are stuck on Confluence version 3.4.9.

I don't know if management is going to allocate resources/or my time to delve into and try to code for Atlassian 'Event triggers' as suggested. We really really want to continue using Confluence (its a fablous product!) and think our user base would flip out if management said we need to move to a different Wiki provider, so I'm pleading that you discuss/promote an Atlassian solution.

Thoughts?

In the meantime, could you please provide more detail on the code we would have to write to invoke 'group sync on login' For example the class names and the events that we would need to try and create.

Thank you,
Craig

Gary Weaver
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 8, 2011

Hi Joseph and Craig,

I've been barely supporting the authenticator, mostly from a project management side, because I don't have a shib'd dev environment where I can test new versions easily, and I do it in my spare time- it isn't a formal job responsibility. So I really appreciate you making us aware of this possible issue, however the way that the Confluence Shibboleth Authenticator supports autogroup joining via LDAP is via Shibboleth having been configured on the IdP side to pull in LDAP values for the users, exposing those to the Shibboleth SP, and the passing in of those Shib MACE attribute values (exposed via attribute-map.xml in Shibboleth 2 or AAP.xml in Shibboleth 1.3) as HTTP headers from Shib (via Apache, etc. if desired) to the container to the authenticator. So, I don't believe we have this issue if it is used in that way?

A little off-topic, but as for authenticators requiring user password in calls, even though the following is probably a horrid suggestion to some, I have been suggesting in the past for people prior to setting up a new custom authenticator to set up a local admin user with the same username as an admin's custom auth'd username, so that you don't use local authN after starting to use the custom authenticator. What this means is that you can fairly safely (I think) use some known password to store as the user's password- you don't need to store it or even an encrypted or hashed password in the Confluence DB via embedded Crowd (or seraph/osuser for earlier versions). This sounds horrible from a security standpoint if there is any chance that the instance will ever intentionally/accidentally need to support local authN. But, if you commit to only using the custom authenticator and only auth'ing users that the external SSO allows, then as long as that environment stays locked down, it should be a safe enough solution.

That said, I like Joseph's second one better than his first, as the first will surely break over time.

Gary

Gary Weaver
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 8, 2011

Craig,

One more note, did you talk to Carl Harris at Va. Tech? He (I think he is the Chief Technology Architect over there now) or someone over there might have some suggestions. I think this is the page, but you could ask on the JASIG list as well: http://www.soulwing.org/confluence-cas.jsp

Gary

0 votes
Craig Solinski December 26, 2011

Hi Joe,

Implemented your code right after our authenticators call to super.getUser: --Our class is very simple and extends ConfluenceAuthenticator.
{code}
... Get CAS validation object including username.
Principal user = super.getUser(validation.getUserName()); # call ConfluenceAuthenticator - works fine and always has.
postLoginUdProcessing(validation.getUserName()); Method call to the code you gave us.
{code}

Username is passed so it can be used on the line you provided with the comment containing 'TODO': like:

... = crowdService.getUser(username); I believe this is your intention.

Found that a NEW user throws a NPE and app ends up on 'System Error' page.

// Find the directory that the user belongs to. NPE is result of second line below according to log.
com.atlassian.crowd.embedded.api.User crowdUser = crowdService.getUser(username); // username is present and accurate.
Directory directory = directoryService.findDirectoryById(crowdUser.getDirectoryId()); # Throws NPE

Could it be that new users have not yet established a directory id?
or
Could it be that this must be done within a transaction?

I was wondering if you could transfer this to a Jira because it seems snapshots and file attachments would help me work with you.

Thank you,
Craig
Atlassian Admin

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 27, 2011

So, to clarify, the NPE is only happening for a new user who is logging in to Confluence for the first time?

We can easily move to JIRA - just create a new issue on https://jira.atlassian.com/browse/CONF and then post the ticket number here - I'll assign it to myself and we can go from there.

Craig Solinski December 27, 2011

Hi Joe,

Created: https://support.atlassian.com/browse/CSP-74002

And 'yes' only new users get thrown to 'System Error' page.

Existing users get logged in successfully (but not to confuse the NPE issue, their post login UD processing 'sync on login' does not fire due to other errors that I'm still investigating - One error says something about 'Cannot Write because in Read Only mode' so I'm going to wrap your code in a 'transaction' and I'll respond on the Jira with Error message snapshots if necessary.

Thank you,

Craig

Craig Solinski December 27, 2011

Hi Joe,

Created https://jira.atlassian.com/browse/CONF-24279

And yes, NPE only occurs for new user who is logging into Confluence for the first time.

Thanks,

Craig

Craig Solinski January 3, 2012

The code on https://jira.atlassian.com/browse/CONF-24279 works very well at our installation.

Thanks,

Craig

0 votes
Terry Luedtke December 16, 2011

Great timing. We didn't upgrade to Confluence 3.5, but now that 4.1 is out I'm working on upgrading our SSO authenticator. We're using SiteMinder and Apache's mod_authnz_ldap (not at the same time, of course :-). Once authenticated, we use LDAP (Microsoft AD) to manage authorization via group memberships. This was trivial in Confluence 3.4.

If there's anything I can do to help, please let me know.

==

Terry Luedtke

terry.luedtke@nih.gov

National Library of Medicine

0 votes
Gary Weaver
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 8, 2011

Craig,

One more note, did you talk to Carl Harris at Va. Tech? He (I think he is the Chief Technology Architect over there now) or someone over there might have some suggestions. I think this is the page, but you could ask on the JASIG list as well: http://www.soulwing.org/confluence-cas.jsp

Gary

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

TAGS
AUG Leaders

Atlassian Community Events