Skip to content

Archive

Archive for February, 2012

This posts relates to Jive SBS version 4.5 series.

In most java J2EE web applications, filters are added in the web.xml file. When doing development on Jive SBS, while you are not completely restricted from making changes to the web.xml file, it is not within the scope of normal development which is limited to themeing and plugins.

We had a need for a filter to intercept all REST calls to make certain they were initiated from only certain specific locations. The solution was found in overidding the Spring Security context in a plugin. To do this, create a plugin using the normal approach as found in the jive documentation.

In the spring.xml file, find the spring-SecurityContext.xml file and copy these items.


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ********************************************************************************************** -->
    <!-- Authentication configuration                                                                                                 -->
    <!-- ********************************************************************************************** -->

    <!-- This bean is the main entry point of the Acegi Filter and a critical configuration point.
            Filters referenced here are defined below. -->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /upgrade/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, upgradeAuthenticationFilter, upgradeExceptionTranslationFilter,jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /post-upgrade/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, postUpgradeAuthenticationFilter, postUpgradeExceptionTranslationFilter,jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /admin/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, sessionTrackingFilter, adminAuthenticationFilter, adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /dwr/call/plaincall/passwordstrength**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /dwr/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, jiveAuthenticationTranslationFilter, denyGuestAccessFilter, pluginPostFilterChain
                /rpc/bridging/**=blockServices
                /rpc/mobile/**=blockServices
                /rpc/api/**=blockServices
                /rpc/openclient/**=blockServices
                /rpc/xmlrpc=pluginPreFilterChain, wsRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter, pluginPostFilterChain
                /rpc/rest/**=pluginPreFilterChain, wsRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter, pluginPostFilterChain
                /rpc/soap/**=pluginPreFilterChain, wsRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /__services/xmlrpc=blockServices
                /__services/rest/**=blockServices
                /__services/soap/**=blockServices
                /__services/api/**=blockServices
                /__services/openclient/**=blockServices
                /__services/office/**=pluginPreFilterChain, dvRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /__services/bridging/**=pluginPreFilterChain, bridgeRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, restExceptionTranslator, jiveAuthenticationTranslationFilter, wsBridgingAccessCheckFilter, pluginPostFilterChain
                /__services/mobile/**=pluginPreFilterChain, mobileRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, mobileBasicAuthenticationFilter, wsExceptionTranslator, mobileRememberMeProcessingFilter, jiveAuthenticationTranslationFilter, wsIphoneAccessCheckFilter, pluginPostFilterChain
                /__services/v2/rest/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, restExceptionTranslator, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /__services/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /api/openclient/**=pluginPreFilterChain, openClientRequireSSLFilter, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, openClientExceptionTranslator, jiveAuthenticationTranslationFilter, openClientAccessTypeCheckFilter, pluginPostFilterChain
                /api/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                /**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, sessionTrackingFilter, formAuthenticationFilter, loginPopupFormAuthenticationFilter, rememberMeProcessingFilter, feedBasicAuthenticationFilter, exceptionTranslationFilter, jiveAuthenticationTranslationFilter,contextOptimizationFilter, termsAndConditionsAcceptanceFilter, pluginPostFilterChain
            </value>
        </property>
    </bean>

Since we were working with rest, I needed to add a new filter to the /rpc/rest/**= line. To add the filter, create the class and make the dependency line in the spring.xml file.


<bean id="restLocationFilter" class="com.jivesoftware.community.aaa.RestLocationFilter">
</bean>

Add whatever dependencies you need to the bean definition for your class. Now create your class using any of the other Spring Security beans as a model. In this case it is called the RestLocationFilter.java. Here is the code.


package com.jivesoftware.community.aaa;

import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import com.jivesoftware.community.JiveGlobals;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RestLocationFilter implements Filter, InitializingBean {
   private static final Log log = LogFactory.getLog(RestLocationFilter.class);
  
   public void destroy() {}
  
   public void doFilter(ServletRequest request, ServletResponse response,
         FilterChain chain) throws IOException, ServletException {
    
      if (!(request instanceof HttpServletRequest)) {
        throw new ServletException("Can only process HttpServletRequest");
      }
      if (!(response instanceof HttpServletResponse)) {
         throw new ServletException("Can only process HttpServletResponse");
      }
      HttpServletResponse httpResponse = (HttpServletResponse) response;
      HttpServletRequest httpRequest = (HttpServletRequest) request;
      String locationFromRestCall = httpRequest.getHeader("RestLocation");
      String configuredRequiredLocation = JiveGlobals.getJiveProperty("authentication.proxy.ip");
      if(locationFromRestCall==null || !locationFromRestCall.equals(configuredRequiredLocation))
      {
         log.info("Webservice request not from required location");
         httpResponse.sendRedirect(JiveGlobals.getJiveProperty("jiveURL") + "/restnotfromrequiredlocation.jspa");
      }
      chain.doFilter(request, response);
   }
  
   public void init(FilterConfig arg0) throws ServletException {}
}

You will notice that the response.sendRedirect is doing a redirect. An appropriate struts entry will be needed in the struts.xml file to properly handle the call.

struts.xml


<action name="restnotfromrequiredlocation" class="com.jivesoftware.community.action.RestNotFromRequiredLocationAction">
   <result name="success">/template/restnotfromrequiredlocation.ftl</result>
 </action>

Knowing that filters can be done this way is the key. You should be able to add any filter in Jive SBS by leveraging this process.

This post is based on using Jive SBS 4.5 series.

The documentation for using REST on Jive SBS is found in the below document. One of the problems with this document is that I cannot just give you a URL. You have to go to the document and in the search type REST.

http://docs.jivesoftware.com/jive_sbs/4.5/index.jsp?topic=/com.jivesoftware.help.sbs.online_4.5/README.html

Search on REST.

Select Core Services.

You will see many services listed including the documentation for these services.

Multiple Method Types
There are four common method types that can be used with REST. GET, POST, PUT, and DELETE. All four methods can be done programmatically. The GET method can often be done by simply dropping a URL in a browser; however, get requests on password protected resources require authentication. POST and PUT usually add an XML based data stream associated with it. DELETE does not have an XML based data stream and is similar to GET requests except that they require authentication.

The best way to do these rest calls is programmatically. In java, we use the apache HttpClient classes to do this. These are the classes:

org.apache.commons.httpclient.methods.GetMethod;
org.apache.commons.httpclient.methods.PostMethod;
org.apache.commons.httpclient.methods.PutMethod;
org.apache.commons.httpclient.methods.DeleteMethod;

Making REST Calls
Lets now go into the Jive Documentation. If you go into User Service, look for web services that are specified as GET calls. Here is an example.

https://myjiveinstance.com/rpc/rest/userService/usersByID/2007

With this call, you will get back basic user information for the user with userid 2007.

https://myjiveinstance.com/rpc/rest/blogService/blogCount

This will give the number of blog posts.

Sample Code
I am including a sample java code that can be used for making rest calls. This sample can be modified in more than one way. While I am calling this java code, I run it as a groovy script. There are a few things to note about this code.

1. Method should be GetMethod, PostMethod, PutMethod, or DeleteMethod depending on the REST call to be used. Reference the jive documentation to know which to use.
2. The authentication credentials need to be a jive user with permissions to the requested resource. You can post for other users if the authenticated user has administrative access or ample permissions to perform the task.

Here is the script as a Post example used for creating blog posts:


// cd C:\development\tfs\jive\jive-sbs\Main\Source\Applications\WebServiceClients\src\com\quest\groovy\restclients
//groovy -classpath c:\groovy\jars\commons-httpclient-3.0.1.jar;c:\groovy\jars\commons-codec.jar RestClientPost.groovy

package com.quest.groovy.restclients

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;

URL requestURL
String restContext = "https://myjiveinstance.com/rpc/rest/blogService/blogPosts";
String userName = "admin";
String password = "admin";

String payloadTemplate ="<createBlogPost>" + 
     "<subject>Rest Created Post</subject>" +
     "<body>Test post.  Please disregard.</body>" +
     "<blogID>3012</blogID>" +
     "<userID>2006</userID>" +
     "</createBlogPost>";

HttpClient client = new HttpClient();
try {
    System.out.println(restContext);
  requestURL = new URL(restContext);
  Credentials defaultcreds = new UsernamePasswordCredentials(
      userName, password);
  client.getParams().setAuthenticationPreemptive(true);
  client.getState().setCredentials(AuthScope.ANY, defaultcreds);
} catch (MalformedURLException e) {
  System.out.println("Couldn't set up the HTTP connection with the community. ");
  e.printStackTrace();
}

try {
   PostMethod method = new PostMethod(restContext);
   StringRequestEntity requestPayload = new StringRequestEntity(
      payloadTemplate, "text/html", null);
   method.setRequestEntity(requestPayload);
   method.setDoAuthentication(true);
  
   // The response code is useful for debugging.
   int response = client.executeMethod(method);
   System.out.println(response);
   InputStream responseStream = method.getResponseBodyAsStream();
   BufferedReader br = new BufferedReader(new InputStreamReader(responseStream));
   String line;
   while ((line = br.readLine()) != null) {
      System.out.println(line);
   }
   br.close();
} catch (HttpException e) {
  System.out.println("Error connecting to the instance. "
      + e.getCause().getLocalizedMessage());
  e.printStackTrace();
} catch (IOException e) {
  e.printStackTrace();
}

Here is a Dot Net sample someone else wrote.


using System.Net;
using System.IO;

            String payloadTemplate = "<" + tbxJivePut_Method.Text + ">" +
                  "<community_id>" + spaceID + "</community_id>" +
                   "</" + tbxJivePut_Method.Text + ">";

     String restContext = "https://jiveinstance.com/rpc/rest/";

            String userID = "2007";
            WebRequest request = HttpWebRequest.Create(restContext + payloadTemplate);
            request.Headers.Add("source","packettrap");
            request.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(userName + ":" + password));

            request.Method = "DELETE";
            WebResponse response = request.GetResponse();

            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            string responseFromServer = reader.ReadToEnd();
           // Response.Write("Response: <br><xmp>" + responseFromServer + "</xmp>");
              tbxJiveDelete_Result.Text = tbxJiveDelete_Result.Text + responseFromServer;
        }

        catch (Exception ex)
        {
            //Response.Write(ex.Message);
            tbxJiveDelete_Result.Text = "";
            tbxJiveDelete_Result.Text = tbxJiveDelete_Result.Text + ex.Message;
        }

Hope this gets you started and good luck!!!