Wednesday, December 30, 2009

Quick Guide to Using Google's OpenID

These notes are based on some work done by Jun Ji in our group.  He's been converting the QuakeSim project's portlets into Google gadgets as a prelude to Open Social integration, as discussed in previous posts.  One problem is that the gadgets need to identify and authenticate the user. We don't have excessive security requirements.  We just need to save the users' interactions with the web interface as persistent sessions ("projects") so that they can be accessed again in later sessions.

This identification step is actually pretty simple to do with OpenID and openid4java, but you may not guess this from Google's documentation (see for example http://code.google.com/apis/apps/sso/openid_reference_implementation.html).   To start, you will need to download the openid4java tar file (see http://code.google.com/p/openid4java/) and unpack it. The openid4java jars need to go in your webapps's WEB-INF/lib directory, as usual.

Next, you need to copy the provided samples consumer_redirect.jsp and consumer_returnurl.jsp to your webapp's directory. They are in ./samples/simple-openid/src/main/webapp/ of the openid4java download.   You will need to modify them a little: consumer_redirect.jsp for example has a variable returnToUrl that needs to be changed to use your webapp's name. For our other modifications to these codes, see the source code at source forge: http://crisisgrid.svn.sourceforge.net/viewvc/crisisgrid/QuakeSim2/portlets_dev/Disloc3/src/main/webapp/


This completes the setup.  You can now make simple login page like the one below (call it index.jsp or whatever).  You need to do the following:
  1. See if a logout action has been requested.  If so, clean up relevant OpenID session variables.
  2. See if the openid attribute has been set.  If so, give the user an option of logging out or navigating on to other pages.
  3. Otherwise, redirect the user to the Google OpenID login page.  
Here's what the JSP page looks like:
<%@ page session="true" %>
<html>
<body>
<%
    if (request.getParameter("logout")!=null)
    {
        session.removeAttribute("openid");
        session.removeAttribute("openid-claimed");
%>
    Logged out!<p>
<%
    }
    if (session.getAttribute("openid")==null) {
%>
<form method="POST" action="consumer_redirect.jsp">
<strong><br>QuakeSim2 OpenID login test page:<br><br>
</strong>
<input type="hidden" name="openid" value="https://www.google.com/accounts/o8/id" size="60"/><br>
<input type="submit" value="log in into google"/>
</form>
<%
} else {
%>

Logged in as <%= session.getAttribute("openid") %><p>
email address <%= session.getAttribute("email")%><p>
<a href="?logout=true">Log out</a>

<% } %>

</body>
</html>

Note the form action sends the user to the consumer_redirect.jsp page we took from openid4java.  The hidden parameter openid is sent to consumer_redirect.jsp and tells it to redirect to the Google login page. The consumer_returnurl.jsp page acts like a callback page and tells the Google login form where to redirect the browser request if login was successful.  Your future requests will be filtered through this servlet, which can enforce verification.

Finally, note that several session variables will be set. Here are some sample name-value pairs (just use session.getAttributeNames() in index.jsp and list):

openid-disco OpenID2 OP-endpoint:https://www.google.com/accounts/o8/ud ClaimedID:null Delegate:null
email marpierc AT gmail.com
openid-claimed https://www.google.com/accounts/o8/id?id=ABCdefg-hIJKLMNop
openid https://www.google.com/accounts/o8/id?id=ABCdefg-hIJKLMNop


You may want to use these in other applications.  We use the email attribute to identify the user for database purposes.  You may also want to use these variables to associate a successful OpenID login with a legacy user entry in your database.

Monday, December 14, 2009

Getting Started with Friend Connect

This blog post will cover two simple capabilities of Google Friend Connect: creating a social network and rating items in a table.  First, however, let's provide some motivation for science portals.

We've developed the QuakeSim portal for several years to support earthquake modeling research and education. A typical scenario, common in many other gateways, is for a user to set up and launch an application through a Web interface.  Web services are needed to both run the modeling code and preserve metadata about the run: the input parameters, the time the code was executed, the URLs of all the input, output, and error files, and so on.  We organize these metadata fields as projects owned by a particular user.  The metadata is modeled with JavaBeans and stored in object databases, although these are irrelevant technical details here.  It is also possible, and appropriate, to model the metadata as Atom/RSS, for example.

Let's look first at creating a little GFC social network of people who may be interested in looking at the metadata feeds.  You will need to create a Google account if you don't have one and register a URL with GFC.  Just use your development machine's URL, as you would with Google Maps.  Note the Site ID that you are assigned (it will be in the GFC URL of the projects you register).  You'll need to use this in your GFC applications.

To get started, you can generate code for lots of GFC applications.  Just save this generated HTML/JavaScript in files that can be loaded from your Web server.   If you want to use these as standalone pages, be sure to add <html> and <body> tags to the beginning and end of the document.  You can also embed the generated code into existing HTML pages.  To wrap the code as a gadget, just paste it in the CDATA section of an "html" gadget.

First, now, we need a GFC code snippet to manage logins and mini-social networks.  The GFC way for doing this is shown below:

<html>
<body>
  <!-- Include the Google Friend Connect javascript library. -->
  <script type="text/javascript" src="http://www.google.com/friendconnect/script/friendconnect.js"></script>
  <!-- Define the div tag where the gadget will be inserted. -->
  <div id="div-4719709228063302739" style="width:276px;border:1px solid #cccccc;"></div>
  <!-- Render the gadget into a div. -->
<script type="text/javascript">
var skin = {};
skin['BORDER_COLOR'] = '#cccccc';
skin['ENDCAP_BG_COLOR'] = '#e0ecff';
skin['ENDCAP_TEXT_COLOR'] = '#333333';
skin['ENDCAP_LINK_COLOR'] = '#0000cc';
skin['ALTERNATE_BG_COLOR'] = '#ffffff';
skin['CONTENT_BG_COLOR'] = '#ffffff';
skin['CONTENT_LINK_COLOR'] = '#0000cc';
skin['CONTENT_TEXT_COLOR'] = '#333333';
skin['CONTENT_SECONDARY_LINK_COLOR'] = '#7777cc';
skin['CONTENT_SECONDARY_TEXT_COLOR'] = '#666666';
skin['CONTENT_HEADLINE_COLOR'] = '#333333';
skin['NUMBER_ROWS'] = '4';
google.friendconnect.container.setParentUrl('/gadgets-repo/' /* location of rpc_relay.html and canvas.html */);
google.friendconnect.container.renderMembersGadget(
 { id: 'div-4719709228063302739',
   site: '16707400350554686557' },
  skin);
</script>
</body>
</html>

The site ID in the above example is one of my site IDs.  You would need to replace this with your site ID.

This is just generated code from the GFC site.  I only added the (necessary) HTML and BODY tags so that it would work as a standalone  page. Load this new HTML page in your browser and use it to sign in and add people to your network.  If you are already signed in to a Google site (say to iGoogle) on the same browser, then the GFC widget above should detect this through cookies. Note that this network should apply only to a specific site ID, so you can create multiple social networks.  One difficulty here of course is that all of this is external to your legacy portal's authentication system.

Our next example is a rating widget that can be embedded into an existing page.  Recall our motivating problem was an archive list of project metadata associated with a particular user's previous code submissions.   In QuakeSim, we render this information using JSF datatables populated with JavaBeans pulled from an object database, but this detail is irrelevant here.  In the browser, the generated page would look something like the HTML below:

<html>
<body>
<!-- Include the Google Friend Connect javascript library. -->
<script type="text/javascript" src="http://www.google.com/friendconnect/script/friendconnect.js"></script>
<!-- Define the div tag where the gadget will be inserted. -->
<table border="1">
  <tr>
     <th>Project</th><th>ID</th><th>Date</th><th>Input File</th><th>Output File</th><th>Recommend</th>
  </tr>
  <tr>
     <td>
        Northridge
     </td>
     <td>
        123456789
     </td>
     <td>
        08:43 GMT, Dec 14, 2009
     </td>
     <td>
        <a href="http://www.quakesim.org/mpierce/northridge/input">Input</a>
     </td>
     <td>
        <a href="http://www.quakeims.org/mpierce/northridge/output">Output</a>
     </td>
     <td>
        <div id="div-1095870898733665271" style="width:100%;"></div>
     </td>
  </tr>
  <tr>
     <td>
        Plieto
     </td>
     <td>
        987654321
     </td>
     <td>
        10:12 GMT, Dec 12, 2009
     </td>
     <td>
        <a href="http://www.quakesim.org/mpierce/plieto/input">Input</a>
     </td>
     <td>
        <a href="http://www.quakeims.org/mpierce/plieto/output">Output</a>
     </td>
     <td>
        <div id="div-123456789" style="width:100%;"></div>
     </td>
  </tr>
</table>

<script type="text/javascript">
var skin = {};
skin['HEIGHT'] = '21';
skin['BUTTON_STYLE'] = 'compact';
skin['BUTTON_TEXT'] = 'Recommend it!';
skin['BUTTON_ICON'] = 'default';
google.friendconnect.container.setParentUrl('/gadgets-repo/' /* location of rpc_relay.html and canvas.html */);
google.friendconnect.container.renderOpenSocialGadget(
 { id: 'div-1095870898733665271',
   url:'http://www.google.com/friendconnect/gadgets/recommended_pages.xml',
   height: 21,
   site: '16707400350554686557',
   'view-params':{"pageUrl":"http://www.quakesim.org/users/mpierce/northridge","pageTitle":"Northridge","docId":"recommendedPages"}
 },
  skin);

google.friendconnect.container.renderOpenSocialGadget(
 { id: 'div-123456789',
   url:'http://www.google.com/friendconnect/gadgets/recommended_pages.xml',
   height: 21,
   site: '16707400350554686557',
   'view-params':{"pageUrl":"http://www.quakesim.org/users/mpierce/plieto","pageTitle":"Plieto","docId":"recommendedPages"}
 },
  skin);
</script>
</body>
</html>


The main thing to notice is that each project has been given a URL.  I use http://www.quakesim.org/users/mpierce as the base--see the pageUrl parameter in the code above. This assumes a REST style programming model for the metadata services, which unfortunately isn't implemented yet (but now I have some motivation to do it...).  The javascript at the end is a slightly modified version of generated GFC code.  The values for the view-params (pageURL, pageTitle) are generated and should be taken from entries in your project database.


Each entry in the table will have a button with "Recommend It" in the final column.  You can get a list of recommended projects in a page like the one below (again, this is generated by an online GFC tool):

<html>
<body>
<!-- Include the Google Friend Connect javascript library. -->
<script type="text/javascript" src="http://www.google.com/friendconnect/script/friendconnect.js"></script>
<!-- Define the div tag where the gadget will be inserted. -->
<div id="div-6585877907190446397" style="width:300px;border:1px solid #cccccc;"></div>
<!-- Render the gadget into a div. -->
<script type="text/javascript">
var skin = {};
skin['BORDER_COLOR'] = '#cccccc';
skin['ENDCAP_BG_COLOR'] = '#e0ecff';
skin['ENDCAP_TEXT_COLOR'] = '#333333';
skin['ENDCAP_LINK_COLOR'] = '#0000cc';
skin['ALTERNATE_BG_COLOR'] = '#ffffff';
skin['CONTENT_BG_COLOR'] = '#ffffff';
skin['CONTENT_LINK_COLOR'] = '#0000cc';
skin['CONTENT_TEXT_COLOR'] = '#333333';
skin['CONTENT_SECONDARY_LINK_COLOR'] = '#7777cc';
skin['CONTENT_SECONDARY_TEXT_COLOR'] = '#666666';
skin['CONTENT_HEADLINE_COLOR'] = '#333333';
skin['HEADER_TEXT'] = 'Recommended stories';
skin['RECOMMENDATIONS_PER_PAGE'] = '5';
google.friendconnect.container.setParentUrl('/gadgets-repo/' /* location of rpc_relay.html and canvas.html */);
google.friendconnect.container.renderOpenSocialGadget(
 { id: 'div-6585877907190446397',
   url:'http://www.google.com/friendconnect/gadgets/recommended_pages.xml',
   site: '16707400350554686557',
   'view-params':{"docId":"recommendedPages"}
 },
  skin);
</script>
</body>
</html>

Integration Thoughts: the main issue I see in the above example is integrating the GFC authentication and authorization system with your portal. We'll look at this next.  Also, one of the strengths of GFC is the simplicity of the JavaScript code exposed to the end developer.  Gateways have long had a problem integrating in programming-literate computational scientists who just didn't care to learn Enterprise Java and similar things. To retain this advantage of GFC, we would probably need to consider building our own higher level gadget that queries our project database and is rendered with google.friendconnect.container.renderOpenSocialGadget() GFC call.

Thursday, December 10, 2009

Building Simple Social Network Applications with Google Friend Connect

Within the academic community, there has been a push by various groups to provide a "Facebook for Scientists", a "Facebook for Scholars", or similar things.  This is to sell you something, of course. I am skeptical of this approach, since we already have Facebook, and I don't want to join and contribute to yet another social network, especially if I don't know its long term sustainability.

Let's try to follow a systematic approach.  As a starting point, I would like to point you to a nice submission by Roger Curry, Cameron Kiddle and Rob Simmonds to our recent GCE09 workshop. The authors looked at using Facebook, Ning, and Elgg as three social networking platforms.  You know Facebook, and you probably know that Ning is a social networking hosting service.  Elgg was new to me: it is a downloadable software package, like Drupal or Mambo.  Unlike Drupal, however, Elgg is designed from the beginning to be a social networking platform rather than a content management system. 

Curry, Kiddle, and Simmonds's paper presents three detailed case studies.  I would add some additional remarks on requirements.  First, a new social networking site should be compatible with existing social networks and related standards.  I should be able to import in my existing profile and social networking information.  I should also be able to reuse my identity from another site.  I don't want to remember yet another password.  Similarly, I should be able to export my social networking information.  I don't want to get locked in. OpenID, OpenSocial, OAuth, and related standards may make it easier to share one's identity and information across social networking services.

Second, development flexibility is important.  Of the three social networks Curry et al reviewed, Ning's hosted service was generally the least favored. Part of this was because of the advertisements, but it was also noted that access to Ning source code was limited.  If you are considering a hosted service, you need also to consider the development framework that you will be required to use, its strengths, and its weaknesses.

Third, I want to use the best available implementation of a capability, which may not be the one that comes in the software. For example, there are many wikis and calendaring tools.  I want the ability to easily import these tools into a social networking framework.  I don't want to rely upon the wiki or calendar plugin that comes with some particular framework.

For these reasons, I have become very interested in Google Friend Connect (GFC) as a way to build social networking science gateways (and credit goes to Raminder Singh and Gerald Guo in our lab for introducing this to me).  Gadgets are particularly interesting in that they are a client-side technology: any Web development framework can be used to develop gadgets.  We have developed an open source gadget container (see http://www.collab-ogce.org/ogce/index.php/OGCE_Gadget_Container) that can be used to host your own gadgets.   We discuss these things in more detail and provide an implementation case study in our own workshop paper.

I have advocated using Open Social and Google Gadgets for some time (see for example http://grids.ucs.indiana.edu/ptliupages/publications/INGRIDFinal.pdf), but the JavaScript API is not particularly simple, and of course Facebook (the biggest US social networking site) doesn't participate in Open Social.  Thus, while Open Social provides a nice and well documented interface, it is likely that the social network information that it can access is lower quality than Facebook's (at least for US users).

GFC solves those two problems. First, the developer API, although still in JavaScript, is much simpler than Open Social's. The GFC site provides a number of basic capabilities (chat, comment, rating, polling, etc) that have generated HTML and JavaScript code.  You can build up more complicated applications by cutting and pasting these capabilities into a single mash-up, add them to an existing HTML page, or make them into a gadget.  Second, GFC applications are linked to Google's single sign-on system.  Google may not have the social networking depth of Facebook, but they do have untold millions of accounts through GMail, Groups, and other popular online tools.  This allows you to create miniature social networks for a particular GFC application out of your gmail contact list.  Each GFC application is associated with a particular URL (and generated site ID), so you can create multiple networks out of the pool of Google accounts.

In subsequent posts, I'll look at GFC in a little more detail to examine its potential for science gateway social networks.

Thursday, December 03, 2009

Mass Imports of Maven Repositories into Archiva

Credit goes to Fugang Wang for this crafty trick.  Archiva is a Web-based system for managing your Maven repositories.  Unfortunately the Web interface only allows you to upload one jar at a time, and you also need to provide metadata (artifactId, version, group, etc).  This isn't very useful if you need to import a few dozen or more jars.

Here is how to import an existing local repository (that is, your $HOME/.m2 directory) into Archiva:

1. Create a new named repository, say cyberaide. Then you'll get a directory 'cyberaide' under the archiva installation directory.
2. Move the whole repo directory under the newly created directory.
3. Click the 'Repository scanning' button for your repository. This will 'repair' the artifacts db so the newly added artifacts would show up.