Clearspace has multiple facilities to handle the three primary facets of network application security. This section will discuss each and highlight APIs commonly of interest to developers customizing Clearspace installations.

Fundamental Terminology

The following terms are used commonly in the remainder of this section and are outlined here for clarification:

Supporting Libraries

Clearspace relies on common security patterns established in the Spring Security (formerly Acegi Security) library. By leveraging Spring Security, Clearspace uses terminology familiar to Spring users in an effort to standardize integration and leverage existing Spring libraries and idioms.

Authentication

Fundamentally, authentication in Clearspace is performed by a series of Spring Security filter (implementations of J2EE Servlet Filters) chains, linked together. Each element in a given chain has a dedicated responsibility, while each chain is responsible for accomplishing high-level goals towards the handling of a request. Ultimately, these chains must prepare a request to fulfill a single contract enforced by the last link in the primary security filter chain.

Security Context

Each thread of execution in Clearspace, including background jobs and asynchronous tasks, is associated with a Spring Security SecurityContext instance. The SecurityContext holds information about the Authentication associated with the request.

Internally within Clearspace code, Jive extends the Spring Security Authentication interface with the JiveAuthentication class. This class serves a number of purposes, including directly exposing a Clearspace User implementation representing the current user through a strongly-typed contract as well as exposing meta data about the user such as whether or not the user is anonymous.

URI Mappings

Each URI handled by the Clearspace system passes through a series of J2EE Servlet Filters at the Clearspace Security Layer before entering the Clearspace Application Layer. The following URI contexts are defined in a standard Clearspace installation:

The series of filters handling each request can be altered through the Clearspace Plugin system when customization of authentication behavior is needed (see below).

Security Filter Chains

Clearspace defines several Security Filter Chains, each mapped to a specific URL pattern described above. The default filter chain is defined in spring-securityContext.xml as the following set of filters:

Place in Chain Filter Used Description As Defined in spring.xml
1 Session Integration filter Associates HTTP requests with a security context when a user has previously authenticated or entered the system as a guest. httpSessionContextIntegrationFilter
2 Authentication filters The default authentication filter is an implementation of Spring Security's FormAuthenticationProcessingFilter which delegates to an internal set of AuthenticationProvider implementations. formAuthenticationFilter
3 Cookie Authentication filter Processes "RememberMe" cookies, long-lived HTTP cookies used to authenticate a given user beyond any given session. rememberMeProcessingFilter
4 Feed Basic Authentication filter Performs HTTP Basic Authentication of requests for RSS/Atom feeds. It is generally intended to authenticate standalone feed readers and not browser-based requests. feedBasicAuthenticationFilter
5 Exception Translation filter Routes redirects of various security-related exceptions to URLs within Clearspace. Security-related exceptions from application-level code are caught and processed by this filter and interceptors in the Struts 2 layer depending on the exception. exceptionTranslationFilter
6 Authentication Translation filter Enforces the authentication contract between the Clearspace Security Layer and Clearspace Application Code. jiveAuthenticationTranslationFilter

Authentication Contract

The authentication contract is a fundamental set of assumptions made by application-level code about the security context of any given request. In a standalone Clearspace configuration (one in which Clearspace is the system of record for user information), the authentication contract is met by out of the box Clearspace functionality. Likewise, for LDAP-based authentication Clearspace fufills the contract. In the case of custom authentication, third-party code must meet the terms of the contract in order to perform a successful authentication.

The authentication contract is enforced by the last filter in the Clearspace Security Filter Chain, the JiveAuthenticationTranslationFilter. This ensures that the authentication associated with the SecurityContext is a valid JiveAuthentication before transferring control of the request handling to the Clearspace application layer downstream.

The contract between the Clearspace security layer and the Clearspace application layer requires that one of the following is true before control is passed from the security layer to the application layer:

As part of the authentication contract, if no authentication is present when the JiveAuthenticationTranslationFilter is invoked, the AnonymousAuthentication will be set to the SecurityContext prior to transferring control to the application layer. As a result, application-level code needn't check to see if user references obtained from the SecurityContext are null.

Clearspace includes several implementations of the JiveAuthentication interface, a subclass of Spring Security's Authentication interface. Most commonly used is JiveUserAuthentication which requires an implementation of the Jive User interface as it's sole constructor argument.

As an example, once a handle to a User implementation has been obtained (directly created or through the UserManager API), that implementation instance can fulfill the authentication contract by creating an instance of JiveUserAuthentication and setting that instance to the SecurityContext.

UserTemplate ut = new UserTemplate(); 
SecurityContextHolder.getContext().setAuthentication(new JiveUserAuthentication(ut));

Authorization

Authorization in Clearspace is addressed via three constructs in the Clearspace Application Layer.

  1. Permissions - The Clearspace admin console provides a user interface with which to grant a series permissions to users or groups. Permissions are granted on containers within Clearspace. Containers include communities, blogs and social groups.
  2. Groups - Groups act as a union of permissions and users within the system. Permissions may be assigned to a group, and all users belonging to that group will be entitled to the group permissions unless overridden by more-specific user permissions.
  3. Proxies - Proxies secure Clearspace application layer objects by restricting access to operations on those objects to users with the appropriate permissions. The proxying of Clearspace application layer objects is generally transparent and does not impact security layer code.

Permissions behavior is governed by the PermissionsManager API, group membership by the GroupManager API. Proxies are used to secure access to Clearspace API methods and domain objects as they move through the system. Proxies enforce security based on the Acegi SecurityContext associated with a request. Clearspace associates instances of an Acegi subclass — JiveAuthentication — with each request by the time the servlet stack leaves the filter chain. That JiveAuthentication contains the effective user for the current call stack, which is in turn used to drive proxy authorization checks.

Customization

When customizing authentication or authorization in Clearspace, you'll need to understand where to integrate with various security concerns. The following provides a high-level overview of common customizations for Clearspace 2.0 (and later), including code samples and common strategies for integrating with third-party authentication systems (integration commonly referred to as single sign-on, or SSO).

Common Customization Patterns

Jive customers have commonly made several kinds of customizations. Generally, these customizations are of the following kinds:

  1. Authenticate a principal. This is commonly seen in several incarnations:
    • Reading HTTP headers on the incoming request to authenticate a user. Common examples of this include Basic Authentication headers or SAMLResponse tokens.
    • Direct integration with a J2EE application server to load user information.
  2. Synchronize user profile data with an external identity provider. For example, this commonly includes web services calls to a system of record, reading SAML assertion values and calling third-party APIs to retrieve user data. In all cases, you should perform user profile synchronization at the point of authentication. This simplifies code and configuration, providing a consistent user experience with up-to-date profile data.
  3. Customizing group membership for authorization purposes. Similar to synchronizing user profile data, this commonly involves either invoking a remote service to retrieve group membership information, or reading values associated with the incoming HTTP request.

Depending on your requirements, you can exclude any of these steps. For example, if your Clearspace deployment is not intended to integrate with a centralized group management system, step three is unnecessary. Likewise, if you're using Clearspace to manage all user profile information, step two above may not be necessary.

Customization Via Filter

The recommended way to perform any of the operations described above is to implement a J2EE Filter. A filter commonly:

  1. Is configured as a Spring-managed resource. As such, the filter can expose setter methods for any Jive Spring resource, including the UserManager and GroupManager implementations.
  2. Replaces the formAuthenticationFilter bean defined in the default Clearspace installation. The formAuthenticationFilter processes username/password authentication. Commonly, customized solutions wish to disable form-based authentication. As such, customizations replace the formAuthenticationFilter bean with a custom-defined filter managed as a Spring bean.
  3. Authenticates unauthenticated requests. Filters processing requests in the security layer of the system must check criteria of the incoming request when the SecurityContext indicates that the current authentication is null.

The following XML fragment demonstrates how to define a Spring-managed Filter and replace the default formAuthenticationFilter bean with the newly defined filter:

<!--  Alter the default mapping chains, switching formAuthenticationFilter 
    with the federatedIdentityAuthFilter defined below. This definition uses the federated identity 
    filter for normal app requests and Web Service requests, but leaves the form mechanism in 
    place for admin and upgrade purposes. The desired behavior will vary depending on the 
    application. -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
    <property name="filterInvocationDefinitionSource">
        <value>
            <!-- The URL mappings here are broken for readability, but
                in your XML file they must be unbroken. -->
	CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
	PATTERN_TYPE_APACHE_ANT
	/upgrade/**=httpSessionContextIntegrationFilter, upgradeAuthenticationFilter, \
	    upgradeExceptionTranslationFilter, jiveAuthenticationTranslationFilter
	/post-upgrade/**=httpSessionContextIntegrationFilter, postUpgradeAuthenticationFilter, \
	    postUpgradeExceptionTranslationFilter,jiveAuthenticationTranslationFilter
	/admin/**=httpSessionContextIntegrationFilter, adminAuthenticationFilter, \
	    adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter
	/rpc/xmlrpc=wsRequireSSLFilter, httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \
	    wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter
	/rpc/rest/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \
	    wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter
	/rpc/soap/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \
	    jiveAuthenticationTranslationFilter
	/**=httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \
	    jiveAuthenticationTranslationFilter
        </value>
    </property>
</bean>

<!--  Define a new filter for externally-managed users. Inject dependencies as needed. -->
<bean id="federatedIdentityAuthFilter" 
    class="com.jivesoftware.plugins.aaa.FederatedIdentityAuthFilter">
    <property name="userManager" ref="userManagerImpl"/>
    <property name="groupManager" ref="groupManagerImpl"/>
    <property name="userAgent" ref="agent"/>
    <property name="groupAgent" ref="agent"/>
</bean>

Using Plugins

As of Clearspace 2.5, you can include J2EE Filters in the Jive Spring context by using a Clearspace plugins. In this way, you can develop custom authentication solutions for Clearspace without changing the contents of its WAR file or changing the application server class path.

Sample SSO Plugin

The following code excerpts are taken from a sample Clearspace 2.5 plugin you'll find on the public Jive Subversion repository. The excerpts demonstrate a working (although contrived) implementation of the common customization objectives described above.

Highlights from the sample code include:

As with any code sample, the following may be out of date. Be sure to view the code in the Jive public Subversion repository. [TODO: get updated URL]

Note that while the sample is delivered as a Clearspace plugin, the code and patterns in the plugin are equally valid for versions of Clearspace after 2.0 and before 2.5. The main difference being older versions of Clearspace require manipulation of the classpath or WAR artifact to deploy the code.

spring.xml from Sample


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
    xmlns:cxf="http://cxf.apache.org/core"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
        http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd
        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
        http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd"            
            default-autowire="no">

    <!-- Alter the default mapping chains, switching formAuthenticationFilter 
        with the federatedIdentityAuthFilter defined below. This definition uses the federated 
        identity filter for normal app requests and web service requests, but leaves the form 
        mechanism in place for admin and upgrade purposes. The desired behavior will vary 
        depending on the application. -->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
        <!-- The URL mappings here are broken for readability, but
            in your XML file they must be unbroken. -->
        CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
        PATTERN_TYPE_APACHE_ANT
        /upgrade/**=httpSessionContextIntegrationFilter, upgradeAuthenticationFilter, \
            upgradeExceptionTranslationFilter, jiveAuthenticationTranslationFilter
        /post-upgrade/**=httpSessionContextIntegrationFilter, postUpgradeAuthenticationFilter, \
            postUpgradeExceptionTranslationFilter,jiveAuthenticationTranslationFilter
        /admin/**=httpSessionContextIntegrationFilter, adminAuthenticationFilter, \
            adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter
        /rpc/xmlrpc=wsRequireSSLFilter, httpSessionContextIntegrationFilter, \
            federatedIdentityAuthFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, \
            wsAccessTypeCheckFilter
        /rpc/rest/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, \
            federatedIdentityAuthFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, \
            wsAccessTypeCheckFilter
        /rpc/soap/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, \
            federatedIdentityAuthFilter, jiveAuthenticationTranslationFilter
        /**=httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \
            jiveAuthenticationTranslationFilter
            </value>
        </property>
    </bean>
        
    <!--  Define a new filter for externally-managed users. Inject dependencies as needed. -->
    <bean id="federatedIdentityAuthFilter" 
        class="com.jivesoftware.plugins.aaa.FederatedIdentityAuthFilter">
        <property name="userManager" ref="userManagerImpl"/>
        <property name="groupManager" ref="groupManagerImpl"/>
        <property name="userAgent" ref="agent"/>
        <property name="groupAgent" ref="agent"/>
    </bean>

    <!-- The Sample Agent - Custom implementations would change this to a more 
        meaningful implementation. This particular implementation merges group 
        and user agents into one class which is not required. -->
    <bean id="agent" class="com.jivesoftware.plugins.aaa.SampleAgent">
        <property name="groupManager" ref="groupManagerImpl"/>
    </bean>        
</beans>

FederatedIdentityAuthFilter from Sample

package com.jivesoftware.plugins.aaa;
         
// Imports omitted for brevity.
        
public class FederatedIdentityAuthFilter implements Filter {
    
    private static final Logger log = LogManager.getLogger(FederatedIdentityAuthFilter.class);
         
    private IdentityProviderUserAgent userAgent;
    private IdentityProviderGroupAgent groupAgent;
        
    private UserManager userManager;
    private GroupManager groupManager;
         
    private boolean active;
         
    /**
     * Ultimately this filter will do nothing if the request is already authenticated.
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
         
        final SecurityContext context = SecurityContextHolder.getContext();
        final HttpServletRequest servletRequest = (HttpServletRequest)request;
         
        Authentication auth = context.getAuthentication();
         
        if(auth == null || !auth.isAuthenticated() && active) {
            long timer = System.currentTimeMillis();
            //attempt to resolve the user from the agent
            User externalUser = null;
         
            try {
                externalUser = userAgent.extractUserFromRequest(servletRequest);
               if(externalUser == null) {
                   log.info("User agent failed to load user.");
               }
               else {
                    User coerced = coerceExternalUser(externalUser);
                    if(log.isDebugEnabled()) {
                        log.debug("Loaded and coerced representation for user " + coerced);
                    }

                    // Create the user if not currently in the local system
                    User jiveRepresentation = userManager.getUser(coerced);
                    if(jiveRepresentation == null) {
                        jiveRepresentation = userManager.createUser(coerced);
                    }
                    else {
                        jiveRepresentation = userManager.updateUser(coerced);
                    }
         
                    if(groupAgent != null && groupManager != null) {
                        handleGroupMappings(groupAgent, jiveRepresentation, groupManager, 
                            servletRequest);
                    }
    
                    // Map the user to the security context
                    setUserToAuthenticationContext(jiveRepresentation, context);
                }
            }

            catch(Exception ex) {
                log.info("User agent failed to load user with exception.", ex);
            }
         
            if(log.isDebugEnabled()) {
                log.debug("Federated identity processing time=" + 
                    (System.currentTimeMillis() - timer));
            }
        }
        chain.doFilter(request, response);
    }

    /**
     * Map a user to system groups given the group membership for a user from 
     * the IdentityProviderGroupAgent. This implementation will not attempt to 
     * create a group if it does not exist.
     *
     * Subclasses should override this method if different mapping behavior is desired.
     *
     * @param agent
     * @param user
     * @param groupManager
     * @param request
     */
    protected void handleGroupMappings(IdentityProviderGroupAgent agent, User user, 
        GroupManager groupManager, HttpServletRequest request) {
        long timer = System.currentTimeMillis();
        long[] groupIDs = agent.getUserGroups(user, request);
        if(null != groupIDs) {
            Arrays.sort(groupIDs);
            Iterable<Group> currentGroups = groupManager.getUserGroups(user);

            if(currentGroups != null) {
                // Put the groups into a more meaningful form
                Map<Long, Group> groups = new HashMap<Long, Group>();
                Iterator<Group> groupIterator = currentGroups.iterator();
                Group workingGroup = null;
                while(groupIterator.hasNext()) {
                    workingGroup = groupIterator.next();
                    groups.put(new Long(workingGroup.getID()), workingGroup);
                }

                // For any group the user doesn't belong to, add them.
                for(long groupID : groupIDs) {
                    if(!groups.containsKey(new Long(groupID))) {
                        try {
                            workingGroup = groupManager.getGroup(groupID);
                            if(!workingGroup.isMember(user)) {
                                workingGroup.addMember(user);
                                groupManager.update(workingGroup);
                            }
                        }
                        catch(GroupNotFoundException gnfe) {
                            log.info("Invalid group agent mapping for groupID=" + groupID);
                        }
                        catch(Exception ge) {
                            log.info("Unhandled exception mapping user " + user.getID() +
                                 " to group " + groupID, ge);
                        }
                    }
                }
         
                // For any group the user currently belongs to and shouldn't, remove them.
                for(Long groupID : groups.keySet()) {
                    // Note that the binary search relies on the IDs being sorted above
                    if(Arrays.binarySearch(groupIDs, groupID.longValue()) == -1) {
                        try {
                            workingGroup = groupManager.getGroup(groupID.longValue());
                            workingGroup.removeMember(user);
                            groupManager.update(workingGroup);
                        }
                        catch(Exception ex) {
                            log.info("Unable to unmap user " + user.getID() + " from group " +
                                groupID.longValue(), ex);
                        }
                    }
                }
            }
        }
    
        if(log.isDebugEnabled()) {
            log.debug("Federated identity group processing time=" + 
                (System.currentTimeMillis() - timer));
        }
    }
         
    /**
     * Template method to bind a user object to the system security context.
     *
     * Subclasses should override if changes to this behavior are needed.
     * @param user
     * @param context
     */
    protected void setUserToAuthenticationContext(User user, SecurityContext context) {
        if(user != null && context != null) {
            context.setAuthentication(new JiveUserAuthentication(user));
        }
    }
         
    /**
     * Template method to coerce the user returned from an agent into a
     * user for addition or update to the Jive local store.
     *
     * Subclasses may override this method to achieve different behavior.
     * @param user
     * @return
     */
    protected User coerceExternalUser(User user) {
         
        UserTemplate ut = new UserTemplate(user);
         
        // Mark as federated.
        ut.setFederated(true);
         
        // Update last logged time.
        ut.setLastLoggedIn(new Date());
         
        return ut;
    }
    
    /**
     * Establishes a user agent to use for fetching user information from
     * an external identity provider.
     * @param userAgent
     */
    public void setUserAgent(IdentityProviderUserAgent userAgent) {
        this.userAgent = userAgent;
        active = true;
    }
         
    /**
     * Establishes a group agent to use for fetching user information from
     * an external identity provider.
     * @param groupAgent
     */
    public void setGroupAgent(IdentityProviderGroupAgent groupAgent) {
        this.groupAgent = groupAgent;
    }
         
    /**
     * Sets the user manager to use for updating the user login information.
     * @param userManager
     */
    public void setUserManager(UserManager userManager) {
        this.userManager = userManager;
    }
         
    /**
     * Sets the group manager to use for updating the user login information.
     * @param groupManager
     */
    public void setGroupManager(GroupManager groupManager) {
        this.groupManager = groupManager;
    }
         
    public void init(FilterConfig arg0) throws ServletException {
        //no-op
     }
         
    public void destroy() {
        if(userAgent != null) {
            userAgent.destroy();
        }
         
        if(groupAgent != null) {
            groupAgent.destroy();
        }
    }
}

IdentityProviderUserAgent from Sample

package com.jivesoftware.plugins.aaa;
import javax.servlet.http.HttpServletRequest;
import com.jivesoftware.base.User;

/**
 * Defines the contract between the FederatedIdentityAuthFilter and the
 * identity provider for user information.
 */
public interface IdentityProviderUserAgent {

    /**
     * Invoked by the FederatedidentityAuthFilter to extract a User object
     * from the incoming request. The User returned by this method will
     * be used to load an internal Jive representation of the user, or
     * to create one if an internal representation does not exist.
     *
     * The User returned from this method should be fully populated and
     * should not hold any backing resources -- i.e. field reads should
     * not result in subsequent calls to the identity provider.
     *
     * Implementations if this method should be safe for use across
     * multiple threads.
     *
     * @param request
     * @return
     */
    public User extractUserFromRequest(HttpServletRequest request);
 

    /**
     * To be called by the containing filter to allow the agent to properly
     * release any resources.
     */
    public void destroy();
}

IdentityProviderGroupAgent from Sample

package com.jivesoftware.plugins.aaa;

import javax.servlet.http.HttpServletRequest;
import com.jivesoftware.base.User;
         
/**
 * Contract between the FederatedUserAuthFilter
 * and the identity provider for group information.
 */
public interface IdentityProviderGroupAgent {
         
    /**
     * Returns a long array of group IDs that the given user should
     * map to in Clearspace. The FederatedIdentityAuthFilter will
     * add the user to each group in the array, and for any group
     * the user currently belongs to not in the array, remove the user.
     *
     * Implementations if this method should be safe for use across
     * multiple threads.
     *
     * @param user
     * @param request
     * @return
     */
    public long[] getUserGroups(User user, HttpServletRequest request);
        
    /**
     * Called by the FederatedIdentityAuthFilter to allow the agent to
     * properly release any resources.
     */
    public void destroy();
         
}

SampleAgent from Sample

package com.jivesoftware.plugins.aaa;
         
import javax.servlet.http.HttpServletRequest;
         
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
         
import com.jivesoftware.base.Group;
        
import com.jivesoftware.base.GroupManager;
import com.jivesoftware.base.GroupNotFoundException;
import com.jivesoftware.base.User;
import com.jivesoftware.base.UserTemplate;
         
public class SampleAgent implements IdentityProviderUserAgent,
        
    IdentityProviderGroupAgent {
         
    private static final String TEST_GROUP = "Test Group";
    private static final Logger log = LogManager.getLogger(SampleAgent.class);
        
    private long testGroupID = -1;
         
    private GroupManager groupManager;
         
    /**
     * Initializes the agent. Spring will call this method after all dependencies have
     * been injected.
     */
    public void init() {
        log.info("Initializing sample user/group agent.");
        //create sample group
        if(groupManager != null) {
            try {
                Group testGroup = groupManager.getGroup(TEST_GROUP);
                testGroupID = testGroup.getID();
            }
            catch(GroupNotFoundException gnfe) {
                try {
                    Group newGroup = groupManager.createGroup(TEST_GROUP);
                    testGroupID = newGroup.getID();
                    log.info("Test group created.");
                }
                catch(Exception ex) {
                    log.error("Failed to create test group.", ex);
                }
            }
        }
    }
         
    /**
     * Over simplified implementation of this method that
     * always returns a test user effectively resulting
     * in unauthenticated requests always becoming this user
     * when the doAuth parameter is present.
     *
     * Real implementations would do things like reading SAMLResponse
     * headers, looking for SiteMinder headers, NTLM headers, etc.
     */
    public User extractUserFromRequest(HttpServletRequest request) {
         
        String isAuth = request.getParameter("doAuth");
        if(isAuth == null) return null;
         
        UserTemplate ut = new UserTemplate();
        ut.setUsername("testauth");
        ut.setEmail("test@jivesoftware.com");
        ut.setName("Test Auth User");
        
        return ut;
    }
    
    public long[] getUserGroups(User user, HttpServletRequest request) {
        //return the test group
        if(testGroupID != -1) {
            return new long[] { testGroupID };
        }
        return null;
    }
         
    /**
     * Spring-managed dependency.
     * @param groupManager
     */
    public void setGroupManager(GroupManager groupManager) {
        this.groupManager = groupManager;
    }
        
    public void destroy() {
        //no-op
    }
}

Extending the Sample Plugin

The sample SSO plugin is designed to be extended by SSO implementations and leverages the Maven2 build and dependency management system. You'll most likely customize this plugin by overriding template methods defined in FederatedIdentityAuthFilter. This class is designed to provide boiler plate code used in the vast majority of SSO customizations. In the case of common extension points, the filter defines overrideable behavior as protected methods that can be redefined in subclasses and changed if necessary.

Implementations of the IdentityProviderUserAgent can perform nearly any action desired by the implementors including making web services calls to remote services, accessing other Jive subsystems or contacting an LDAP directory. In effect, this class is similar to a J2EE filter with the exception that it does not need to be concerned with HTTP response or filter chain management. The implementation of the agent should be packaged as a Spring-managed bean. As such, that agent can expose setters for desired Jive manager objects which it can use to process requests as desired.

Many of the classes and interfaces above extend base Spring Security classes. This gives implementors a wide variety of well tested, vetted code on which to base customizations. For example, Spring Security has several classes to assist with processing X509-based authentication. Implementors are encouraged to consult the Spring Security Documentation for more information and to favor existing code wherever possible.