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)








No comments:

Post a Comment