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...