Skip navigation

In 2.0.6 and 2.5.0 the UserService deleteUser method is going away. This is due to a transaction issue in the core UserManager#deleteUser method. The UserManager#deleteUser method is also disabled in these versions because of this.

 

In the web service we opted to remove the method, rather than have the WSDL reflect a method that would only throw an Exception.

 

The method will once again return in a future version. In the meantime we recommend using the UserService disableUser operation instead as this will prevent the user from contributing any new content.

This post is the seventh and final post in a series of blog posts about customizing for Clearspace 2.x. The previous posts covered:

  1. Customizations in Clearspace 2.x

  2. Upgrading Themes and FTL Files.

  3. Widgets in Clearspace 2.x

  4. Macros for Clearspace 2.0

  5. Web Services

  6. Custom Authentication and User Data Providers

 

Some of the most important changes in version 2 of Clearspace required that the underlying API be changed. To get things working on version 2, you'll need to change not only references to these in your Java classes, but also references to them in your FreeMarker templates.

 

The following describes the most significant changes.

  • Inject Dependencies with Spring

  • Work with Containers for Projects and Spaces/Communities

  • Use Transaction Support When Updating Multiple Tables

  • Use Manager Interfaces to Work with Managed Things

  • Handle Content As XML

  • Other API Changes

 

Inject Dependencies With Spring

For version 2, the Clearspace codebase was refactored to support Spring, a framework with modules for inversion of control, transaction management, authentication, and more. This has potentially the biggest impact on your code because components that extend Clearspace must use the same conventions in order to interact reliably with Clearspace. For example, as described below, dependency injection replaces version 1 conventions for using JiveContext and factory classes to get manager instances.

 

Dependency injection is a way to obtain a dependency, such as an object on which your code relies, by having the instance "injected" at run time  -- that is, the instance is set with a JavaBeans-style accessor method. In Clearspace code, manager class instances are generally now injected rather than obtained via a context or factory class method.

 

A class that supports injection includes a class-level ++ variable for holding the instance it depends on and provides a set* accessor method through which Spring can inject the instance (in other words, your code provides a property of the interface's type). Your setter implementation assigns the injected instance to the variable, which your code can then use to access the instance. By specifying the interface on which your code depends  -- rather than an implementation of the interface  -- you have a loosely-coupled dependency. This lets you avoid breakage that might occur if the implementation changes.

 

Note: You can ensure that the setter you provide actually has a configured type associated with it by annotating the setter method with @org.springframework.beans.factory.annotation.Required. If the injected type is not known to Spring configuration, then the Spring container will throw an exception at run time.

 

 

The following example illustrates the basics of dependency injection with Spring: declare private class-level variables for manager instances and declare setter methods through which Spring will inject the instances. Code for version 1 techniques has been commented out in favor of the Spring conventions.

 

// Variables to hold the manager instances.
private DocumentManager documentManager;
private FreemarkerManager freemarkerManager;


// Setters for injecting manager instances.
public void setDocumentManager(DocumentManager documentManager) {
    this.documentManager = documentManager;
}
public void setFreemarkerManager(FreemarkerManager freemarkerManager) {
    this.freemarkerManager = freemarkerManager;
}

private String getDocContent(String documentID)
{
    // ... Declare variables ... 
    
        // Don't use JiveContext (from JiveApplication) to get a DocumentManager 
        // instance. The instance has been injected through the setter above.
        // DocumentManager documentManager = JiveApplication.getContext(AuthFactory
        //    .getSystemAuthToken()).getDocumentManager();
        
        // Use the manager to get a document by its ID.
        document = documentManager.getDocument(documentID);
        Map properties = new HashMap();
        
        // ... Set FreeMarker properties from the document, then apply 
        // a template with them ...
        result = applyFreemarkerTemplate(properties, FREEMARKER_HTML_FILE);
        
    // ... catch exceptions ...

    return result;
}

private String applyFreemarkerTemplate(Map properties,
        String templateName) {
        
    // ... Declare variables ... 
    
        // Don't use getInstance to get the FreemarkerManager instance. It has 
        // been injected.
        // FreemarkerManager freemarkerManager = FreemarkerManager.getInstance();
        
        // ... Use the manager to set up FreeMarker configuration ...
        config = freemarkerManager.getConfiguration(ServletActionContext.getServletContext());
        if (properties != null) {
            // Process the FreeMarker template and store the resulting HTML as a String
            result = applyFreemarkerTemplate(config, properties, templateName);
        }
        
    // ... catch exceptions ...

    return result;
}

While you can optionally name the specific interface implementation you want to have injected, Spring in Clearspace supports a feature known as autowiring. In autowiring, you merely include the setter method (using JavaBeans naming conventions) with a single parameter of the interface's type. To optionally specify the implementation you want injected, you include a spring.xml file that associates the implementation class with your setter.

 

Remove JiveContext uses that retrieve managers. In version 1 the JiveContext interface was a popular convention to get instances of the various manager interfaces  -- CommunityManager, UserManager, DocumentManager, and so on. In version 2 this convention is, depending on the case, either unavailable or unreliable. For example, if you've written a plugin that has a static initializer that tries to bootstrap with call to JiveContext, Clearspace startup will likely fail.

 

Instead, use dependency injection as described above. In fact, to stay out of trouble, the general rule in version 2 is "Don't use JiveContext."

 

Remove getInstance calls that retrieve managers. This includes FreemarkerManager.getInstance, but also all of the *Factory.getInstance methods you might have used in version 1. In version 2 most of the factory classes have been removed. You should replace your calls to their getInstance methods with a property for setting the instance from Spring as described above. Here's a list of the removed classes:

 

  • GroupManagerFactory

  • UserManagerFactory

  • AvatarManagerFactory

  • BanManagerFactory

  • PollManagerFactory

  • SearchQueryLoggerFactory

  • StatusLevelManagerFactory

  • TagManagerFactory

  • WidgetDAOFactory

 

Work with Containers for Projects and Spaces/Communities

Version 2 introduces projects, which, like spaces (known as communities in Clearspace Community), collect content such as documents, discussions, and blogs. Projects also add tasks as a content type. To organize the conceptually similar projects and spaces, the API was changed to introduce the idea of a container. The interfaces that represent projects and spaces  -- Project and Community  -- now inherit from a common JiveContainer interface.

 

In practical terms, this means that some methods taking an instance of the Community interface now take a JiveContainer instance instead. The following code works to get the latest document in either a project or space, for example, because they inherit from JiveContainer and both can contain documents. To see how your code might be affected, search the Javadoc index for occurrences of JiveContainer in method signatures and return types.

 

public Document getLatestDocument(JiveContainer container) {
    if (getDocumentCount(container) == 0) {
        return null;
    }
    DocumentResultFilter filter = new DocumentResultFilter();
    filter.setSortOrder(DocumentResultFilter.DESCENDING);
    filter.setSortField(JiveConstants.MODIFICATION_DATE);

    // Round down the lower date boundary by 1 day.
    filter.setModificationDateRangeMin(ThreadResultFilter
        .roundDate(container.getModificationDate(), 24 * 60 * 60));
    filter.setNumResults(10);
    Iterable<Document> documents = documentManager.getDocuments(container, filter);
    if (documents.iterator().hasNext()) {
        return documents.iterator().next();
    }
    else {
        return null;
    }
}

By the way, this getLatestDocument method is now exposed by the DocumentManager interface; in version 1 this method was exposed by the Document interface. Methods such as this one were moved as part of a larger effort to migrate such functionality into managers.

 

Use Transaction Support When Updating Multiple Tables

Version 2 supports transaction management through Spring with the @Transactional annotation. When the work of your code results in updates (including deletes) to data in multiple database tables, supporting transactional behavior can help to ensure that the updates are made consistently (that is, all of the effected tables are updated or none are).

 

If you're explicitly supporting transactions with @Transactional, you'll need to add an AspectJ compilation step. The AspectJ compiler weaves into your code the support needed to include at load time your transactional method calls in a transaction context.

 

You'll want to add transaction support if your code updates multiple database tables as while executing a method. While the need to support transactions is fairly rare if you're doing all your work through the Clearspace API, keep in mind that your calls to API set* methods might result in database updates. If you're not sure whether your code's work results in database updates, it's not a bad idea probably to add the @Transactional annotation to the method.

 

Adding support for transactions is pretty easy. Here's what you do:

 

 

Use Manager Interfaces to Work with Managed Things

Much of the Clearspace code was rewritten to clarify (or create) relationships between instances of manager interfaces and instances of what they manage. The result is an API that's more intuitive, but it also might mean changes to your code.

 

In general, methods designed to handle types of things  -- documents, attachments, discussion messages, and so on  -- were moved from a "containing" type to a manager type. For example, in version 1 the method getAttachmentCount was exposed by the interfaces BlogPost, Document, and ForumMessage ; each of these could have attachments, and getAttachmentCount was how you got the count of them. In version 2, you get the count of attachments with the AttachmentManager.getAttachmentCount(AttachmentContentResource) method. The AttachmentContentResource is a marker interface extended by BlogPost, Document, ForumMessage, and other things that can have attachments.

 

So in version 2 the rule of thumb, when looking for a way to manage types of things, is to look for a manager of those things.

 

Handle Content As XML

Version 2 includes significant changes in the way Clearspace handles content. Improving the rich text editor (which blogs, documents, and discussions threads all use) included completely reworking the way that content is handled in conversions between plain text (wiki markup), rich text, and HTML (for previewed and published content).

 

In particular, content is set and received as XML. In version 1, the content-related accessors that get and set content body, subject and so on received and returned String instances. In version 2, these methods handle content as instances of org.w3c.dom.Document. Version 2 interfaces that used content accessor methods  -- including Document, PrivateMessage, ForumMessage, BlogPost, and others  -- still include methods such as getPlainBody for retrieving content without markup.

 

For plugins that include actions, you can extend JiveActionSupport for several methods helpful in converting content from one format to another.

 

While version 2 features a model for setting and getting content as XML, there's also an exception: the Macro interface render method still returns content as a String. It has to parse as well-formed XML, but it's still a string.

 

Other API Changes

 

  • In some cases, methods that took a JiveObject instance now take an instance of an interface that extends JiveObject. The marker interfaces CommentContentResource and AttachmentContentResource were created to indicate an object's support for comments or attachments. Look for these types in the Javadoc index to find out whether you need to update your own code.

 

 

The information above along with more details can be found in the Upgrading Extensions to 2.0 documentation.

 

 

I created a quick, one-question poll on Jivespace to help understand what types of customizations are the most commonly performed. It would be great if you could take a few seconds to answer this one question for us.

 

Picture 7.png

Respond to the poll now.

 

We'll use this information to help decide what information would be the most helpful for this blog. I'm also happy to take suggestions here in the comments if you have anything that you would like to see us blog about here on Jivespace.

This post is the sixth in a series of blog posts about customizing for Clearspace 2.x. The previous posts covered:

  1. Customizations in Clearspace 2.x

  2. Upgrading Themes and FTL Files.

  3. Widgets in Clearspace 2.x

  4. Macros for Clearspace 2.0

  5. Web Services

 

As in the Clearspace 1.x series, in Clearspace 2.x versions you can write your own components to manage user information and provide custom authentication.

 

Upgrading a User Data Provider

In version 1 you created a custom user provider by implementing the com.jivesoftware.spi.user.UserProvider interface to interact with your user data source and implementing the com.jivesoftware.base.User interface to represent a user. In version 2, you implement UserProvider and User, but the interfaces have changed a bit. The version 2 model is more streamlined, removing the need to implement the lifecycle methods that the SPI framework requires. Here's a summary of the changes:

 

  • Because you no longer implement the ServiceProvider interface, you don't implement support for its life cycle methods. Clearspace handles provider life cycle through Spring.

  • The com.jivesoftware.base.User interface is read-only (lacks setters) in version 2. Your implementation now provides the interface and logic for updating user information and returning it to Clearspace.

  • The User.authenticate method has been removed. Use an authentication provider to authenticate a user.

  • The User interface includes several new methods for retrieving information about the user.

  • You now connect your custom provider to Clearspace using Spring conventions rather than by setting a Jive property.

 

Here's a high level view of the upgrade steps you'll need to consider.

 

  • Implement the User interface to support whatever setters it needs. You'll also want to implement the is*Supported methods that indicate to Clearspace which user data is supported for setting.

  • For UserProvider methods in which your code receives and returns a User instance, you'll need to rewrite a bit to return your own User implementation. In version 1 the UserProvider instance received a UserTemplate with setters. Because you can no longer simply call setters on the User instance you receive (it might not have any), you'll instead copy data from the received instance into an instance of your own implementation and return your own (such as by constructing your instance from the User your code receives). The methods you'll need to change include create(User) and getUser(User).

  • Implement UserProvider.supportsUpdate to return true if your data store supports creating or updating users from Clearspace. If you return false from this method, be sure to also throw an UnsupportedOperationException from the create and update methods.

 

Upgrading an Authentication Provider

Authentication is substantially changed in version 2. For example, you no longer need to pass an AuthToken instance with method calls. Every request is guaranteed to have an authentication context. In version 2, Clearspace uses Acegi security, which is designed to fit well with the Spring framework. If you're implementing your own authentication provider, your components are based on Acegi; in some cases you might be able to use the classes included with Acegi, such as Authentication implementations.

 

Be sure to read the Acegi documentation, which includes information you'll find useful.

 

What Your Implementation Should Include

 

  • Implement org.acegisecurity.AuthenticationProvider to provide the service that authenticates users for Clearspace. This interface includes two methods: authenticate(Authentication) and supports(Class). Your supports method is called by Clearspace to determine whether the Authentication approach is supported by your provider. You should return true if the Authentication class it receives is one your authenticate method supports.

public boolean supports(Class authentication) {
    return authentication == UsernamePasswordAuthenticationToken.class;
}


The authenticate method receives an Authentication instance containing information identifying the user. Your implementation knows how to authenticate the user and should return an Authentication instance that indicates whether the user is authenticated. Here's a simple authenticate method example in which the checkAuth method (not shown) communicates with the data source and returns true if the user is authentic:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
    String username = String.valueOf(auth.getPrincipal());
    String password = String.valueOf(auth.getCredentials());
    if(!checkAuth(username, password)){
        throw new BadCredentialsException("Username:" + username + " was not authenticated");
    }
    return new UsernamePasswordAuthenticationToken(username, password, new GrantedAuthority[]{});
}


  • Implement org.acegisecurity.Authentication to contain details of the authentication request (you can also use an existing class that implements Authentication ). An Authentication class represents the user in the context of a particular authentication approach -- such as basic authentication, LDAP, X.509 certificate, and so on. An instance includes details such as principal (something identifying the user, such as a username) and credentials (such as a password). Your isAuthenticated method should return true if the user is authentic.
     
    The Authentication class includes a getAuthorities method that is not currently supported by Clearspace. Your implementation should return an empty array:

public GrantedAuthority[] getAuthorities() {
    return new GrantedAuthority[0];
}]


  • Connect your authentication provider to Clearspace by adding a Spring configuration XML file to the jiveHome/etc directory.

 

<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
    PATTERN_TYPE_APACHE_ANT
    /upgrade/**=httpSessionContextIntegrationFilter, upgradeAuthenticationFilter, upgradeExceptionTranslationFilter
    /post-upgrade/**=httpSessionContextIntegrationFilter, postUpgradeAuthenticationFilter, postUpgradeExceptionTranslationFilter
    /admin/**=httpSessionContextIntegrationFilter, adminAuthenticationFilter, adminExceptionTranslationFilter
    /rpc/xmlrpc=httpSessionContextIntegrationFilter, basicAuthenticationFilter, wsExceptionTranslator
    /rpc/rest/**=httpSessionContextIntegrationFilter, basicAuthenticationFilter, wsExceptionTranslator
    /**=httpSessionContextIntegrationFilter, formAuthenticationFilter, rememberMeProcessingFilter, anonymousProcessingFilter, exceptionTranslationFilter
    .... add your implementation ...
</value>
</property>
</bean>

The following stanza lists the authentication providers that Clearspace tries to use. When trying to authenticate a user, Clearspace tries with each in turn, top to bottom. Each of the beans listed here

 

<!-- A list of authentication sources that will be consulted when attempting to
    authenticate the user. Each is consulted in order until a provider does
    *not* return null. This chains multiple providers together
    until one decides it can handle the user. -->
<bean id="authenticationManager" 
    class="org.acegisecurity.providers.ProviderManager">
    <property name="providers">
    <list>
        <ref bean="jiveLdapAuthenticationProvider"/>
        <ref bean="jiveLegacyAuthenticationProvider"/>
        <ref bean="daoAuthenticationProvider" />
        <ref bean="rememberMeAuthenticationProvider"/>
        <ref bean="anonymousAuthenticationProvider"/>
    </list>
    </property>
</bean>

 

The information above along with more details can be found in the Upgrading Extensions to 2.0 documentation.

 

If you are working on authentication for SSO, you might also be interested in reading Fred's post about Quick SSO on Clearspace 2.0

 

 

In my previous blog posts I talked about Connecting a chat client to Clearspace and Scheduling chats in Clearspace. Today we are going to explain another usage of Chat inside of Clearspace.

 

Lets say that we create a new social group that can be seen by anyone and anyone can join the group. As described in Connecting a chat client to Clearspace, I can use my XMPP client to log into Openfire using my Clearspace credentials. I will then see in my roster (aka contacts list) the newly created social group and its members. From there I can easily tell if they are available, away, unavailable, etc. If I want I can start a one-to-one conversation or even send messages to offline users knowing that they will get them when they come back online.

 

A room in Openfire was created when we created the social group in Clearspace. Rooms are also created when you create new spaces or projects. So what are these rooms? These rooms are defined by XEP-0045: Multi-User Chat and will exist as long as the social group, space or project exists. When someone tries to join the room, Openfire will ask Clearspace if that user is allowed to join the room. Clearspace will check the user permissions on the social group, space or project to answer that question.

 

Most XMPP clients allow you to browse rooms on the server. This is a very convenient way for users to discover rooms and join them. As we said, Clearspace has the ultimate control on who can join which room. Once in the room, everything that is said and information about users that join and left the room is stored in Clearspace. The chat transcript and presence information is updated every minute in Clearspace. It is also immediately available for searches.

 

Besides being able to join from your XMPP client of choice you will also have the choice to join the room from the Clearspace site. When you go to the page of the social group, project or space you can customize it to have a widget that will show the room activity (without actually joining the room). Users can then click the join button to actually join the room and participate from the widget. Beside the widget option we also provide other ways to join the room from your site.

 

Moreover, just like you can do in youtube.com it is also possible to copy some HTML instructions to embed the chat widget in your site outside of Clearspace. You can even pass the user/password from your site to the embedded widget so that the user does not need to log in again. However, if no user/password was passed then the widget will ask the user for his credentials before joining the room.

This post is the fifth in a series of blog posts about customizing for Clearspace 2.x. The previous posts covered:

  1. Customizations in Clearspace 2.x

  2. Upgrading Themes and FTL Files.

  3. Widgets in Clearspace 2.x

  4. Macros for Clearspace 2.0

 

Several changes were made in Clearspace 2.0 that could impact your web services code, whether the code is for a client or new service. Generally, these changes were made to make the API easier to use, to add functionality (such as support for REST, new in version 2), or to remove a little-used feature that couldn't be made to work well.

 

  • ln version 2 Clearspace web services are built on the CXF framework. CXF evolved from XFire, the framework on which Clearspace version 1 web services were built. When upgrading, be sure to update your classpath to replace the XFire JAR file with the CXF JAR.

  • SOAP-based web services no longer support caching; references to caching have been removed from the web service client API.

  • If you're using the Clearspace web service client API (for SOAP web services), you'll need to update your references to some classes. To clarify the difference between the standard and web service client APIs, some class names were prefixed with "WS". The Upgrading Extensions to Version 2 documentation contains a list of the renamed classes; these are all in the com.jivesoftware.community.webservices package. Of course, this also means that signatures for some of the methods within the API itself have changed where these types were used.

  • Update some API references where arrays were used so that collections are used instead.

  • The .NET client API is no longer available. If you've got a .NET client, you'll need to rewrite it using another web service client library.

  • If you've written web services that expose Clearspace functionality, note that the WSUtil.getJiveContext method has been removed. Instead, as noted in the description of API changes, you should use Spring properties to obtain manager references.

  • Web services you've included in a plugin are no longer declared in the plugin.xml file. In version 2, when writing a web service for deployment in a plugin, you declare the web service in a spring.xml file included in your plugin. Use the conventions described in the Apache CXF user's guide.Here's an example

<bean id="echoServiceImpl" class="com.jivesoftware.clearspace.plugin.example.webservices.EchoServiceImpl /">
    
<bean id="saajInInterceptor" class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
    <bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="passwordCallbackClass"
                    value="com.jivesoftware.community.webservices.server.xfire.PasswordHandler" />
                <entry key="action" value="UsernameToken" />
            </map>
        </constructor-arg>
    </bean>
    <bean id="validationCheckInterceptor"
        class="com.jivesoftware.community.webservices.server.xfire.ValidationCheckInterceptor">
    <property name="jiveUserDetailsService" ref="jiveUserDetailsService" />
</bean>

<jaxws:endpoint id="echoService" address="/soap/EchoService">
    <jaxws:implementor>
        <ref bean="echoServiceImpl" />
    </jaxws:implementor>
    <jaxws:inInterceptors>
        <ref bean="saajInInterceptor" />
        <ref bean="wss4jInInterceptor" />
        <ref bean="validationCheckInterceptor" />
    </jaxws:inInterceptors>
</jaxws:endpoint>


  • In version 2 WSDL operation params have friendly names. You should get new version 2 WSDL.

  • In version 1 accessing web services via XML-RPC used two extra parameters for username and password; version 2 uses basic auth instead.

 

The information above along with more details can be found in the Upgrading Extensions to 2.0 documentation.

 

We also have a video (with an attached presentation) that shows more about using web services to access your data in Clearspace 2.0 with Andrew Wright, Jive Software Engineer.

 

 

This post is the fourth in a series of blog posts about customizing for Clearspace 2.x. The previous posts covered:

  1. General information about Customizations in Clearspace 2.x

  2. Upgrading Themes and FTL Files.

  3. Widgets in Clearspace 2.x

 

This post continues the series with more information about macros in Clearspace 2.0

 

Depending on what your macro does, it might need substantial upgrade changes or none at all. The main macro-specific difference arises from the fact that Clearspace content is now stored as XML, rather than plain text. At run time Clearspace renders plain text, rich text, or HTML from stored XML. This means that the HTML you return for display when the macro is previewed or published must qualify as well-formed XML.

 

Note that unless your macro is extremely simple, you're likely to also need to keep in mind API changes and FreeMarker changes. The rest of macro development model  -- artifacts involved, how you deploy them, and so on  -- is unchanged from version 1.

 

In version 1, you might have written your macro class in one of two ways: implementing the com.jivesoftware.base.Macro interface or extending the com.jivesoftware.base.BaseMacro class. Implementing the Macro interface was the relatively simple (and documented) way to write a macro class. If you extended the BaseMacro class you were probably doing so in order to support alternate content formats for targets other than HTML  -- including for email, the plain text editor, and so on. In version 2 Clearspace handles conversion to these other formats for you.

 

The process for upgrading your macro class will differ depending on which approach you took. The following describes the two common version 1 ways to implement a macro class and describes what you can do to upgrade each.

 

Your Macro Class Implements the Macro Interface

In version 1 the Macro interface has a single method: String render(String, Map, MacroContext). Your implementation received macro content and parameters (if any), along with information about the context in which it was being used, then simply returned the HTML that should be displayed in the published content.

 

In version 2 this is still supported with one caveat: The HTML returned by your render method must now qualify as well-formed XML. That's because the return value is now inserted into the XML that represents the content it's being used in. Malformed XML returned by a macro won't be used by Clearspace.

 

To upgrade your macro class, simply ensure that the HTML your render method implementation returns is well-formed XML.

 

Your Macro Class Extends the Version 1 BaseMacro Class

In version 1 the BaseMacro class extended BaseFilter, which provided all of the methods a macro needed to return content to Clearspace, whether the content was being displayed in the Clearspace UI or in a notification email. You implemented each of these methods to handle the different targets. In version 2, Clearspace handles rendering for each of these targets so your code doesn't need to.

 

In version 2 there's a BaseMacro class, but it no longer extends BaseFilter, implementing RenderMacro instead. In other words, macro classes extending the version 1 BaseMacro class won't compile on version 2. The version 2 BaseMacro class is for internal use and will almost surely change in future Clearspace releases.

 

To upgrade your macro class, rewrite it so that it implements the Macro interface rather than extending BaseMacro. In most cases this will mean moving your BaseMacro.executeHtmlTarget method implementation (along with the logic that supports it) to your Macro.render method implementation. Both methods return HTML as a String. You can discard the code designed to support the other targets (methods executePlainTextTarget, executeWysiwygEditorTarget, and so on).

 

The information above along with more details can be found in the Upgrading Extensions to 2.0 documentation.

 

 

 

Below is a small filter that I co-authored recently to integrate with Oracle Access Manager (formerly called Oblix). With the release of CS 2.0 we have totally revamped the authentication process and it is now built on spring-security (formerly acegi). Doing this makes it super easy for most of the typical SSO use-cases to be implemented in a reasonable amount of time.

 

Here was the use case:

1. User Makes initiial request and is not authenticated yet.

2. Webgate routes user to the corporate login page

3. User supplies auth credentials

4. Webgate authenticates the user

5. Webgate sets an Authentication Cookie that identifies this user to Webgate

6. Webgate adds custom HTTP Headers to a new request to the originally requested resource, in this case clearspace.

7. Clearspace ACEGI Filter chain executes for /*** path, this is where I inserted the OblixSSOFilter right before the form authentication.

8. The Filter executes, grabs the HTTP Header "jwt-dn" and extracts the users DN (the user name).

9. The Filter retrieves the User and creates an Authentication and allows the rest of the filters to execute.

 

The User is now authenticated. The Default authoprovider ultimatly loads the Users permission etc downstream using the default AuthProvider.

 

Here is the code for the filter. The filter is pretty straight forward. It looks at the incoming HttpServletRequest and attempts to retrieve a HTTP Header that was sent along from the webgate authentication form previously visited by the user, as stated above, in this particular scenario I was able to assume authentication would always be done prior to accessing clearspace.

 

 

 package com.jivesoftware.clearspace.sso.oblix;


import com.jivesoftware.community.aaa.AnonymousAuthentication;
import com.jivesoftware.community.aaa.JiveUserAuthentication;
import com.jivesoftware.base.*;


import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import java.io.IOException;

import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.Authentication;
import org.apache.commons.lang.StringUtils;

/**
 * Created by IntelliJ IDEA.
 * User: fred
 * Date: Jun 11, 2008
 * Time: 12:36:14 PM
 */
public class OblixSSOFilter implements Filter {


    private static String OAMHEADER = "jwt-unique";
    private static String HEADER_NAME = "jwt-dn";
    private UserManager userManager;
    //possible to use system properties to enable and change the header, for
    //now just keep it simple.
    private String oamHeaderName = HEADER_NAME;
    private boolean enabled = true;

    public OblixSSOFilter(){
        super();
    }

  

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        Authentication authentication;

        if(!enabled){
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            try {
                Log.debug("executing oblix filter");
                HttpServletRequest request = (HttpServletRequest)servletRequest;
                String oamHeader = request.getHeader(getOamHeaderName());
                if(oamHeader != null){
                    Log.debug("got OAM header: " + oamHeader);
                    String userDN = extractUserDN(oamHeader);

                    User authenticationTarget = null;
                    try{
                        authenticationTarget = userManager.getUser(StringUtils.chomp(userDN));
                    }catch(UserNotFoundException e){
                        Log.error("no user found with username: " + userDN);
                    }
                    //Found an a
                    authentication = new JiveUserAuthentication(authenticationTarget);
                    authentication.setAuthenticated(true);
                }else{
                    Log.debug("no OAM Header");
                    authentication = new AnonymousAuthentication();
                }
                    SecurityContextHolder.getContext().setAuthentication(authentication);
               }catch (Exception e) {
                    Log.error("Exception occured while trying to authenticate OAM response: " + e.getMessage());
               }
           
               
          filterChain.doFilter(servletRequest,servletResponse);
       }
    }


    private String extractUserDN(String header){
        String userName = null;
        String[] elements = StringUtils.split(header,',');
        for(String element: elements){
            Log.debug("processing header: " + element);
            if(element.startsWith(OAMHEADER)){
                String[] uniqueID = StringUtils.split(element,'=');
                userName = uniqueID[1];
            }
        }
        return(userName);
    }

    public void destroy(){
    }

    public void setUserManager(UserManager userManager) {
        this.userManager = userManager;
    }

    public AuthenticationProvider getAuthenticationProvider() {
        return authenticationProvider;
    }

    public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }

    public String getOamHeaderName() {
        return oamHeaderName;
    }

    public void setOamHeaderName(String oamHeaderName) {
        this.oamHeaderName = oamHeaderName;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}

 

I simply created a jar containing this one class that I deployed to the WEB-INF\lib directory of a expanded clearspace war file. You can use any IDE or VI and Ant to create the jar, nothing special about it or clearspace specific.

 

After I had the jar. I needed to tell clearspace about the filter. Since 2.0 there is a back door that can be utilized to override the default implementation of clearspace managers,DAOs and other spring managed beans, this back door is your jiveHome\etc directoy. Within the jiveHome\etc directory you can copy and modify the various spring context files packaged in the clearspace.jar file found in \WEB\lib. This is done by extracting the appropriate spring context file from the clearspace.jar file found in WEB-INF\lib, make your edits to it and copy it into \jiveHome\etc. In my case the authentication filter stack is configured in spring-securityContext.xml so I extracted that and made the changes listed below:

 

<!-- NOTICE THE ADDITION OF oblixSS0Filter -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                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, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter
                /rpc/rest/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter
                /rpc/soap/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, jiveAuthenticationTranslationFilter 
                /**=httpSessionContextIntegrationFilter, oblixSSoFilter formAuthenticationFilter, rememberMeProcessingFilter, feedBasicAuthenticationFilter, jiveAuthenticationTranslationFilter
            </value>
        </property>
    </bean>

<!-- DECLARE THE NEW FILTER -->
<bean id="oblixSSoFilter" class="com.jivesoftware.clearspace.sso.oblix.OblixSSOFilter">
        <property name="userManager" ref="userManager" />
    </bean>

 

 

As you can see the stack is configured to protect various resources within clearspace, you can add to this stack as required for your particular case, in my case it was simply a matter of declaring my filter, this allows spring to handle the creation of the object and changing the filter stack for the root path of the application to ensure the oblixSSOFilter fired off prior to formAuthentication. If the oblix filter is able to vouch for the user making the request (via the headers) the filter sets the Authentication on the SecurityContext and life moves forward with an authenticated user, if not, I allow it to fall to the next filter in the stack and the process repeats, with this new framework in place it makes it easy to support multiple authentication sources while still staying on the peripheral edges of the product which will help when it comes time to upgrade.

 

 

After you have made the changes, restart your clearspace instance and you should be up and running with your new filter.

 

Enjoy!.  

 

 

 

 

 

 

 

 

 

 

Filter Blog

By date: By tag: