Monday, May 14, 2012

Install PLJava Postgresql 8.4 on Ubuntu LTS 10.04 Lucid Lynx

Before you can use java stored procedures on PostreSQL you have to install PLJava. The following assumes you already have Java 6 (Sun/Oracle or OpenJdk) installed on your machine.

Download the PLJava binary

This binary can be found on the pljava page of PgGoundry. Since we are running a 64 bit server with PostgreSQL 8.4 we used this distribution.

Next we extract this tar.gz file in the newly created directory /usr/src/pljava.

Setup PLJava

Next we create a file /etc/ld.so.conf.d/postgres.conf with the following content:

 /usr/lib/jvm/java-6-sun/lib
 /usr/lib/jvm/java-6-sun/jre/lib/amd64/server
Now run:
sudo ldconfig
Next edit the /etc/postgresql/8.4/main/postgresql.conf and add the following lines:
custom_variable_classes = 'pljava' 
pljava.classpath = '/usr/lib/postgresql/8.4/lib/pljava.jar'
Now copy the files pljava.jar and pljava.so to /usr/lib/postgresql/8.4/lib:
sudo cp /usr/src/pljava/pljava.jar /usr/lib/postgresql/8.4/lib
sudo cp /usr/src/pljava/pljava.so /usr/lib/postgresql/8.4/lib
Finally restart postgres:
sudo /etc/init.d/postgresql-8.4 restart

Install PLJava

Go to the /usr/src/pljava directory and run:

sudo su postgres -c "/usr/bin/psql -d template1 -f /usr/src/pljava/install.sql"
Where template1 is the database you want to install pljava. The result should look simular to this:
CREATE SCHEMA
GRANT
CREATE FUNCTION
CREATE LANGUAGE
CREATE FUNCTION
CREATE LANGUAGE
psql:/usr/src/pljava/install.sql:23: NOTICE:  CREATE TABLE will create implicit sequence "jar_repository_jarid_seq" for serial column "jar_repository.jarid"
psql:/usr/src/pljava/install.sql:23: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "jar_repository_pkey" for table "jar_repository"
psql:/usr/src/pljava/install.sql:23: NOTICE:  CREATE TABLE / UNIQUE will create implicit index "jar_repository_jarname_key" for table "jar_repository"
CREATE TABLE
GRANT
psql:/usr/src/pljava/install.sql:32: NOTICE:  CREATE TABLE will create implicit sequence "jar_entry_entryid_seq" for serial column "jar_entry.entryid"
psql:/usr/src/pljava/install.sql:32: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "jar_entry_pkey" for table "jar_entry"
psql:/usr/src/pljava/install.sql:32: NOTICE:  CREATE TABLE / UNIQUE will create implicit index "jar_entry_jarid_key" for table "jar_entry"
CREATE TABLE
GRANT
ALTER TABLE
psql:/usr/src/pljava/install.sql:43: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "classpath_entry_pkey" for table "classpath_entry"
CREATE TABLE
GRANT
psql:/usr/src/pljava/install.sql:50: NOTICE:  CREATE TABLE will create implicit sequence "typemap_entry_mapid_seq" for serial column "typemap_entry.mapid"
psql:/usr/src/pljava/install.sql:50: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "typemap_entry_pkey" for table "typemap_entry"
CREATE TABLE
GRANT
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
You are now ready to use Java stored procedures on PostreSQL 8.4 !!!

Test PLJava

To verify that it all works we are going to install a java stored procedure. Open your favorite SQL-editor and connect to the database and run:

CREATE FUNCTION getsysprop(VARCHAR)
  RETURNS VARCHAR
  AS 'java.lang.System.getProperty'
  LANGUAGE java;
Now run:
SELECT getsysprop('user.home');
This should return something like:
 getsysprop
 -------------------
 /var/lib/postgresql

Many thanks to BANYM'S BLOG for the inspiration for this blog.

Saturday, May 12, 2012

Authentication in Seam using Google OAuth 2.0 for Google Apps integration

This blog explains how you can use Google's OAuth 2.0 so users can login to your Seam application using their google account. Using this technique it is possible to integrate your Seam application with Google Apps.

Click here to go to the demo application

Currently there are several ways to authenticate users in your seam application with a google account. Unfortunately the OAuth 1.0 protocol is depricated since april 2012. If you want to integrate your Seam application with Google Apps it's best to use OAuth 2.0.

Setup Google API in your Goolge account

Before you can use Google's OAuth 2.0 for your own web application you have to setup a client ID for your web application. This can be done via API console.

The client ID and Client Secret are used by the web application to call the Google Apps Api.

Setup Seam security

In the WEB-INF/compenents.xml we add the authentication-method:

<security:identity authenticate-method="#{oAuth2Action.checkGoogleApiAuthorization}"/>
This method is triggered from the #{identity.login} action in the login screen. In the WEB-INF/pages.xml we define a login-view-id and the pages that require login:
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd"
 login-view-id="/viewsPublic/login.xhtml">
 
 <page view-id="/oauth2.xhtml" action="#{oAuth2Action.getGoogleApiAuthorizationToken}" /> 
 ... 
 <page view-id="/views/*" login-required="true" />

</pages>
All pages in the /views folder require login, calling a page in this folder when the user is not logged in, will trigger a call to the viewsPublic/login.xhtml page. The view /oauth2.xhtml is called by Google after successful authentication on the Google website. In the viewsPublic/login.xhtml we define the login screen:
...
<h:commandLink title="#{messages['login']}" action="#{identity.login}" >
 <h:graphicImage value="/images/logo_google.png" />
</h:commandLink>
...
Clicking on this link in the login page will start the authentication process:
  • The #{identity.login} action will call the #{oAuth2Action.checkGoogleApiAuthorization} method, this method redirects the user to the Google login screen
  • After successful authentication on the Google website the user is redirected to the /oauth2.seam view, this will call #{oAuth2Action.getGoogleApiAuthorizationToken} method. This method retrieves a Google Token to communicate with the Google API, this token is used to fetch the User Profile information.

Setup Google API in your application

Google provides Java classes to call the Google API, more information can be found here. In this demo we only use the following dependencies:

<dependency>
 <groupId>com.google.api-client</groupId>
 <artifactId>google-api-client-servlet</artifactId>
 <version>1.8.0-beta</version>
 <exclusions>
  <exclusion>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
  </exclusion>
  <exclusion>
   <groupId>javax.transaction</groupId>
   <artifactId>transaction-api</artifactId>
  </exclusion>
 </exclusions>
 <scope>compile</scope>
</dependency>
<dependency>
 <groupId>com.google.apis</groupId>
 <artifactId>google-api-services-calendar</artifactId>
 <version>v3-rev3-1.5.0-beta</version>
 <scope>compile</scope>
</dependency>
<dependency>
 <groupId>com.google.apis</groupId>
 <artifactId>google-api-services-oauth2</artifactId>
 <version>v2-rev3-1.5.0-beta</version>
 <scope>compile</scope>
</dependency>
In this demo application we have abstracted all calls to the Google API in one service class.
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;
import com.google.api.client.auth.oauth2.MemoryCredentialStore;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Userinfo;

@Name("googleApiService")
@Scope(ScopeType.APPLICATION)
@AutoCreatepublic class GoogleApiService implements Serializable {

 private static final long serialVersionUID = -4366524069518491233L;
 
 private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
 private static final JsonFactory JSON_FACTORY = new JacksonFactory(); 
 private static final List SCOPES = Arrays.asList(
   "https://www.googleapis.com/auth/userinfo.profile",
   "https://www.googleapis.com/auth/userinfo.email");

 private static AuthorizationCodeFlow authorizationCodeFlow;
 private static String clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 private static String clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
 
 static {
  authorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder(
    HTTP_TRANSPORT, JSON_FACTORY, clientId, clientSecret, SCOPES)
    .setCredentialStore(new MemoryCredentialStore()).build();
 }
 
 public boolean hasCredentials(String userId) {
  if (userId != null) {
   return authorizationCodeFlow.loadCredential(userId) != null;
  }
  return false;
 }
 
 public String getAuthorizationUrl(String redirectUrl) {
  return authorizationCodeFlow.newAuthorizationUrl().setRedirectUri(redirectUrl).build();
 }
 
 public void setCredentials(HttpServletRequest request, String redirectUrl, String userId) throws GoogleAuthorizationException {
  
  StringBuffer fullUrlBuf = request.getRequestURL();
  if (request.getQueryString() != null) {
   fullUrlBuf.append('?').append(request.getQueryString());
  }
  AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(
    fullUrlBuf.toString());
  if (authResponse.getError() != null) {
   throw new GoogleAuthorizationException(authResponse.getError());
  } else {
   try {
    GoogleTokenResponse response = new GoogleAuthorizationCodeTokenRequest(
      HTTP_TRANSPORT, JSON_FACTORY, clientId, clientSecret,
      authResponse.getCode(), redirectUrl).execute();
    authorizationCodeFlow.createAndStoreCredential(response,
      userId);    
   } catch (IOException e) {
    throw new GoogleAuthorizationException(e);
   }
  }
 }
 
 public void removeCredentials(String userId) {
  authorizationCodeFlow.getCredentialStore().delete(userId, authorizationCodeFlow.loadCredential(userId));
 }
 
 public Userinfo getUserInfo(String userId) throws GoogleAuthorizationException {
  Oauth2 userInfoService = Oauth2.builder(HTTP_TRANSPORT, JSON_FACTORY)
    .setHttpRequestInitializer(authorizationCodeFlow.loadCredential(userId)).build();
  Userinfo userInfo = null;
  try {
   userInfo = userInfoService.userinfo().get().execute();
  } catch (IOException e) {
   throw new GoogleAuthorizationException(e);
  }
  if (userInfo != null && userInfo.getId() != null) {
   return userInfo;
  } else {
   throw new GoogleAuthorizationException("No userinfoId exception");
  }
 }

}
This GoogleApiService uses the client ID and client Secret provided by the Google API Console. This service is used by the OAuth2Action.class:
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.faces.FacesManager;
import org.jboss.seam.faces.Redirect;
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.SimplePrincipal;
import org.jboss.seam.web.ServletContexts;

import com.google.api.services.oauth2.model.Userinfo;

@Name("oAuth2Action")
@Scope(ScopeType.SESSION)
public class OAuth2Action implements Serializable {

 private static final long serialVersionUID = -4366524069518498663L;
 
 @Out(scope=ScopeType.SESSION, required=false)
 private Userinfo userinfo;

 @In
 protected GoogleApiService googleApiService;
 
 @In
 protected Identity identity; 

 public String returnToUrl() {
  FacesContext context = FacesContext.getCurrentInstance();
  HttpServletRequest request = (HttpServletRequest) context
    .getExternalContext().getRequest();

  try {
   URL returnToUrl;
   if (request.getServerPort() == 80) {
    returnToUrl = new URL("http", request.getServerName(), context
      .getApplication().getViewHandler()
      .getActionURL(context, "/oauth2.xhtml"));
   } else {
    returnToUrl = new URL("http", request.getServerName(),
      request.getServerPort(), context.getApplication()
        .getViewHandler()
        .getActionURL(context, "/oauth2.xhtml"));

   }
   return returnToUrl.toExternalForm();
  } catch (MalformedURLException e) {
   throw new RuntimeException(e);
  }
 }

 public void checkGoogleApiAuthorization() throws IOException,
   GoogleAuthorizationException {
  
  String url = googleApiService.getAuthorizationUrl(returnToUrl());
  if (url != null) {
   Redirect redirect = Redirect.instance();
   redirect.captureCurrentView();
   FacesManager.instance().redirectToExternalURL(url);
  } else {
   throw new GoogleAuthorizationException(
     "No authorizationUrl definied");
  }
 }

 public String getGoogleApiAuthorizationToken() throws GoogleAuthorizationException {
  ExternalContext context = javax.faces.context.FacesContext
    .getCurrentInstance().getExternalContext();
  HttpServletRequest request = (HttpServletRequest) context.getRequest();
  
  googleApiService.setCredentials(request, returnToUrl(),
    ServletContexts.instance().getRequest().getSession().getId());
  
  userinfo = googleApiService.getUserInfo(ServletContexts.instance().getRequest().getSession().getId());
  
  identity.acceptExternallyAuthenticatedPrincipal(new SimplePrincipal(userinfo.getName()));

  return "userinfo";

 }
 
 @Observer(Identity.EVENT_LOGGED_OUT)
 public String logout() {
  googleApiService.removeCredentials(ServletContexts.instance().getRequest().getSession().getId());
  return "start";
 }

}