CASifying Apache Pluto portal driver

The following is an account of how to integrate CAS with the Apache Pluto portal driver and a sample CASified portlet. To reproduce this solution some prior familiarity with Tomcat, Pluto and an understanding of CAS proxying would be advantageous. For further coverage of why CAS and portlets should work together see: Portlets using Proxy CAS

Why use the Pluto portal driver instead of a real portal platform?

The Pluto portal is not meant to be a fully functional portal as it only meets the bare minimum requirements for the Pluto container. That said it is distributed as part of the reference implementation of JSR168 therefore any portlet that works in the Pluto portal driver can be assured as JSR168 compliant. Since the Pluto portal is a dedicated portlet platform it is lightweight and easy to deploy portlets to, which is very handy for development purposes.

Preparation: Establishing the Tomcat, Pluto and CAS server platform

The Apache Pluto binary bundled distribution includes a version of Tomcat 5. If you are using a 1.4X JVM you will also need to install an additional Tomcat 5 compatibility pack.

In order for the CAS proxying to work you need to ensure that a SSL certificate is installed into Tomcat and that the CAS server trusts this certificate. This is beyond the scope of this document but it is very easy to install a CAS server onto the Pluto Tomcat instance, generate a local Tomcat SSL certificate and import this into the local JVM (into %JAVA_HOME%/jre/lib/security/cacerts).

Handy Hint

Incidentally you may find IBM's alphaWorks KeyMan utility helpful if you find Java's Keytool a little painful to use. It is also very helpful for managing pkcs format keystores for using certificates signed by third-party certificate authorities (VeriSign, Thawte etc.).

Alternatively it is not actually that difficult to install Pluto from the source distribution into an existing Tomcat server using Maven.

At this point, I will assume that you have a working installation of Pluto, a working CAS server and appropriate SSL certificates and trust relationships established.
In the recipe below I will assume that Tomcat is running at http://localhost:8080 and https://localhost:8443. This Tomcat instance has pluto, portlets and a CAS server installed.

Step One: Install CASFilter into the pluto web application.

You need to install the casclient.jar libraries into the Pluto webapp so that it can find the CAS classes. Then you need to modify the Pluto web.xml to install CASFilter and the CAS proxy servlet. An example modified web.xml might look like:

pluto web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
           "http://java.sun.com/dtd/web-app_2_3.dtd">
<!-- 
Copyright 2004 The Apache Software Foundation
Licensed  under the  Apache License,  Version 2.0  (the "License");
you may not use  this file  except in  compliance with the License.
You may obtain a copy of the License at 

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed  under the  License is distributed on an "AS IS" BASIS,
WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
implied.

See the License for the specific language governing permissions and
limitations under the License.
-->
<web-app>

    <display-name>Pluto Reference Implementation</display-name>

    <!-- ======================= -->
    <!-- CAS Filter settings  -->
    <!-- ======================= -->

    <context-param>
        <param-name>edu.yale.its.tp.cas.proxyUrl</param-name>
        <param-value>https://localhost:8443/cas/proxy</param-value>
    </context-param>

   <filter>
      <filter-name>CAS Filter</filter-name>
      <filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
      <init-param>
         <param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
         <param-value>https://localhost:8443/cas/login</param-value>
      </init-param>
      <init-param>
         <param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
         <param-value>https://localhost:8443/cas/proxyValidate</param-value>
      </init-param>
      <init-param>
         <param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
         <param-value>localhost:8080</param-value>
      </init-param>
      <init-param>
         <param-name>edu.yale.its.tp.cas.client.filter.wrapRequest</param-name>
         <param-value>true</param-value>
      </init-param>
      <init-param>
         <param-name>edu.yale.its.tp.cas.client.filter.proxyCallbackUrl</param-name>
         <param-value>https://localhost:8443/pluto/CasProxyServlet</param-value>
      </init-param>
      
   </filter>

  <filter-mapping>
      <filter-name>CAS Filter</filter-name>
      <url-pattern>/portal</url-pattern>
   </filter-mapping>


    <servlet>
        <servlet-name>pluto</servlet-name>
        <display-name>Pluto Driver</display-name>
        <servlet-class>org.apache.pluto.portalImpl.Servlet</servlet-class>
        <!-- Uncomment the following to allow for non latin-1 character sets in output. -->
        <!--
        <init-param>
            <param-name>charset</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        -->
    </servlet>
    <servlet>
        <servlet-name>TCKDriver</servlet-name>
        <display-name>TCK Servlet</display-name>
        <description>Test servlet for TCK Tests</description>
        <servlet-class>org.apache.pluto.portalImpl.servlet.TCKdriver</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>portletentityregistry</servlet-name>
        <display-name>portletentityregistry Wrapper</display-name>
        <description>Automated generated Portlet Wrapper</description>
        <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>
        <init-param>
            <param-name>portlet-class</param-name>
            <param-value>org.apache.pluto.portlet.admin.controller.PortletEntityRegistryPortlet</param-value>
        </init-param>
        <init-param>
            <param-name>portlet-guid</param-name>
            <param-value>pluto.portletentityregistry</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>pageregistry</servlet-name>
        <display-name>pageregistry Wrapper</display-name>
        <description>Automated generated Portlet Wrapper</description>
        <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>
        <init-param>
            <param-name>portlet-class</param-name>
            <param-value>org.apache.pluto.portlet.admin.controller.PageRegistryPortlet</param-value>
        </init-param>
        <init-param>
            <param-name>portlet-guid</param-name>
            <param-value>pluto.pageregistry</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>deploywar</servlet-name>
        <display-name>deploywar Wrapper</display-name>
        <description>Automated generated Portlet Wrapper</description>
        <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>
        <init-param>
            <param-name>portlet-class</param-name>
            <param-value>org.apache.pluto.portlet.admin.controller.DeployWarPortlet</param-value>
        </init-param>
        <init-param>
            <param-name>portlet-guid</param-name>
            <param-value>pluto.deploywar</param-value>
        </init-param>
    </servlet>

    <!-- ======================= -->
    <!-- CAS ProxyTicketReceptor settings  -->
    <!-- ======================= -->

    <servlet>
        <servlet-name>ProxyTicketReceptor</servlet-name>
        <servlet-class>edu.yale.its.tp.cas.proxy.ProxyTicketReceptor</servlet-class>
    </servlet>

    <!-- ======================= -->
    <!-- Portal Driver Servlets  -->
    <!-- ======================= -->
    <servlet-mapping>
       <servlet-name>pluto</servlet-name>
       <url-pattern>/portal/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
       <servlet-name>pluto</servlet-name>
       <url-pattern>/privileged/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
      <servlet-name>pluto</servlet-name>
      <url-pattern>/secure/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>TCKDriver</servlet-name>
        <url-pattern>/tck/*</url-pattern>
    </servlet-mapping>

    <!-- ======================= -->
    <!-- Administrative Portlets -->
    <!-- ======================= -->
    <servlet-mapping>
        <servlet-name>deploywar</servlet-name>
        <url-pattern>/deploywar/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>portletentityregistry</servlet-name>
        <url-pattern>/portletentityregistry/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>pageregistry</servlet-name>
        <url-pattern>/pageregistry/*</url-pattern>
    </servlet-mapping>

    <!-- ======================= -->
    <!-- CAS ProxyTicketReceptor servlet  -->
    <!-- ======================= -->

   <servlet-mapping>
       <servlet-name>ProxyTicketReceptor</servlet-name>
       <url-pattern>/CasProxyServlet</url-pattern>
   </servlet-mapping>

    <taglib>
      <taglib-uri>http://portals.apache.org/pluto/admin</taglib-uri>
      <taglib-location>/WEB-INF/tld/pluto-admin.tld</taglib-location>
    </taglib>

    <security-constraint>
      <web-resource-collection>
       <web-resource-name/>
       <url-pattern>/secure/*</url-pattern>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
       <http-method>PUT</http-method>
     </web-resource-collection>
     <auth-constraint>
       <role-name>tomcat</role-name>
     </auth-constraint>
     <user-data-constraint>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
     </user-data-constraint>
   </security-constraint>

    <security-constraint>
      <web-resource-collection>
       <web-resource-name/>
       <url-pattern>/privileged/*</url-pattern>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
       <http-method>PUT</http-method>
     </web-resource-collection>
     <auth-constraint>
       <role-name>tomcat</role-name>
     </auth-constraint>
   </security-constraint>

  <login-config>
    <auth-method>FORM</auth-method>
	<form-login-config>
      <form-login-page>/login.jsp</form-login-page>
      <form-error-page>/login.jsp?error=1</form-error-page>
    </form-login-config>
  </login-config>

    <security-role>
        <role-name>tomcat</role-name>
    </security-role>

</web-app>

Re-start the Tomcat server and you should now be redirected to the CAS server for authentication when you access http://localhost:8080/pluto/portal. If after login you are redirected to the Pluto portal then all is well with the Pluto/CAS server trust relationships.

Step Two: Create a CASified portlet

The next step is to a create a "HelloWorld" type of portlet to be CASified (see attached helloWorldPortlet.zip). This uses copies of CASPortletWrapper, CASPortletUtils and ProxyTicketValidatorFactory taken from this Wiki. The attached example is a simple HelloWorld portlet whose VIEW mode includes the contents of a JSP page. This portlet is wrapped by the CASPortletWrapper. The portlet.xml for this portlet application will look something like this:

helloworld portlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" 
             version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
                                 http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">
   <portlet>
      <portlet-name>CASPortletWrapper</portlet-name>
      <portlet-class>edu.yale.its.tp.cas.client.portlet.CASPortletWrapper</portlet-class>
      <init-param>
         <name>edu.yale.its.tp.cas.client.portlet.WRAPPED_PORTLET_CLASS</name>
         <value>helloworld.HelloWorld</value>
      </init-param>
      <init-param>
         <name>edu.yale.its.tp.cas.client.portlet.CasPortletUtilsPORTLET_SERVICE</name>
         <value>http://localhost:8080/portletCasServiceTargets/portletApp.portlet</value>
      </init-param>
      <init-param>
         <name>edu.yale.its.tp.cas.client.portlet.CasPortletUtils.PROXY_CALLBACK_URL</name>
         <value>https://localhost:8443/helloworld/CasProxyServlet</value>
      </init-param>
      <init-param>
         <name>edu.yale.its.tp.cas.client.portlet.CasPortletUtils.CAS_VALIDATE_URL</name>
         <value>https://localhost:8443/cas/proxyValidate</value>
      </init-param>
      <expiration-cache>0</expiration-cache>
      <supports>
         <mime-type>text/html</mime-type>
         <portlet-mode>VIEW</portlet-mode>
      </supports>
      <supported-locale>en</supported-locale>
      <portlet-info>
         <title>
         </title>
         <keywords>
         </keywords>
      </portlet-info>
   </portlet>
   <portlet>
      <portlet-name>helloworld</portlet-name>
      <portlet-class>helloworld.HelloWorld</portlet-class>
      <expiration-cache>0</expiration-cache>
      <supports>
         <mime-type>text/html</mime-type>
         <portlet-mode>VIEW</portlet-mode>
      </supports>
      <supported-locale>en</supported-locale>
      <portlet-info>
         <title>HelloWorld</title>
         <keywords>Hello, world, test</keywords>
      </portlet-info>
      <portlet-preferences>
         <preference>
            <name>displaytext</name>
            <value>Hello, from your preferences</value>
         </preference>
      </portlet-preferences>
   </portlet>
</portlet-app>

and the web.xml for this portlet will look something like this:

helloworld web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
   "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
   <display-name>HelloWorld</display-name>
   <description>Hello World Portlet</description>
   
   <servlet>
       <servlet-name>CasProxyServlet</servlet-name>
       <servlet-class>edu.yale.its.tp.cas.proxy.ProxyTicketReceptor</servlet-class>
        <init-param>
        	<param-name>edu.yale.its.tp.cas.proxyUrl</param-name>
		<param-value>https://localhost:8443/cas/proxy</param-value>
        </init-param>
     </servlet>
     
     <servlet-mapping>
       <servlet-name>CasProxyServlet</servlet-name>
       <url-pattern>/CasProxyServlet</url-pattern>
  </servlet-mapping>

</web-app>
Install the portlet into the Pluto portal

At this point you can compile (using Ant) and install the portlet into the Pluto portal using the portlet admin application Deploy War (I found that I needed to create a temp directory for this to work), this will install the portlet for you into the Pluto portal and make any necessary adjustments for it to run as a portlet in this container. The portlet won't work yet because the Pluto portal has not yet been configured to pass CAS tickets to the backend portlets.

Step Three: Configure Pluto portal to pass CAS tickets

After a series of experiments I decided to integrate CAS into the portal by intercepting each portlet request as this seemed the least complicated technique. The officially endorsed method to pass dynamic information into the portlets would be to implement a DynamicInformationProvider but I found that this was not being called when establishing the inital portlet rendering.

Intercepting portlet requests is achieved by implementing a PortletContainerWrapper class. I created a new class CASPortletContainerWrapperImpl based on a mixture of Pluto container's PortletContainerImpl and Pluto portal's PortletContainerWrapperImpl with additional CAS specific methods to obtain proxy tickets for the portlets. This new class will be called everytime a portlet is invoked by the portal. I have attached CASPortletContainerWrapperImpl.java and CASPortletContainerWrapperImpl.class to this page and I have also included it with helloWorldPortlet.zip (although this is just for ease of build it is not used directly by the portlet).

You need to install this class into the Pluto portal by copying CASPortletContainerWrapperImpl.class into /pluto/WEB-INF/classes/org/apache/pluto/core directory.

I aimed to achieve Pluto/CAS integration with minimal changes to Pluto portal code so I wrote CASPortletContainerWrapperImpl as part of the org.apache.pluto.core package. This was done for convienience because if I was to move this class outside of the org.apache.pluto.core package I would no longer be able to use the default PortletContainerFactory from this package which would overcomplicate things.

You now need to configure Pluto to use this new wrapper, this is done by editing /pluto/WEB-INF/config/services/configServices.properties and commenting out the previous PortletContainerWrapperImpl and adding the new CAS wrapper thus:

/pluto/WEB-INF/config/services/configServices.properties
#portletcontainer.entrance.wrapper.impl = org.apache.pluto.portalImpl.core.PortletContainerWrapperImpl
portletcontainer.entrance.wrapper.impl = org.apache.pluto.portalImpl.core.CASPortletContainerWrapperImpl

If all has gone well, after a server restart you should be able to see something like the following screen:

Attached files

helloworldPortlet.zip
CASPortletContainerWrapperImpl.java
CASPortletContainerWrapperImpl.class

This was example was produced using Pluto-1.0.1-rc4 (bundled with Tomcat 5.5.9), J2SE SDK v 1.4.2_09, Apache Ant 1.6.2 and Yale CAS Server 2.0.11.