Thursday, March 1, 2012

CILogon and Liferay Part 3: Installing the Servlets

This is part 3 of a set of blog posts detailing a procedure for setting up CILogon to provide authentication for a Liferay Portal.

So your Servlets build successfully in Maven, right?  if not, go back and make sure they do before proceeding. Also, by now you should have either your official X509 certificate form your CA or gotten a test cert from CILogon, along with instructions on how to install it.  If not, get these before going any further.

Let's configure Tomcat:

Open up the server.xml file in Tomcat's conf folder.  Find the tag for the secure Connector port.  It may be commented out by default.  Uncomment it and edit it to look like this:


    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS"
keystoreFile="[The path to your local keystore]"
keystoreType="PKCS12"
keystorePass="[Your keystore password]" />

You should have the path and password for your keystore from the installation steps when you received the certificate.  This isn't an optional step, even for testing.  The CILogon establishes a trust before sending you to your IdP, and if it can't do that you'll go no further.

All done?  Good.  Now restart your Tomcat.

Once Tomcat is back up and running, and there are no errors related to your keystore, you can install your CILogon servlet app.  Take the war file and copy it to the /webapps folder in your Tomcat.  It will automatically hot deploy.  Watch the catalina.out file and make sure no errors arise from the installation.

Done?  Good.  On to the next step...

Wednesday, February 29, 2012

CILogon and Liferay Part 2: The CILogon Servlets

This is part 2 of a set of blog posts detailing a procedure for setting up CILogon to provide authentication for a Liferay Portal.

If you're reading this, then by now you have your Liferay Portal up and running, you have the CILogon portal servlet project in your Eclipse, you have a cert ready to go, and you're feeling either nervous or pleased with yourself, maybe both...

Now we dive into some servlet code.

The CILogon servlet app was originally written by Jeff Gaynor, a highly intelligent and amazingly patient fellow who created this app for use as a starting point for people like you and me who want to use it as a platform for portal authenticating awesomeness.  He designed it to be pretty flexible, so the approach I took is only one of many, and you may find that once you get into this and understand how it works better, a different approach may work better for you.  Remember what I said in the last post about leaving the elegance to the reader?  Well, I meant it!

Within the project you'll find a set of files under src/main/resources which are templates for the configuration you're going to use.  Which one you use is up to you, and will depend on the exact requirements and architecture of your system.  I went with memory.xml, but there's also file.xml and postgres.xml.

The important thing to focus on in the file is the hasPortalParameters section.  Here you will configure the URIs for the servlets in this application.  All you really need to change is the domain name for your server.  The callbackUri element will point to the ready servlet (which is in a jar that Maven got from the repository) and the failureUri element points to the failure servlet, which, shockingly, is defined by the FailureServlet class.  The portalName tag is whatever you named you rportal, and the successUri points to the...  you guessed it... success servlet (SuccessServlet).

So when you're done configuring this thing, it'll look something like this:


        <c:hasPortalParameters>
             < rdf:Description rdf:about="ncsa:cilogon.org,2010:206d23fd4830174408e16c649f6d4a06">
                 < rdf:type rdf:resource="ncsa:cilogon.org,2010:/1.0/configuration/portal/"/>
                 < d:callbackUri>https://my.awesome.portal/portal/ready < /d:callbackUri>
                 < d:failureUri>https:// my.awesome.portal /portal/failure < /d:failureUri>
                 < d:portalName>My Awesome Portal< /d:portalName>
                 < d:successUri>https:// my.awesome.portal /portal/success < /d:successUri>
                < d:tempDirectory>none < /d:tempDirectory>
             < /rdf:Description>
         < /c:hasPortalParameters>


Note: The "https" in the successUri.  It MUST be a secure url.

Now, copy that file into the project's src/main/webapp/WEB-INF folder and rename it cfg.rdf


The point of entry into this application is the WelcomeServlet.  This amazingly simple servlet can be the baseline for whatever you want to do with it, but in this project we don't really need it.  You can delete the WelcomeServlet.java and the welcome.jsp or leave them in, it's up to you.  If you do remove it, be sure to remove the corresponding servlet mapping from the web.xml.  Later, when we create the apropriate Sign In link, we'll point it to the StartRequest servlet.

What happens behind the scenes is fully documented in the docs available at the CILogon website, but essentially what happens is that the servlets will contact CILogon and establish a trust before redirecting the user to the IdP selection page.  Have a look at the source code in the StartRequest.java file.

Next, let's take a look at SuccessServlet.java.

This is the servlet that will receive the cert back from the IdP with the user's identifying data, which Liferay will use to log them in.  In a future revision I'm going to see if I can eliminate that servlet altogether and incorporate that processing into the Extension for Liferay, but for now it's out here, separate.

What you'll need from the X509Certificate you get back from CILogon is the user's E-mail address and their name.  Then, you'll take that data and put it into cookies.  Adding to the existing code, here's the code I ended up with in the present method:


        String userEmail = "";
    SuccessfulState ss = (SuccessfulState) pState;
        request.setAttribute("cert", toPEM(ss.getX509Certificate()));
        request.setAttribute("key", toPKCS1PEM(ss.getPrivateKey()));
        
Collection certItems = ss.getX509Certificate().getSubjectAlternativeNames();

String certDetails = ss.getX509Certificate().getSubjectX500Principal().getName();

System.out.println("Details from cert are: " + certDetails);

Iterator itCertItems = certItems.iterator();

while(itCertItems.hasNext()){
List entry = (List)itCertItems.next();
if(isEmail((String)entry.get(1))){
userEmail = (String)entry.get(1);
}
}
System.out.println("User Email received:" + userEmail);
        
        response.addCookie(buildCookie("CILOGON-USER_NAME", getUserName(certDetails)));
        response.addCookie(buildCookie("CILOGON-USER_EMAIL", userEmail));
        
        fwd(request, response, "success.jsp");


Note that we're anticipating a specific format for the incoming data.  (I left my debug statements in.)

The buildCookie method:


    private Cookie buildCookie(String cookieName, String cookieValue){
   
Cookie cookie = new Cookie(cookieName, cookieValue);
        cookie.setDomain(TARGET_DOMAIN);
        cookie.setSecure(false);
        cookie.setMaxAge(60000);
        cookie.setPath("/");


        return cookie;
    }


Pretty straightforward, right?  Here's the getUserName method:


    private String getUserName(String details){
   
    String[] detailArray = details.split("=");
    String[] nameArray;
    String nameSegment = "";
    String name = "";


    for(int i = 0; i < detailArray.length; i++){
    if(detailArray[i].equals("CN")){
    nameSegment = detailArray[i + 1];
    }
    }
   
    if(!nameSegment.equals("")){
    nameArray = nameSegment.split(" ");
        name = nameArray[0] + " " + nameArray[1];
    }    
    return name;
    }



And the isEmail method.  Note that it used a regex to ensure that it is getting a correctly formatted E-mail:


    private boolean isEmail(String value){
   
    String regex = "^[\\w\\.-]+@([\\w\\-]+\\.)+[A-Z]{2,4}$";  
    CharSequence inputStr = value;  
    //Make the comparison case-insensitive.  
    Pattern pattern = Pattern.compile(regex,Pattern.CASE_INSENSITIVE);  
    Matcher matcher = pattern.matcher(inputStr);  
    if(matcher.matches()){  
    return true;
    }
    else
    return false;
    }


Now you can build this application using Maven install.  Don't deploy it yet though... There's one thing you need to do with Tomcat...

To be continued...

-----
This project incorporates tools whose development was funded in part by the NIH through the NHLBI grant: The Cardiovascular Research Grid (R24HL085343)








CILogon and Liferay Part 1: Getting Started

A few months ago I posted a series of entries on this blog to help people get started using Shibboleth as an IdP for logging into a Liferay portal.  Well, it's time to revisit the topic only this time instead of using Shibboleth, we'll be using CILogon.

CILogon is similar to Shibboleth in that it connects the user to a separate Identity Provider for authentication.  A nice feature of CILogon is that it allows the user to choose from a list of available Identity Providers for use with the resource they're accessing.  For more information, visit the CILogon Website.

One thing to keep in mind is that some users may have multiple accounts across multiple Identity Providers.  For example, both Google and PayPal can appear in the list of IdPs and the authenticated user data that they return aren't necessarily going to match.  That means that the same user can come at the portal from multiple different IdPs with different authentication details.

For example, when I authenticate through CILogon on my test server, I can choose from Google, PayPal and Johns Hopkins University as my IdP.  I have accounts with all three of them, and each of them would return a different E-mail address and username after authenticating.  When you use CILogon, you need to keep that in mind, and make decisions on how you're going to handle that kind of situation.

In the case of this tutorial, we're going to ignore that since design decisions like that will vary by application, and implementing solutions will be unique.

One more caveat:  I'm still in the process of refining and fine tuning this process myself, so I won't pretend there isn't room for improvement.  As with all of my blog posts, I'm sharing the basics of what I've found to work, and I leave the elegance to the reader.

This particular setup will involve:

Operating System: (Linux) CentOS 5.6
Authentication Provider: CILogon 1.01
Servlet Container: Tomcat 6.0.29
Portal: Liferay 6.06
IDE: Eclipse Helios

(Yeah, I know Liferay is up to version 6.1 and Tomcat is on version 7, but this solution hasn't yet been tested in those environments.  Liferay 6.1 is different enough from 6.0 that I make no guarantees that this same version will work.)

Prep work:

  Before you go any further, you absolutely must obtain a certificate from CILogon if you don't already have one from a trusted Certificate Authority.  The process for doing so is detailed here.  This is not an optional step!  That means you also need to be doing this on a machine that has a static IP address and hostname.

Got it?  Alright.  Let's proceed.

You're also going to need Maven to build the CILogon portal servlet application.  If you don't already have the Maven plugin, download and install that.

Step 1: Install Liferay

If you haven't done so already, download Liferay 6.06 and install it according to the Liferay instructions.

Start it up and make sure it works, and that you're able to log in as the admin.  All set?  Good.

Step 2: Download the CILogon Portal App.

The way I did this was to download and customize the cilogon-portal-servlet project.  You can import it right into Eclipse using SVN.  In the File menu, click Import...  You'll then see a popup box where you can select the import source.  Under the SVN folder, select "Checkout Projects from SVN" and click "Next."  Choose "Create a new repository location" and enter the URL in the link at the beginning of this paragraph.  Click "Finish."

This can sometimes be a little tricky, so you might have to wrestle with it a little.  (I did, but I managed to get it to download into Eclipse eventually.)

Now that you have the project in your Eclipse, you can customize it to fit your needs.  Eventually you'll be installing this as a webapp in the same Tomcat where Liferay lives.  Make sure you can build it with Maven before moving on to the next step.

To be continued...










Thursday, December 8, 2011

Concurrent Tomcats Different Versions

If you're trying to run concurrent Tomcats in your Windows environment (and presumably, Linux as well, I'll let you know) there are plenty of helpful guides out there that will tell you how quick and easy it is...

The problem is they're making assumptions that may not be true.

1)They assume both of your Tomcats are the same version.
2)They assume both of your Tomcats have the same general structure.

If both of the above conditions are not true, then just setting CATALINA_BASE on the fly and changing a few port numbers is going to crash and fail.

Ask me how I know.

I'm experimenting with new ways to handle SSO with Liferay and my current model calls for a Liferay instance running in a Tomcat bundle along with a separate instance of Tomcat that is NOT running Liferay.

Instance 1: Tomcat 6.0.29 running Liferay 6
Instance 2: Tomcat 6.0.32

Go ahead and try to just set CATALINA_BASE when you start that second instance and see what happens.

*KABOOM*

The reason should be fairly obvious.

When Tomcat starts up, it looks at your environment variables to find CATALINA_HOME.  Once it has that, it will use it to set up a few other things, like CATALINA_BASE.  Most online guides will  tell you that setting a new CATALINA_BASE as part of the startup script to point to the folders in the second instance of Tomcat will allow both instances to share CATALINA_HOME (because all those jars are expected to be identical) but have separate configurations (Like server.xml, where you'll be setting separate ports between instances.)  The trouble is that as you can see above, these two Tomcats are running slightly different versions.  Also, in a Liferay bundle there are a few other minor differences that will cause trouble is a non-Liferay Tomcat is trying to share resources.

That means you're going to have to completely separate the two.

This isn't hard, but you do have to be careful.

First, create a new environment variable.  I called mine CATALINA_ALT.  That should be set to the Tomcat home folder of your second Tomcat instance.  (Just like how CATALINA_HOME is set to the tomcat folder for the first.)

Next, you'll have to edit the .bat files in the second Tomcat instance to use CATALINA_ALT instead of CATALINA_HOME.  The files I modified to start with are startup.bat, catalina.bat, service.bat and shutdown.bat.

I used a global find/replace to change all occurrences of CATALINA_HOME to CATALINA_ALT.  Then, I did the same thing to change all instances of CATALINA_BASE to CATALINA_BASEALT.  It doesn't matter what you call them as long as they're different from the original and consistent with each other.

Next, go into your server.xml file and change all the ports to something different from the way they're configured in the first Tomcat instance.  (For example, change the connector port 8080 to 8081.)  Again, it's not as important what values you choose as long as they're different and internally consistent.

Now the two Tomcats will operate independently of each other.  (It's okay that hey share the same JAVA_HOME variable, but if you're running some kind of setup where the Java versions also need to be different, then just create an alternate JAVA_HOME variable for the second Tomcat as well.)

This tactic can also be useful if you're running two separate Liferay instances where one of them has been modified through Ext plugins.

Now, ideally you'll want to go through and make sure that ALL of the .bat files are consistently using CATALINA_ALT and CATALINA_BASEALT in your second Tomcat instance's bin folder.  This guide is just to give you a basic understanding of what's going on and what you'll need to do.  These steps should also work for Linux except that you'll work with the .sh files instead of the .bat files.

Friday, November 18, 2011

Liferay Portlet Build Numbers

Here's another error to pick apart:  

com.liferay.portal.OldServiceComponentException: Build namespace X has build number 18 which is newer than 13

Yeah... Google that and you get lots of helpful advice about going into your WEB-INF/src/service.properties file and changing the build.number to something higher.  Simple, huh?

But what if that doesn't fix it?

Here's another clue:  You've set that build number higher and yet... it still reports the same numbers it did before.  What's the deal? 

Do you have separate portlet projects installed using the same namespace in your service.xml?

If you do, there's your problem.

You see, when you're building your service.xml file, the namespace you use needs to be unique to the portlet.  Otherwise, if you deploy another portlet later that's trying to use that same namespace, Liferay will look at the namespace, compare it to the one you've already installed, and think they're the same service project.  If your new one is lower than the old one, Liferay assumes you're trying to replace the same namespace with an older version and won't allow it.

So when you're using the service layer, always make sure your namespace is unique.

Tuesday, November 1, 2011

Video Display Portlet Available on Github

I've just added a new portlet to my Github repo (And the Liferay Community Plugins).  It's a Video Display Portlet for Liferay 6 that will play videos from the Document Library, similar to the methods described in some of the earlier posts in this blog.

Here's a link to the page: Link
And the Liferay Download page: Link

Tuesday, October 11, 2011

In a Portlet: Where's my FacesContextFactory?

Getting this error?

java.lang.IllegalStateException: Application was not properly initialized at startup, could not find Factory: javax.faces.context.FacesContextFactory

Liferay 6
JSF 2.0

Was it working just fine a little while ago?
Well when I got this one all I'd changed was to set the "instanceable" setting in the liferay-portlet.xml file from "true" to "false."  Then I started getting this error.

Restarting the portal won't fix this.
Re-installing the portlet won't fix it either.

See, if your portlet was previously instanceable and now it isn't, but you left the portlet on the page when you re-installed, then it blows up all sorts of interesting things.  When you make a change like that you have to remove the portlet from the page and drop it back on again.

So to fix this, just remove it from the page, place it back on, and presto!  All should be well.