OAuth-2 authentication with full name / e-mail lookup.

Instead of requiring manual Gerrit user profile registration,
the Full name and EMail are automatically retrieved at
login and stored in Gerrit as read-only.

Profile is automatically re-synched at next login
on by clicking the "Reload" button on the Gerrit
settings page.

Change-Id: I952e8e79d05f3ba102b012958afda31dd979d658
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedHttpRequest.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedHttpRequest.java
index ccb2d5c..46f6ea9 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedHttpRequest.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedHttpRequest.java
@@ -14,66 +14,45 @@
 package com.googlesource.gerrit.plugins.github.oauth;
 
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 
-import com.google.common.base.Objects;
+import com.google.common.collect.Iterators;
 
 public class AuthenticatedHttpRequest extends HttpServletRequestWrapper {
-  private String httpHeaderName;
-  private String httpHeaderValue;
+  private HashMap<String, String> headers = new HashMap<String, String>();
 
   public AuthenticatedHttpRequest(HttpServletRequest request,
-      String authHeaderName, String authHeaderValue) {
+      String... headerNamesValues) {
     super(request);
-    this.httpHeaderName = authHeaderName;
-    this.httpHeaderValue = authHeaderValue;
+
+    for (int i = 0; i < headerNamesValues.length;) {
+      String name = headerNamesValues[i++];
+      String value = headerNamesValues[i++];
+      if (name != null && value != null) {
+        headers.put(name, value);
+      }
+    }
   }
 
   @Override
   public Enumeration<String> getHeaderNames() {
-
     final Enumeration<String> wrappedHeaderNames = super.getHeaderNames();
-    return new Enumeration<String>() {
-
-      boolean lastElement;
-      boolean headerFound;
-
-      @Override
-      public boolean hasMoreElements() {
-        if (wrappedHeaderNames.hasMoreElements()) {
-          return true;
-        } else if (!lastElement && !headerFound) {
-          return true;
-        } else {
-          return false;
-        }
-      }
-
-      @Override
-      public String nextElement() {
-        if (wrappedHeaderNames.hasMoreElements()) {
-          String nextHeader = wrappedHeaderNames.nextElement();
-          if (nextHeader.equalsIgnoreCase(httpHeaderName)) {
-            headerFound = true;
-          }
-          return nextHeader;
-        } else if (!lastElement && !headerFound) {
-          lastElement = true;
-          return httpHeaderName;
-        } else {
-          return null;
-        }
-      }
-
-    };
+    HashSet<String> headerNames = new HashSet<String>(headers.keySet());
+    while (wrappedHeaderNames.hasMoreElements()) {
+      headerNames.add(wrappedHeaderNames.nextElement());
+    }
+    return Iterators.asEnumeration(headerNames.iterator());
   }
 
   @Override
   public String getHeader(String name) {
-    if (name.equalsIgnoreCase(httpHeaderName)) {
-      return httpHeaderValue;
+    String headerValue = headers.get(name);
+    if (headerValue != null) {
+      return headerValue;
     } else {
       return super.getHeader(name);
     }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
index 50018ea..69ceaec 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
@@ -1,3 +1,17 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package com.googlesource.gerrit.plugins.github.oauth;
 
 import java.io.IOException;
@@ -15,6 +29,7 @@
 public class GitHubLogin {
   public AccessToken token;
   public GitHub hub;
+  private String redirectUrl;
 
   private transient OAuthProtocol oauth;
 
@@ -36,8 +51,14 @@
       throws IOException {
     if (oauth.isOAuthFinal(request)) {
       init(oauth.loginPhase2(request, response));
-      return isLoggedIn();
+      if(isLoggedIn()) {
+        response.sendRedirect(redirectUrl);
+        return true;
+      } else {
+        return false;
+      }
     } else {
+      redirectUrl = request.getRequestURL().toString();
       oauth.loginPhase1(request, response);
       return false;
     }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java
index 5415ca6..faa60a3 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthConfig.java
@@ -37,6 +37,8 @@
   public final String gitHubClientId;
   public final String gitHubClientSecret;
   public final String httpHeader;
+  public final String httpDisplaynameHeader;
+  public final String httpEmailHeader;
   public final String gitHubOAuthUrl;
   public final String oAuthFinalRedirectUrl;
   public final String gitHubOAuthAccessTokenUrl;
@@ -46,6 +48,8 @@
   public OAuthConfig(@GerritServerConfig Config config)
       throws MalformedURLException {
     httpHeader = config.getString("auth", null, "httpHeader");
+    httpDisplaynameHeader = config.getString("auth", null, "httpDisplaynameHeader");
+    httpEmailHeader = config.getString("auth", null, "httpEmailHeader");
     gitHubUrl =
         Objects.firstNonNull(config.getString("github", null, "url"),
             GITHUB_URL);
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java
index 3bf35d5..d886b82 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java
@@ -20,10 +20,27 @@
   public static final String OAUTH_COOKIE_NAME = "GerritOAuth";
 
   public final String user;
+  public final String email;
+  public final String fullName;
 
-  OAuthCookie(String user, String cookieValue) {
-    super(OAUTH_COOKIE_NAME, cookieValue);
-
+  public OAuthCookie(TokenCipher cipher, final String user, final String email,
+      final String fullName) throws OAuthTokenException {
+    super(OAUTH_COOKIE_NAME, cipher.encode(String.format("%s\n%s\n%s", user,
+        email, fullName)));
     this.user = user;
+    this.email = email;
+    this.fullName = fullName;
+    setMaxAge((int) (TokenCipher.COOKIE_TIMEOUT/1000L));
+    setHttpOnly(true);
+  }
+
+  public OAuthCookie(TokenCipher cipher, Cookie cookie)
+      throws OAuthTokenException {
+    super(OAUTH_COOKIE_NAME, cookie.getValue());
+    String clearTextValue = cipher.decode(cookie.getValue());
+    String[] clearText = clearTextValue.split("\n");
+    user = clearText.length > 0 ? clearText[0]:null;
+    email = clearText.length > 1 ? clearText[1]:null;
+    fullName = clearText.length > 2 ? clearText[2]:null;
   }
 }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookieProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookieProvider.java
index 2cdaab4..65cd93e 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookieProvider.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookieProvider.java
@@ -26,106 +26,23 @@
 import org.slf4j.Logger;
 
 public class OAuthCookieProvider {
-  private static final String UTF8 = "UTF-8";
-  private static final String ENC_ALGO_PADDING = "AES/CBC/PKCS5Padding";
-  private static final String JCE_PROVIDER = "SunJCE";
-  private static final String ENC_ALGO = "AES";
-  private static final Logger log = org.slf4j.LoggerFactory
-      .getLogger(OAuthCookieProvider.class);
-  private static final Long COOKIE_TIMEOUT = 15 * 60 * 1000L;
-  private SecretKey aesKey;
-  private byte[] IV;
-  private SecureRandom sessionRnd = new SecureRandom();
 
+  private TokenCipher cipher;
 
-  void init() {
-    KeyGenerator kgen;
-    try {
-      kgen = KeyGenerator.getInstance(ENC_ALGO);
-      kgen.init(128);
-      SecureRandom sr = new SecureRandom();
-      sr.setSeed(System.currentTimeMillis());
-      byte[] key = new byte[16];
-      IV = new byte[16];
-      sr.nextBytes(key);
-      sr.nextBytes(IV);
-      aesKey = kgen.generateKey();
-      sessionRnd.setSeed(System.currentTimeMillis());
-    } catch (NoSuchAlgorithmException e) {
-      log.error("Cannot find encryption algorithm " + ENC_ALGO);
-      throw new IllegalArgumentException(e);
-    }
+  public OAuthCookieProvider(TokenCipher cipher) {
+    this.cipher = cipher;
   }
 
-  public OAuthCookie getFromUser(String username) {
+  public OAuthCookie getFromUser(String username, String email, String fullName) {
     try {
-      return new OAuthCookie(username, encode(username));
+      return new OAuthCookie(cipher, username, email, fullName);
     } catch (OAuthTokenException e) {
       return null;
     }
   }
 
-  public OAuthCookie getFromCookie(Cookie cookie) {
-    try {
-      return new OAuthCookie(decode(cookie.getValue()), cookie.getValue());
-    } catch (OAuthTokenException e) {
-      return null;
-    }
+  public OAuthCookie getFromCookie(Cookie cookie) throws OAuthTokenException {
+      return new OAuthCookie(cipher, cookie);
   }
 
-  public String encode(String user) throws OAuthTokenException {
-    try {
-      long sessionId = sessionRnd.nextLong();
-      long ts = System.currentTimeMillis();
-      String userSession =
-          String.format("%d/%d/%s", sessionId, ts,
-              URLEncoder.encode(user, UTF8));
-      byte[] plainText =
-          (userSession + "/" + userSession.hashCode()).getBytes(UTF8);
-
-      Cipher cipher = Cipher.getInstance(ENC_ALGO_PADDING, JCE_PROVIDER);
-      cipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(IV));
-      byte[] enc = cipher.doFinal(plainText);
-      return org.eclipse.jgit.util.Base64.encodeBytes(enc).trim();
-    } catch (Exception e) {
-      log.error("Encryption failed", e);
-      throw new OAuthTokenException("Cannot generate session token for user "
-          + user, e);
-    }
-  }
-
-  public String decode(String sessionToken) throws OAuthTokenException {
-    try {
-      byte[] enc =
-          org.eclipse.jgit.util.Base64.decode(sessionToken.trim().getBytes(),
-              0, sessionToken.length());
-      Cipher cipher = Cipher.getInstance(ENC_ALGO_PADDING, JCE_PROVIDER);
-      cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(IV));
-
-      String[] clearTextParts =
-          new String(cipher.doFinal(enc), UTF8).split("/");
-
-      isValid(sessionToken, clearTextParts);
-
-      return clearTextParts[2];
-    } catch (Exception e) {
-      log.error("Decryption failed", e);
-      throw new OAuthTokenException("Invalid session token " + sessionToken, e);
-    }
-  }
-
-  private void isValid(String sessionToken, String[] clearTextParts)
-      throws OAuthTokenException {
-    int hashCode = Integer.parseInt(clearTextParts[3]);
-    if (hashCode != (clearTextParts[0] + "/" + clearTextParts[1] + "/" + clearTextParts[2])
-        .hashCode()) {
-      throw new OAuthTokenException("Invalid or forged token " + sessionToken);
-    }
-
-    long ts = Long.parseLong(clearTextParts[1]);
-    if ((System.currentTimeMillis() - ts) > COOKIE_TIMEOUT) {
-      throw new OAuthTokenException("Session token " + sessionToken
-          + " has expired");
-    }
-  }
 }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
index 50cc750..7e43ba7 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
@@ -26,15 +26,19 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.kohsuke.github.GHMyself;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Strings;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
+@Singleton
 public class OAuthFilter implements Filter {
   private static final org.slf4j.Logger log = LoggerFactory
       .getLogger(OAuthFilter.class);
+  private static final String GERRIT_COOKIE_NAME = "GerritAccount";
 
   private final OAuthConfig config;
   private final OAuthCookieProvider cookieProvider;
@@ -43,7 +47,7 @@
   @Inject
   public OAuthFilter(OAuthConfig config) {
     this.config = config;
-    this.cookieProvider = new OAuthCookieProvider();
+    this.cookieProvider = new OAuthCookieProvider(new TokenCipher());
     this.oauth =
         new OAuthProtocol(config, GitHubHttpProvider.getInstance().get(),
             new Gson());
@@ -51,7 +55,6 @@
 
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
-    cookieProvider.init();
   }
 
   @Override
@@ -66,18 +69,27 @@
     HttpServletResponse httpResponse = (HttpServletResponse) response;
     log.info("doFilter(" + httpRequest.getRequestURI() + ")");
 
-    OAuthCookie authCookie = getOAuthCookie(httpRequest);
+    Cookie gerritCookie = getGerritCookie(httpRequest);
+    OAuthCookie authCookie =
+        getOAuthCookie(httpRequest, (HttpServletResponse) response);
     String targetUrl = httpRequest.getParameter("state");
 
-    if (authCookie == null) {
+    if (((oauth.isOAuthLogin(httpRequest) || oauth.isOAuthFinal(httpRequest)) && authCookie == null)
+        || (authCookie == null && gerritCookie == null)) {
       if (oauth.isOAuthFinal(httpRequest)) {
 
-        String user =
-            oauth.loginPhase2(httpRequest, httpResponse).hub.getMyself()
-                .getLogin();
+        GHMyself myself =
+            oauth.loginPhase2(httpRequest, httpResponse).hub.getMyself();
+        String user = myself.getLogin();
+        String email = myself.getEmail();
+        String fullName =
+            Strings.emptyToNull(myself.getName()) == null ? user : myself
+                .getName();
 
         if (user != null) {
-          httpResponse.addCookie(cookieProvider.getFromUser(user));
+          OAuthCookie userCookie =
+              cookieProvider.getFromUser(user, email, fullName);
+          httpResponse.addCookie(userCookie);
           httpResponse.sendRedirect(targetUrl);
           return;
         } else {
@@ -85,33 +97,63 @@
               "Login failed");
         }
       } else {
-        if(oauth.isOAuthLogin(httpRequest)) {
-        oauth.loginPhase1(httpRequest, httpResponse);
+        if (oauth.isOAuthLogin(httpRequest)) {
+          oauth.loginPhase1(httpRequest, httpResponse);
         } else {
           chain.doFilter(request, response);
         }
       }
       return;
     } else {
-      HttpServletRequest wrappedRequest =
-          new AuthenticatedHttpRequest(httpRequest, config.httpHeader,
-              authCookie.user);
+      if (gerritCookie != null && !oauth.isOAuthLogin(httpRequest)) {
+        if (authCookie != null) {
+          authCookie.setMaxAge(0);
+          authCookie.setValue("");
+          httpResponse.addCookie(authCookie);
+        }
+      } else if (authCookie != null) {
+        httpRequest =
+            new AuthenticatedHttpRequest(httpRequest, config.httpHeader,
+                authCookie.user, config.httpDisplaynameHeader,
+                authCookie.fullName, config.httpEmailHeader, authCookie.email);
+      }
 
       if (targetUrl != null && oauth.isOAuthFinal(httpRequest)) {
         httpResponse.sendRedirect(config.getUrl(targetUrl,
             OAuthConfig.OAUTH_FINAL) + "?code=" + request.getParameter("code"));
         return;
       } else {
-        chain.doFilter(wrappedRequest, response);
+        chain.doFilter(httpRequest, response);
       }
     }
   }
 
-  private OAuthCookie getOAuthCookie(HttpServletRequest request) {
+  private Cookie getGerritCookie(HttpServletRequest httpRequest) {
+    for (Cookie cookie : httpRequest.getCookies()) {
+      if (cookie.getName().equalsIgnoreCase(GERRIT_COOKIE_NAME)) {
+        return cookie;
+      }
+    }
+    return null;
+  }
+
+  private OAuthCookie getOAuthCookie(HttpServletRequest request,
+      HttpServletResponse response) {
     for (Cookie cookie : request.getCookies()) {
       if (cookie.getName().equalsIgnoreCase(OAuthCookie.OAUTH_COOKIE_NAME)
           && !Strings.isNullOrEmpty(cookie.getValue())) {
-        return cookieProvider.getFromCookie(cookie);
+        try {
+          return cookieProvider.getFromCookie(cookie);
+        } catch (OAuthTokenException e) {
+          log.warn(
+              "Invalid cookie detected: cleaning up and sending a reset back to the browser",
+              e);
+          cookie.setValue("");
+          cookie.setPath("/");
+          cookie.setMaxAge(0);
+          response.addCookie(cookie);
+          return null;
+        }
       }
     }
     return null;
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
index 82af2dd..790df59 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
@@ -59,7 +59,7 @@
   }
   
   public boolean isOAuthLogin(HttpServletRequest request) {
-    return request.getRequestURI().endsWith(OAuthConfig.OAUTH_LOGIN);
+    return request.getRequestURI().indexOf(OAuthConfig.OAUTH_LOGIN) >= 0;
   }
   
   public GitHubLogin loginPhase2(HttpServletRequest request,
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/TokenCipher.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/TokenCipher.java
new file mode 100644
index 0000000..9f4cbf6
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/TokenCipher.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.slf4j.Logger;
+
+import com.google.inject.Singleton;
+
+@Singleton
+public class TokenCipher {
+  private static final String UTF8 = "UTF-8";
+  private static final String ENC_ALGO_PADDING = "AES/CBC/PKCS5Padding";
+  private static final String JCE_PROVIDER = "SunJCE";
+  private static final String ENC_ALGO = "AES";
+  private static final Logger log = org.slf4j.LoggerFactory
+      .getLogger(OAuthCookieProvider.class);
+  public static final Long COOKIE_TIMEOUT = 60 * 1000L;
+  
+  private SecretKey aesKey;
+  private byte[] IV;
+  private SecureRandom sessionRnd = new SecureRandom();
+
+  public TokenCipher() {
+    KeyGenerator kgen;
+    try {
+      kgen = KeyGenerator.getInstance(ENC_ALGO);
+      kgen.init(128);
+      SecureRandom sr = new SecureRandom();
+      sr.setSeed(System.currentTimeMillis());
+      byte[] key = new byte[16];
+      IV = new byte[16];
+      sr.nextBytes(key);
+      sr.nextBytes(IV);
+      aesKey = kgen.generateKey();
+      sessionRnd.setSeed(System.currentTimeMillis());
+    } catch (NoSuchAlgorithmException e) {
+      log.error("Cannot find encryption algorithm " + ENC_ALGO);
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  public String encode(String clearText) throws OAuthTokenException {
+    try {
+      long sessionId = sessionRnd.nextLong();
+      long ts = System.currentTimeMillis();
+      String userSession =
+          String.format("%d/%d/%s", sessionId, ts,
+              URLEncoder.encode(clearText, UTF8));
+      byte[] plainText =
+          (userSession + "/" + userSession.hashCode()).getBytes(UTF8);
+
+      Cipher cipher = Cipher.getInstance(ENC_ALGO_PADDING, JCE_PROVIDER);
+      cipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(IV));
+      byte[] enc = cipher.doFinal(plainText);
+      return org.eclipse.jgit.util.Base64.encodeBytes(enc).trim();
+    } catch (Exception e) {
+      log.error("Encryption failed", e);
+      throw new OAuthTokenException("Cannot generate token for "
+          + clearText, e);
+    }
+  }
+  
+  public String decode(String sessionToken) throws OAuthTokenException {
+    try {
+      byte[] enc =
+          org.eclipse.jgit.util.Base64.decode(sessionToken.trim().getBytes(),
+              0, sessionToken.length());
+      Cipher cipher = Cipher.getInstance(ENC_ALGO_PADDING, JCE_PROVIDER);
+      cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(IV));
+
+      String[] clearTextParts =
+          new String(cipher.doFinal(enc), UTF8).split("/");
+
+      isValid(sessionToken, clearTextParts);
+
+      return URLDecoder.decode(clearTextParts[2], UTF8);
+    } catch (Exception e) {
+      log.error("Decryption failed", e);
+      throw new OAuthTokenException("Invalid session token " + sessionToken, e);
+    }
+  }
+
+  private void isValid(String sessionToken, String[] clearTextParts)
+      throws OAuthTokenException {
+    int hashCode = Integer.parseInt(clearTextParts[3]);
+    if (hashCode != (clearTextParts[0] + "/" + clearTextParts[1] + "/" + clearTextParts[2])
+        .hashCode()) {
+      throw new OAuthTokenException("Invalid or forged token " + sessionToken);
+    }
+
+    long ts = Long.parseLong(clearTextParts[1]);
+    if ((System.currentTimeMillis() - ts) > COOKIE_TIMEOUT) {
+      throw new OAuthTokenException("Session token " + sessionToken
+          + " has expired");
+    }
+  }
+}