Wednesday, November 28, 2012

Remote Debugging Client Side GWT Code




A while ago I had to remotely debug client side code of a GWT application already compiled and deployed  onto a server. After searching around and reading this document a few times, I finally managed to do it following these steps:
  1. Compile and install the application on the server and write down the URL (like http://remote.host:8080/myapp/index.jsp).
  2. Create an Application launcher (Run->Edit Configurations->Applications in IntelliJ).
    • Set the main class to: com.google.gwt.dev.DevMode
    • Put this line in the program arguments: -noserver -war target/myapp -logLevel DEBUG -startupUrl http://remote.host:8080/myapp/index.jsp -port 8080 my.package.myapp.Module
  3. Debug this new run configuration. This will run the GWT Dev Mode and enable you to set breakpoints on the client side code.
  4. Copy and paste URL provided by Dev Mode into your browser (like http://remote.host:8080/myapp/indexjsp?gwt.codesvr=127.0.0.1:9997). You will need to modify Dev Mode extension options to allow this new combination (of app server and code server). On Chrome, for example, you can do this by clicking on the greyed out Dev Mode logo if you get an error.
  5. Remotely debug the server in the normal way (JDWP) to be able to debug server-side code as well.

Friday, November 9, 2012

Expired/Expiring passwords and CAS

Once I had CAS working with our users database, the next step was to support expired user passwords or those in the grace password. CAS supported neither of those cases out of the box. Searching for it landed me on this page describing a solution, but it was old and not compatible with a fairly recent CAS source code. It was also coupled with LDAP which I didn't need.
After spending some time, I finally managed to extend CAS to provide a generic enough support for such cases. With no more ado, I am providing you a patch here that can be applied to the 3.4.11 version (and later version with possibly not much more effort). In order to enjoy this, just download CAS from here and apply the patch provided.
Though, I could extend from CAS Maven module (as described here) but then I had to repeat so many things because of a small change, making things more confusing. I am hoping to get this patch added to the main code, if guys over there are happy with it.


Monday, August 13, 2012

Android app to send emails from GMail using OAuth for authorisation

UPDATE: This no longer works, since OAuth 1 is no longer supported by Google.
 
It is appalling to see how few documents and sample codes exist for something this simple (check this): I want an app to authorise me to GMail through OAuth and let me send emails.

I am going to release parts of my project that deals with this as a complete working application, sounds good ha? The bad news is that I'm using OAuth 1 which is officially deprecated by Google, yet they don't advise on how to do the same with OAuth 2 as many things are different in the new protocol.

Feel free to download my code here or check it out from SVN:
svn checkout http://tinywebgears-samples.googlecode.com/svn/trunk/email-oauth/ tinywebgears-samples-read-only
You can import it in Eclipse once you have checked it out, enjoy.


While you are here, it is worthwhile to take you through some important parts of my code. Everything mentioned here is already put in the SVN module, so you don't need to copy/paste from here.

As you can see I'm using a patched version of commons-net library, to be able to pass "AUTH XOAUTH BASE64_TOKEN" to the SMTP server (update: this feature is available in commons-net, from this revision onwards):
diff -ru commons-net-3.1-src/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java commons-net-3.1-src-patched/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java
--- commons-net-3.1-src/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java 2012-02-15 03:11:49.000000000 +1100
+++ commons-net-3.1-src-patched/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java 2012-07-25 00:36:30.809267711 +1000
@@ -208,6 +208,10 @@
             }
             return SMTPReply.isPositiveCompletion(sendCommand(
                 new String(Base64.encodeBase64(password.getBytes()))));
+        }
+        else if (method.equals(AUTH_METHOD.XOAUTH))
+        {
+            return SMTPReply.isPositiveIntermediate(sendCommand(username));
         } else {
             return false; // safety check
         }
@@ -243,7 +247,9 @@
         /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */
         CRAM_MD5,
         /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */
-        LOGIN;
+        LOGIN,
+        /** XOAuth method which accepts a signed and base64ed OAuth URL. */
+        XOAUTH;

         /**
          * Gets the name of the given authentication method suitable for the server.
@@ -258,6 +264,8 @@
                 return "CRAM-MD5";
             } else if (method.equals(AUTH_METHOD.LOGIN)) {
                 return "LOGIN";
+            } else if (method.equals(AUTH_METHOD.XOAUTH)) {
+                return "XOAUTH";
             } else {
                 return null;
             }
The main OAuth functions are performed by a helper class, which comprise, by their appearing order:
  • creating a request token,
  • making an authorization url,
  • getting an access token using the verifier code passed back by Google,
  • making a secure request to the user details API to get user's email address,
  • and finally making an XOAUTH string every time an email is due to be sent.
One thing that took me a while to figure out was that the access token is long-lived and almost never expires (unless the user revokes it), but the XOAUTH string has a very short life-time and hence must be generated quite frequently.

Let's have a look at important parts of the code. Using Scribe library my code is much less than when I used Signpost:

package com.tinywebgears.gmailoauth.util;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.GoogleApi;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

public class OAuthHelperV1 implements OAuthHelper
{
    public static final String OAUTH_APP_KEY = "anonymous";
    public static final String OAUTH_APP_SECRET = "anonymous";
    public static final String URL_OAUTH_AUTHORIZE = "https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=";
    public static final String OAUTH_PARAM_VERIFIER = "oauth_verifier";

    private OAuthService service;
    private Token requestToken;

    private static OAuthHelperV1 oauthHelper;

    static
    {
        if (oauthHelper == null)
        {
            String scope = OAUTH_SCOPE_EMAIL + " " + OAUTH_SCOPE_GMAIL;
            oauthHelper = new OAuthHelperV1(OAUTH_APP_KEY, OAUTH_APP_SECRET, scope, OAUTH_CALLBACK_URL, OAUTH_APP_NAME);
        }
    }

    public static OAuthHelperV1 get()
    {
        return oauthHelper;
    }

    public OAuthHelperV1(String consumerKey, String consumerSecret, String scope, String callbackUrl, String appname)
    {
        service = new ServiceBuilder().provider(GoogleApi.class).apiKey(consumerKey).apiSecret(consumerSecret)
                .scope(scope).callback(callbackUrl).build();
    }

    @Override
    public String getAuthorizationUrl()
    {
        requestToken = service.getRequestToken();
        return URL_OAUTH_AUTHORIZE + requestToken.getToken();
    }

    @Override
    public String extractVerifier(String uri)
    {
        if (uri.indexOf(OAUTH_PARAM_VERIFIER + "=") != -1)
            return UriUtil.getParam(uri, OAUTH_PARAM_VERIFIER);
        return null;
    }

    @Override
    public Token getAccessToken(String verifier)
    {
        Token accessToken = service.getAccessToken(requestToken, new Verifier(verifier));
        return accessToken;
    }

    @Override
    public String makeSecuredRequest(Token accessToken, String url)
    {
        OAuthRequest request = new OAuthRequest(Verb.GET, url);
        service.signRequest(accessToken, request);
        request.addHeader("GData-Version", "3.0");
        Response response = request.send();
        System.out.println("Got it! Lets see what we found...");
        System.out.println();
        System.out.println(response.getCode());
        System.out.println(response.getBody());
        return response.getBody();
    }

    @Override
    public void signRequest(Token accessToken, OAuthRequest request)
    {
        service.signRequest(accessToken, request);
    }
}

Here is also the code that sends emails:

package com.tinywebgears.gmailoauth.mail;

import java.io.PrintWriter;
import java.io.Writer;
import java.security.AccessController;
import java.security.Provider;
import java.security.Security;

import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.smtp.AuthenticatingSMTPClient;
import org.apache.commons.net.smtp.SMTPReply;
import org.apache.commons.net.smtp.SimpleSMTPHeader;

import android.util.AndroidRuntimeException;
import android.util.Log;

public class OAuthGMailSender
{
    private static final String TAG = "OAuthGMailSender";

    private String mailhost = "smtp.gmail.com";
    private Integer mailport = 587;
    private String xoauthToken;

    static
    {
        Security.addProvider(new JSSEProvider());
    }

    public OAuthGMailSender(String xoauthToken)
    {
        this.xoauthToken = xoauthToken;

    }

    public synchronized void sendMail(String subject, String body, String sender, String recipient) throws Exception
    {
        Log.d(TAG, "Sending email...");
        SimpleSMTPHeader header = new SimpleSMTPHeader(sender, recipient, subject);

        AuthenticatingSMTPClient client = new AuthenticatingSMTPClient();
        client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
        client.connect(mailhost, mailport);
        client.login();
        client.execTLS();
        client.auth(AuthenticatingSMTPClient.AUTH_METHOD.XOAUTH, xoauthToken, null);

        if (!SMTPReply.isPositiveCompletion(client.getReplyCode()))
        {
            Log.e(TAG, "SMTP Authentication failed: " + client.getReplyCode() + ":" + client.getReplyString());
            client.disconnect();
            throw new AndroidRuntimeException("SMTP Authentication failed.");
        }

        client.setSender(sender);
        client.addRecipient(recipient);

        Writer writer = client.sendMessageData();
        if (writer != null)
        {
            writer.write(header.toString());
            writer.write(body);
            writer.write("\n.\n");
            writer.close();
            client.completePendingCommand();
        }

        client.logout();

        client.disconnect();
    }

    @SuppressWarnings("serial")
    public static final class JSSEProvider extends Provider
    {
        public JSSEProvider()
        {
            super("HarmonyJSSE", 1.0, "Harmony JSSE Provider");
            AccessController.doPrivileged(new java.security.PrivilegedAction<Void>()
            {
                public Void run()
                {
                    put("SSLContext.TLS", "org.apache.harmony.xnet.provider.jsse.SSLContextImpl");
                    put("Alg.Alias.SSLContext.TLSv1", "TLS");
                    put("KeyManagerFactory.X509", "org.apache.harmony.xnet.provider.jsse.KeyManagerFactoryImpl");
                    put("TrustManagerFactory.X509", "org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl");
                    return null;
                }
            });
        }
    }
}


Read less interesting stuff yourselves!

Sunday, August 5, 2012

Google OAuth 2 with Scribe on Android

Update: See this post for the latest on this, and links to the source code.

I started off with Nilvec's valuable blog post to do oauth on my android application using signpost library, which was great except it only supported the first version of oauth (already deprecated). The problem with it (rather than deprecation) is not having anything like a refresh token. Once the access token expires, after an hour, your application stops working and you have to wake up the user to re-grant you, such a pain! Update: I was wrong. The access token is actually long-lived but you need to regenerate a  fresh XOAUTH string to give to SMTP server. You don't need the concept of refresh token in OAuth1, that's why it isn't there!

Then I went to try Scribe (version 1.3.1, available here). It is a very nice library, but unfortunately it doesn't inherently support Google's OAuth 2 at the time of writing. Although it is not very hard to implement one, but I happened to be lucky enough to find one already done. I had to slightly modify it to get it to work with my installed application client ID (where there is no client secret):

package org.scribe.builder.api;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.scribe.exceptions.OAuthException;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.OAuthConstants;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuth20ServiceImpl;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.OAuthEncoder;
import org.scribe.utils.Preconditions;

/**
 * Google OAuth2.0
 * Released under the same license as scribe (MIT License)
 * @author yincrash
 *
 */
public class Google2Api extends DefaultApi20 {

    private static final String AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s";
    private static final String SCOPED_AUTHORIZE_URL = AUTHORIZE_URL + "&scope=%s";

    @Override
    public String getAccessTokenEndpoint() {
        return "https://accounts.google.com/o/oauth2/token";
    }

    @Override
    public AccessTokenExtractor getAccessTokenExtractor() {
        return new AccessTokenExtractor() {

            public Token extract(String response) {
                Preconditions.checkEmptyString(response, "Response body is incorrect. Can't extract a token from an empty string");

                Matcher matcher = Pattern.compile("\"access_token\" : \"([^&\"]+)\"").matcher(response);
                if (matcher.find())
                {
                    String token = OAuthEncoder.decode(matcher.group(1));
                    return new Token(token, "", response);
                }
                else
                {
                    throw new OAuthException("Response body is incorrect. Can't extract a token from this: '" + response + "'", null);
                }
            }
        };
    }

    @Override
    public String getAuthorizationUrl(OAuthConfig config) {
        // Append scope if present
        if (config.hasScope()) {
            return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()),
                    OAuthEncoder.encode(config.getScope()));
        } else {
            return String.format(AUTHORIZE_URL, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()));
        }
    }

    @Override
    public Verb getAccessTokenVerb() {
        return Verb.POST;
    }

    @Override
    public OAuthService createService(OAuthConfig config) {
        return new GoogleOAuth2Service(this, config);
    }

    private class GoogleOAuth2Service extends OAuth20ServiceImpl {

        private static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
        private static final String GRANT_TYPE = "grant_type";
        private DefaultApi20 api;
        private OAuthConfig config;

        public GoogleOAuth2Service(DefaultApi20 api, OAuthConfig config) {
            super(api, config);
            this.api = api;
            this.config = config;
        }

        @Override
        public Token getAccessToken(Token requestToken, Verifier verifier) {
            OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
            switch (api.getAccessTokenVerb()) {
                case POST:
                    request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
                    // TODO HA: API Secret is optional
                    if (config.getApiSecret() != null && config.getApiSecret().length() > 0)
                        request.addBodyParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
                    request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
                    request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
                    request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE);
                    break;
                case GET:
                default:
                    request.addQuerystringParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
                    // TODO HA: API Secret is optional
                    if (config.getApiSecret() != null && config.getApiSecret().length() > 0)
                        request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
                    request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue());
                    request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
                    if(config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope());
            }
            Response response = request.send();
            return api.getAccessTokenExtractor().extract(response.getBody());
        }
    }
}

My modifications are marked by the "TODO HA" string. I also had to modify a few other places to allow null/empty client secret.

package org.scribe.builder;

import java.io.*;
import org.scribe.builder.api.*;
import org.scribe.exceptions.*;
import org.scribe.model.*;
import org.scribe.oauth.*;
import org.scribe.utils.*;

/**
 * Implementation of the Builder pattern, with a fluent interface that creates a
 * {@link OAuthService}
 * 
 * @author Pablo Fernandez
 *
 */
public class ServiceBuilder
{
  private String apiKey;
  private String apiSecret;
  private String callback;
  private Api api;
  private String scope;
  private SignatureType signatureType;
  private OutputStream debugStream;
  
  /**
   * Default constructor
   */
  public ServiceBuilder()
  {
    this.callback = OAuthConstants.OUT_OF_BAND;
    this.signatureType = SignatureType.Header;
    this.debugStream = null;
  }
  
  /**
   * Configures the {@link Api}
   * 
   * @param apiClass the class of one of the existent {@link Api}s on org.scribe.api package
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder provider(Class<? extends Api> apiClass)
  {
    this.api = createApi(apiClass);
    return this;
  }

  private Api createApi(Class<? extends Api> apiClass)
  {
    Preconditions.checkNotNull(apiClass, "Api class cannot be null");
    Api api;
    try
    {
      api = apiClass.newInstance();  
    }
    catch(Exception e)
    {
      throw new OAuthException("Error while creating the Api object", e);
    }
    return api;
  }

  /**
   * Configures the {@link Api}
   *
   * Overloaded version. Let's you use an instance instead of a class.
   *
   * @param api instance of {@link Api}s
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder provider(Api api)
  {
   Preconditions.checkNotNull(api, "Api cannot be null");
   this.api = api;
   return this;
  }

  /**
   * Adds an OAuth callback url
   * 
   * @param callback callback url. Must be a valid url or 'oob' for out of band OAuth
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder callback(String callback)
  {
    Preconditions.checkNotNull(callback, "Callback can't be null");
    this.callback = callback;
    return this;
  }
  
  /**
   * Configures the api key
   * 
   * @param apiKey The api key for your application
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder apiKey(String apiKey)
  {
    Preconditions.checkEmptyString(apiKey, "Invalid Api key");
    this.apiKey = apiKey;
    return this;
  }
  
  /**
   * Configures the api secret
   * 
   * @param apiSecret The api secret for your application
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder apiSecret(String apiSecret)
  {
    // TODO HA: Commented out
    //Preconditions.checkEmptyString(apiSecret, "Invalid Api secret");
    this.apiSecret = apiSecret;
    return this;
  }
  
  /**
   * Configures the OAuth scope. This is only necessary in some APIs (like Google's).
   * 
   * @param scope The OAuth scope
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder scope(String scope)
  {
    Preconditions.checkEmptyString(scope, "Invalid OAuth scope");
    this.scope = scope;
    return this;
  }

  /**
   * Configures the signature type, choose between header, querystring, etc. Defaults to Header
   *
   * @param scope The OAuth scope
   * @return the {@link ServiceBuilder} instance for method chaining
   */
  public ServiceBuilder signatureType(SignatureType type)
  {
    Preconditions.checkNotNull(type, "Signature type can't be null");
    this.signatureType = type;
    return this;
  }

  public ServiceBuilder debugStream(OutputStream stream)
  {
    Preconditions.checkNotNull(stream, "debug stream can't be null");
    this.debugStream = stream;
    return this;
  }

  public ServiceBuilder debug()
  {
    this.debugStream(System.out);
    return this;
  }
  
  /**
   * Returns the fully configured {@link OAuthService}
   * 
   * @return fully configured {@link OAuthService}
   */
  public OAuthService build()
  {
    Preconditions.checkNotNull(api, "You must specify a valid api through the provider() method");
    Preconditions.checkEmptyString(apiKey, "You must provide an api key");
    // TODO HA: Commented out
    // Preconditions.checkEmptyString(apiSecret, "You must provide an api secret");
    return api.createService(new OAuthConfig(apiKey, apiSecret, callback, signatureType, scope, debugStream));
  }
}

package org.scribe.oauth;

import org.scribe.builder.api.*;
import org.scribe.model.*;

public class OAuth20ServiceImpl implements OAuthService
{
  private static final String VERSION = "2.0";
  
  private final DefaultApi20 api;
  private final OAuthConfig config;
  
  /**
   * Default constructor
   * 
   * @param api OAuth2.0 api information
   * @param config OAuth 2.0 configuration param object
   */
  public OAuth20ServiceImpl(DefaultApi20 api, OAuthConfig config)
  {
    this.api = api;
    this.config = config;
  }

  /**
   * {@inheritDoc}
   */
  public Token getAccessToken(Token requestToken, Verifier verifier)
  {
    OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
    request.addQuerystringParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
    // TODO HA: API Secret is optional
    if (config.getApiSecret() != null && config.getApiSecret().length() > 0)
          request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
    request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue());
    request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
    if(config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope());
    Response response = request.send();
    return api.getAccessTokenExtractor().extract(response.getBody());
  }

  /**
   * {@inheritDoc}
   */
  public Token getRequestToken()
  {
    throw new UnsupportedOperationException("Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
  }

  /**
   * {@inheritDoc}
   */
  public String getVersion()
  {
    return VERSION;
  }

  /**
   * {@inheritDoc}
   */
  public void signRequest(Token accessToken, OAuthRequest request)
  {
    request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken.getToken());
  }

  /**
   * {@inheritDoc}
   */
  public String getAuthorizationUrl(Token requestToken)
  {
    return api.getAuthorizationUrl(config);
  }
}

Here is a sample java code that uses this to get a user's email address:

package org.scribe.examples;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.FacebookApi;
import org.scribe.builder.api.Google2Api;
import org.scribe.model.*;
import org.scribe.oauth.OAuthService;

import java.util.Scanner;

public class Google2Example
{
    private static final String NETWORK_NAME = "Google";
    private static final String PROTECTED_RESOURCE_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
    private static final String SCOPE = "https://mail.google.com/ https://www.googleapis.com/auth/userinfo.email";
    private static final Token EMPTY_TOKEN = null;

    public static void main(String[] args)
  {
      // Client ID for web applications
      // String apiKey = "random-string.apps.googleusercontent.com";
      // String apiSecret = "client-secret-generated-by-google";
      // String callbackUrl = "https://code.google.com/oauthplayground";
      // Client ID for installed applications (there is no secret)
      String apiKey = "random-string.apps.googleusercontent.com";
      String apiSecret = "";
      String callbackUrl = "http://localhost";

      OAuthService service = new ServiceBuilder()
                                  .provider(Google2Api.class)
                                  .apiKey(apiKey)
                                  .apiSecret(apiSecret)
                                  .callback(callbackUrl)
                                  .scope(SCOPE)
                                  .build();
    Scanner in = new Scanner(System.in);

    System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ===");
    System.out.println();

    // Obtain the Authorization URL
    System.out.println("Fetching the Authorization URL...");
    String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN);
    System.out.println("Got the Authorization URL!");
    System.out.println("Now go and authorize Scribe here:");
    System.out.println(authorizationUrl);
    System.out.println("And paste the authorization code here");
    System.out.print(">>");
    Verifier verifier = new Verifier(in.nextLine());
    System.out.println();
    
    // Trade the Request Token and Verfier for the Access Token
    System.out.println("Trading the Request Token for an Access Token...");
    Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier);
    System.out.println("Got the Access Token!");
    System.out.println("(if your curious it looks like this: " + accessToken + " )");
    System.out.println();

    // Now let's go and ask for a protected resource!
    System.out.println("Now we're going to access a protected resource...");
    OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
    service.signRequest(accessToken, request);
    Response response = request.send();
    System.out.println("Got it! Lets see what we found...");
    System.out.println();
    System.out.println(response.getCode());
    System.out.println(response.getBody());

    System.out.println();
    System.out.println("Thats it man! Go and build something awesome with Scribe! :)");

  }
}

Sorry if there is no complete code as I'm still experimenting with all this stuff.

Tuesday, May 29, 2012

CAS Attribute Release, Backed by Database

Earlier in this post, I provided a CAS security server that would authenticate a user and release their attributes as a list of granted authorities to be used in Spring to provide access control on different parts of the application.

This post will be on something very similar to that, with the difference of going to a database instead. The complete source code of a Maven based project can be seen here, or checked out this way:
svn co http://tinywebgears-samples.googlecode.com/svn/trunk/sso-cas-spring-db sso-cas-spring-db

In order to run this sample application you need to set up a database and change the connection properties in the cas.properties file as necessary (it defaults to MySQL though). The application uses binding method to authenticate the user, meaning you need to be able to log in directly to the database using username/password provided. It then will go to a different database (configured in the same place) and fetch more information about the user from a databse table. This table is very simple and can be genereted using this script (also provided in the source tree):

CREATE TABLE `USER_DATA` (
  `uid` varchar(255) NOT NULL,
  `email` varchar(255) DEFAULT NULL,
  `roles` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Contents of the roles column in this table will be converted to granted authorities for Spring.

Since the code is very similar to the LDAP-based one, I will onlly show the major differences here. You can easily investigate the source code if you are interested.

The cas.properties is where most of the configuration takes place:
server.prefix=https://localhost:8443/cas

cas.securityContext.serviceProperties.service=${server.prefix}/services/j_acegi_cas_security_check
# Names of roles allowed to access the CAS service manager
cas.securityContext.serviceProperties.adminRoles=ROLE_ADMINISTRATORS
cas.securityContext.casProcessingFilterEntryPoint.loginUrl=${server.prefix}/login
cas.securityContext.ticketValidator.casServerUrlPrefix=${server.prefix}


cas.themeResolver.defaultThemeName=cas-theme-default
cas.viewResolver.basename=default_views

host.name=localhost

#database.hibernate.dialect=org.hibernate.dialect.OracleDialect
database.hibernate.dialect=org.hibernate.dialect.MySQLDialect
#database.hibernate.dialect=org.hibernate.dialect.HSQLDialect
database.url=jdbc:mysql://localhost/cas
database.username=cas
database.password=cas
database.driver.class=com.mysql.jdbc.Driver

users.database.url=jdbc:mysql://localhost/userdata
users.database.username=cas
users.database.password=cas
users.database.driver.class=com.mysql.jdbc.Driver
#users.database.hibernate.dialect=org.hibernate.dialect.OracleDialect
users.database.hibernate.dialect=org.hibernate.dialect.MySQLDialect
#users.database.hibernate.dialect=org.hibernate.dialect.HSQLDialect

The real change goes into the deployerConfigContext.xml file. I have left comments in the file so that you can see what will change if you move from LDAP to database, or vice versa:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <bean id="authenticationManager"
          class="org.jasig.cas.authentication.AuthenticationManagerImpl">
        <property name="credentialsToPrincipalResolvers">
            <list>
                <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" >
                    <property name="attributeRepository">
                        <ref bean="attributeRepository"/>
                    </property>
                </bean>
                <!-- LDAP -->
                <!--<bean class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver">-->
                    <!--&lt;!&ndash; The Principal resolver form the credentials &ndash;&gt;-->
                    <!--<property name="credentialsToPrincipalResolver">-->
                        <!--<bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver"/>-->
                    <!--</property>-->
                    <!--&lt;!&ndash; The query made to find the Principal ID. "%u" will be replaced by the resolved Principal &ndash;&gt;-->
                    <!--<property name="filter" value="(uid=%u)"/>-->
                    <!--&lt;!&ndash; The attribute used to define the new Principal ID &ndash;&gt;-->
                    <!--<property name="principalAttributeName" value="cn"/>-->
                    <!--<property name="searchBase" value="ou=users,ou=system"/>-->
                    <!--<property name="contextSource" ref="contextSource"/>-->
                    <!--<property name="attributeRepository">-->
                        <!--<ref bean="attributeRepository"/>-->
                    <!--</property>-->
                <!--</bean>-->
                <bean class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver"/>
            </list>
        </property>

        <property name="authenticationHandlers">
            <list>
                <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
                 p:httpClient-ref="httpClient" />
                <!-- DB -->
                <bean class="org.jasig.cas.adaptors.jdbc.BindModeSearchDatabaseAuthenticationHandler">
                    <property name="dataSource" ref="usersDataSource" />
                </bean>
                <!-- LDAP -->
                <!--<bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">-->
                    <!--<property name="filter" value="uid=%u"/>-->
                    <!--<property name="searchBase" value="ou=users,ou=system"/>-->
                    <!--<property name="contextSource" ref="contextSource"/>-->
                <!--</bean>-->
            </list>
        </property>
    </bean>

    <bean id="userDetailsService"
          class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">
        <constructor-arg>
            <list>
                <value>authorities</value>
            </list>
        </constructor-arg>
    </bean>

    <!-- DB -->
    <bean id="attributeRepository" class="com.tinywebgears.sso.cas.server.DatabasePersonAttributeAndRoleDao">
        <constructor-arg index="0" ref="usersDataSource" />
        <constructor-arg index="1" value="SELECT * FROM USER_DATA WHERE {0}" />
        <property name="authoritiesColumnName" value="roles"/>
        <property name="defaultRole">
            <bean class="org.springframework.security.core.authority.GrantedAuthorityImpl">
                <constructor-arg index="0" value="ROLE_USER"/>
            </bean>
        </property>
        <property name="queryAttributeMapping">
            <map>
                <entry key="username" value="uid" />
            </map>
        </property>
        <property name="resultAttributeMapping">
            <map>
                <entry key="uid" value="username" />
                <entry key="email" value="email" />
                <entry key="roles" value="authorities" />
            </map>
        </property>
    </bean>
    <bean id="usersDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="${users.database.driver.class}"/>
        <property name="jdbcUrl" value="${users.database.url}"/>
        <property name="user" value="${users.database.username}"/>
        <property name="password" value="${users.database.password}"/>
    </bean>
    <!-- LDAP -->
    <!--<bean id="attributeRepository" class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeAndRoleDao">-->
        <!--<property name="contextSource" ref="contextSource"/>-->
        <!--<property name="baseDN" value="ou=users,ou=system"/>-->
        <!--<property name="requireAllQueryAttributes" value="true"/>-->
        <!--&lt;!&ndash; Attribute mapping between principal (key) and LDAP (value) names used to perform the LDAP search.-->
<!--By default, multiple search criteria are ANDed together.  Set the queryType property to change to OR. &ndash;&gt;-->
        <!--<property name="queryAttributeMapping">-->
            <!--<map>-->
                <!--<entry key="username" value="uid"/>-->
            <!--</map>-->
        <!--</property>-->
        <!--<property name="resultAttributeMapping">-->
            <!--<map>-->
                <!--&lt;!&ndash; Mapping beetween LDAP entry attributes (key) and Principal's (value) &ndash;&gt;-->
                <!--<entry key="mail" value="email"/>-->
                <!--<entry key="authorities" value="authorities"/>-->
            <!--</map>-->
        <!--</property>-->
        <!--<property name="ldapAuthoritiesPopulator" ref="ldapAuthoritiesPopulator"/>-->
    <!--</bean>-->
    <!--<bean id="ldapAuthoritiesPopulator"-->
          <!--class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">-->
        <!--<constructor-arg ref="contextSource"/>-->
        <!--<constructor-arg value="ou=groups,ou=system"/>-->
        <!--<property name="groupRoleAttribute" value="cn"/>-->
        <!--<property name="groupSearchFilter" value="(uniqueMember={0})"/>-->
    <!--</bean>-->
    <!--<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">-->
        <!--<property name="pooled" value="false"/>-->
        <!--<property name="urls">-->
            <!--<list>-->
                <!--<value>ldap://localhost:10389</value>-->
            <!--</list>-->
        <!--</property>-->
        <!--<property name="userDn" value="uid=admin,ou=system"/>-->
        <!--<property name="password" value="secret"/>-->
        <!--<property name="baseEnvironmentProperties">-->
            <!--<map>-->
                <!--<entry>-->
                    <!--<key>-->
                        <!--<value>java.naming.security.authentication</value>-->
                    <!--</key>-->
                    <!--<value>simple</value>-->
                <!--</entry>-->
            <!--</map>-->
        <!--</property>-->
    <!--</bean>-->

    <bean id="serviceRegistryDao" class="org.jasig.cas.services.JpaServiceRegistryDaoImpl"
          p:entityManagerFactory-ref="entityManagerFactory"/>
    <!-- This is the EntityManagerFactory configuration for Hibernate -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="casDataSource"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true"/>
                <property name="showSql" value="false"/>
            </bean>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager"/>
    <bean id="casDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="${database.driver.class}"/>
        <property name="jdbcUrl" value="${database.url}"/>
        <property name="user" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
    </bean>

</beans>
Please note that this application is set-up for HTTPS. If you haven't yet set up your application server for HTTPS yet, simply replace URLs from https://localhost:8443/ to http://localhost:8080/ in a few places.

Monday, April 2, 2012

When Dates go wild

It's really easy to get the dates wrong if you don't have a central place for date/time functions in your application, especially if you have a multi-tiered application where you can have different dates in the database server, application server, client side, etc (Let alone situations when your database or application is clustered on different servers).

It is a good practice to have a central place/strategy to provide current date when one is required, but there is no simple answer to where this central place should be. Whatever you come up with, you need to make sure that no one goes around it and do their own new Date(). It is really easy to avoid such a thing with a custom Checkstyle check, as you can see here:

import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class CentralPlaceForDateConstruction extends Check
{
private final static Set<String> PROHIBITED_CLASS_NAMES =
new HashSet<String>(Arrays.asList(
"Date", "GregorianCalendar",
"DateTime", "BaseDateTime", "MutableDateTime", "DateMidnight"));

public int[] getDefaultTokens()
{
return new int[]{TokenTypes.EXPR};
}

public void visitToken(DetailAST ast)
{
if (ast.getChildCount() == 1 && ast.branchContains(TokenTypes.LITERAL_NEW))
{
DetailAST newKeyworkAST = ast.findFirstToken(TokenTypes.LITERAL_NEW);
if (newKeyworkAST == null)
return;
DetailAST createdClassAST = newKeyworkAST.findFirstToken(TokenTypes.IDENT);
if (createdClassAST == null)
return;
if (PROHIBITED_CLASS_NAMES.contains(createdClassAST.getText()))
{
if (createdClassAST.getNextSibling().getType() != TokenTypes.ARRAY_DECLARATOR)
log(createdClassAST.getLineNo(),
"Use the central place for Date creation.");
}
}
}
}


Please see here for more information on developing a custom check, and probably here if you want to integrate your work with Maven.

Don't forget to use a suppression filter to ignore this for the central date/time manipulation classes.

P.S. I stumbled upon this on JodaTime's homepage: The 'J' in 'Joda' is pronounced the same as the 'J' in 'Java'. The project is not capable of wielding a light saber and is thus pronounced differently.

Monday, March 26, 2012

Single-Sign-On using CAS, Spring, and LDAP

In one of the previous posts, which turned out to be quite popular, I wrote about doing custom authentication with Spring. This time I am going to tackle the problem in a more proper and standardized way. This will be based on CAS, Spring, and LDAP.

In order to achieve what I want I'll set up a CAS server, and modify my Spring application to use CAS for authentication. Once this step passed, I'll go ahead and bind the CAS server to a backing LDAP which provides user information, and finally populate group memberships in LDAP as user roles to be used for authorisation. I'll frequently refer to CAS and Spring Security documentations along the way. I'll also refer to this useful blog post for LDAP integration.

There are so many good resources on how to do this, at least up to the point where you want to talk to LDAP. Hence I'll cut the story short and provide my final and complete configuration which contains everything together. You need to have and LDAP server installed before you can try this out for yourself. I'd highly recommend Apache DS as it is very easy to set up. I spent hours on configuring OpenLDAP/SLAP with no luck.

The complete source code for this sample can be found here, or checked out on your own machine using this command:
svn checkout http://tinywebgears-samples.googlecode.com/svn/trunk/sso-cas-spring sso-cas-spring


The client code is pretty straightforward, just any other Spring client as described here. CAS server is only a web application and if you follow the WAR Overlay method, you only need to modify a few places. I had to modify deployerConfigContext.xml, securityContext.xml, and cas.properties, then added a new jsp page (casServiceValidationSuccess.jsp) and a Java class (LdapPersonAttributeAndRoleDao.java).

For your convenience and easy reading I'm putting contents of these files here at the end.

deployerConfigContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

<bean id="authenticationManager"
     class="org.jasig.cas.authentication.AuthenticationManagerImpl">
   <property name="credentialsToPrincipalResolvers">
       <list>
           <bean class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver">
               <!-- The Principal resolver form the credentials -->
               <property name="credentialsToPrincipalResolver">
                   <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver"/>
               </property>
               <!-- The query made to find the Principal ID. "%u" will be replaced by the resolved Principal -->
               <property name="filter" value="(uid=%u)"/>
               <!-- The attribute used to define the new Principal ID -->
               <property name="principalAttributeName" value="cn"/>
               <property name="searchBase" value="ou=users,ou=system"/>
               <property name="contextSource" ref="contextSource"/>
               <property name="attributeRepository">
                   <ref bean="attributeRepository"/>
               </property>
           </bean>
           <bean
                   class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver"/>
       </list>
   </property>

   <property name="authenticationHandlers">
       <list>
           <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
                 p:httpClient-ref="httpClient"/>
           <bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">
               <property name="filter" value="uid=%u"/>
               <property name="searchBase" value="ou=users,ou=system"/>
               <property name="contextSource" ref="contextSource"/>
           </bean>
       </list>
   </property>
</bean>


<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
   <property name="pooled" value="false"/>
   <property name="urls">
       <list>
           <value>ldap://localhost:10389</value>
       </list>
   </property>
   <property name="userDn" value="uid=admin,ou=system"/>
   <property name="password" value="secret"/>
   <property name="baseEnvironmentProperties">
       <map>
           <entry>
               <key>
                   <value>java.naming.security.authentication</value>
               </key>
               <value>simple</value>
           </entry>
       </map>
   </property>
</bean>

<bean id="userDetailsService"
     class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">
   <constructor-arg>
       <list>
           <value>authorities</value>
       </list>
   </constructor-arg>
</bean>

<bean id="attributeRepository" class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeAndRoleDao">
   <property name="contextSource" ref="contextSource"/>
   <property name="baseDN" value="ou=users,ou=system"/>
   <property name="requireAllQueryAttributes" value="true"/>
   <!-- Attribute mapping between principal (key) and LDAP (value) names used to perform the LDAP search.
By default, multiple search criteria are ANDed together.  Set the queryType property to change to OR. -->
   <property name="queryAttributeMapping">
       <map>
           <entry key="username" value="uid"/>
       </map>
   </property>
   <property name="resultAttributeMapping">
       <map>
           <!-- Mapping beetween LDAP entry attributes (key) and Principal's (value) -->
           <entry key="mail" value="email"/>
           <entry key="authorities" value="authorities"/>
       </map>
   </property>
   <property name="ldapAuthoritiesPopulator" ref="ldapAuthoritiesPopulator"/>
</bean>

<bean id="ldapAuthoritiesPopulator"
     class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
   <constructor-arg ref="contextSource"/>
   <constructor-arg value="ou=groups,ou=system"/>
   <property name="groupRoleAttribute" value="cn"/>
   <property name="groupSearchFilter" value="(uniqueMember={0})"/>
</bean>


<bean id="serviceRegistryDao" class="org.jasig.cas.services.JpaServiceRegistryDaoImpl"
     p:entityManagerFactory-ref="entityManagerFactory"/>
<!-- This is the EntityManagerFactory configuration for Hibernate -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <property name="jpaVendorAdapter">
       <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
           <property name="generateDdl" value="true"/>
           <property name="showSql" value="false"/>
       </bean>
   </property>
   <property name="jpaProperties">
       <props>
           <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
           <prop key="hibernate.hbm2ddl.auto">update</prop>
       </props>
   </property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
   <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
   <property name="url" value="${database.url}"/>
   <property name="username" value="${database.username}"/>
   <property name="password" value="${database.password}"/>
   <property name="validationQuery" value="select 1"/>
   <property name="testOnBorrow" value="false"/>
   <property name="testWhileIdle" value="true"/>
   <property name="defaultAutoCommit" value="false"/>
</bean>

<bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager"/>

</beans>


securityContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xmlns:sec="http://www.springframework.org/schema/security"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<description>
   This is the configuration file for the Spring Security configuration used with the services management tool. You
   shouldn't
   have to modify anything in this file directly. The configuration options should all be in the cas.properties
   file.
</description>

<sec:http entry-point-ref="casProcessingFilterEntryPoint" auto-config="true">
   <sec:intercept-url pattern="/services/loggedout.html" filters="none"/>
   <sec:intercept-url pattern="/**" access="${cas.securityContext.serviceProperties.adminRoles}"/>
   <sec:logout logout-url="/services/logout.html" logout-success-url="/services/loggedOut.html"/>
   <sec:custom-filter ref="casProcessingFilter" after="CAS_FILTER"/>
</sec:http>

<sec:authentication-manager alias="casAuthenticationManager">
   <sec:authentication-provider ref="casAuthenticationProvider"/>
</sec:authentication-manager>

<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"
     p:service="${cas.securityContext.serviceProperties.service}"
     p:sendRenew="false"/>

<bean id="casProcessingFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"
     p:authenticationManager-ref="casAuthenticationManager"
     p:filterProcessesUrl="/services/j_acegi_cas_security_check">
   <property name="authenticationSuccessHandler">
       <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"
             p:alwaysUseDefaultTargetUrl="true"
             p:defaultTargetUrl="/services/manage.html"/>
   </property>
   <property name="authenticationFailureHandler">
       <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
           <constructor-arg index="0" value="/authorizationFailure.html"/>
       </bean>
   </property>
</bean>

<bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"
     p:loginUrl="${cas.securityContext.casProcessingFilterEntryPoint.loginUrl}"
     p:serviceProperties-ref="serviceProperties"/>

<bean id="casAuthenticationProvider"
     class="org.springframework.security.cas.authentication.CasAuthenticationProvider"
     p:key="my_password_for_this_auth_provider_only"
     p:serviceProperties-ref="serviceProperties"
     p:authenticationUserDetailsService-ref="userDetailsService">
   <property name="ticketValidator">
       <bean class="org.jasig.cas.client.validation.Saml11TicketValidator">
           <constructor-arg index="0" value="${cas.securityContext.ticketValidator.casServerUrlPrefix}"/>
       </bean>
   </property>
</bean>

</beans>


cas.properties
server.prefix=https://localhost:8443/cas

cas.securityContext.serviceProperties.service=${server.prefix}/services/j_acegi_cas_security_check
# Names of roles allowed to access the CAS service manager
cas.securityContext.serviceProperties.adminRoles=ROLE_ADMINISTRATORS
cas.securityContext.casProcessingFilterEntryPoint.loginUrl=${server.prefix}/login
cas.securityContext.ticketValidator.casServerUrlPrefix=${server.prefix}


cas.themeResolver.defaultThemeName=cas-theme-default
cas.viewResolver.basename=default_views

host.name=localhost

#database.hibernate.dialect=org.hibernate.dialect.OracleDialect
database.hibernate.dialect=org.hibernate.dialect.MySQLDialect
#database.hibernate.dialect=org.hibernate.dialect.HSQLDialect
database.url=jdbc:mysql://localhost/cas
database.username=cas
database.password=cas


casServiceValidationSuccess.jsp
<%@ page session="false" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %><cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
<cas:proxies>
<c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
<!-- Begin Ldap Attributes -->
<c:if test="${fn:length(assertion.chainedAuthentications) > 0}">
<cas:attributes>
<c:forEach var="auth" items="${assertion.chainedAuthentications}">
<c:forEach var="attr" items="${auth.principal.attributes}" >
<cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>
</c:forEach>
</c:forEach>
</cas:attributes>
</c:if>
<!-- End Ldap Attributes -->
</cas:authenticationSuccess>
</cas:serviceResponse>


LdapPersonAttributeAndRoleDao.java
package org.jasig.services.persondir.support.ldap;

import org.jasig.services.persondir.IPersonAttributes;
import org.jasig.services.persondir.support.CaseInsensitiveAttributeNamedPersonImpl;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;

import java.util.*;

public class LdapPersonAttributeAndRoleDao extends LdapPersonAttributeDao
{
private DefaultLdapAuthoritiesPopulator ldapAuthoritiesPopulator;

@Override
protected List<IPersonAttributes> getPeopleForQuery(LogicalFilterWrapper queryBuilder, String queryUserName)
{
   //the list of users with the username as fetched from the base class (no role info here)
   List<IPersonAttributes> attribs = super.getPeopleForQuery(queryBuilder, queryUserName);

   //the list of users that we want to return
   final List<IPersonAttributes> peopleWithRoles = new ArrayList<IPersonAttributes>(attribs.size());

   //Fetch the Authorities from Ldap
   Collection<GrantedAuthority> authorities = null;
   try
   {
       String userDn = "cn=" + queryUserName + "," + getBaseDN();
       //Utilize the Spring Security Ldap functionality to obtain granted authorities
       authorities = ldapAuthoritiesPopulator.getGrantedAuthorities(new DirContextAdapter(userDn), queryUserName);
       logger.info("Authorities: " + authorities);
   }
   catch (Exception nnfe)
   {
       //we just won't add authorities if there was an error.
       logger.error("error looking up authorities", nnfe);
   }

   //add authorities in the format required, if there are any found
   List<Object> authoritiesList;
   if (null != authorities)
   {
       //transform the GrantedAuthority list into a List of Strings
       authoritiesList = new ArrayList<Object>();
       for (GrantedAuthority auth : authorities)
       {
           authoritiesList.add(auth);
       }

       for (IPersonAttributes person : attribs)
       {
           // the new list of attributes
           Map<String, List<Object>> attrs = new HashMap<String, List<Object>>();
           // add the old attributes
           attrs.putAll(person.getAttributes());
           // add the authorities
           attrs.put("authorities", authoritiesList);
           // add the person to the return list.
           peopleWithRoles.add(new CaseInsensitiveAttributeNamedPersonImpl(this.getConfiguredUserNameAttribute(), attrs));
       }
   }
   else
   {
       peopleWithRoles.addAll(attribs);
   }

   return peopleWithRoles;
}

public DefaultLdapAuthoritiesPopulator getLdapAuthoritiesPopulator()
{
   return ldapAuthoritiesPopulator;
}

public void setLdapAuthoritiesPopulator(DefaultLdapAuthoritiesPopulator ldapAuthoritiesPopulator)
{
   this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator;
}
}

Thursday, February 9, 2012

Testing Checksums

We all know that we should always test the checksum of the packages we download from Internet to avoid running malicious software, but we sometimes ignore it because it requires some manual process.

I am not aware of any tools/plugins that might be available for this purpose, but I made my own little shell script to help me with that. It didn't take me long, perhaps less than the time I needed to spend searching around.


#!/bin/sh

if [ $# != 2 ]; then
echo "Usage: $0 file_name checksum"
exit 1
fi

sum=`sha1sum $1 | awk '{print $1}'`
if [ "$sum" == "$2" ]; then
echo "Valid SHA-1 Checksum"
else
echo "*** Invalid SHA-1 Checksum ***"
fi