Package com.google.gdata.client.http

Source Code of com.google.gdata.client.http.AuthSubUtil

/* Copyright (c) 2008 Google Inc.
*
* 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.google.gdata.client.http;

import com.google.gdata.util.common.base.CharEscapers;
import com.google.gdata.util.common.base.Charsets;
import com.google.gdata.util.common.base.StringUtil;
import com.google.gdata.util.common.io.Characters;
import com.google.gdata.util.common.util.Base64;
import com.google.gdata.util.AuthenticationException;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.util.Map;


/**
* Utility functions to support AuthSub (Account Authentication for Web
* Applications).
*
*
*/
public class AuthSubUtil {

  // The default protocol to use for AuthSub communication
  private static final String DEFAULT_PROTOCOL = "https";

  // The default domain to use for AuthSub communication
  private static final String DEFAULT_DOMAIN = "www.google.com";

  // Secure random number generator to sign requests
  private static final SecureRandom RANDOM = new SecureRandom();


  /**
   * Signature algorithms supported by AuthSub to sign the requests.
   */
  private enum SignatureAlgorithm {
    DSA_SHA1("dsa-sha1", "SHA1withDSA"),
    RSA_SHA1("rsa-sha1", "SHA1withRSA");

    SignatureAlgorithm(String authSubName, String jcaName) {
      this.authSubName = authSubName;
      this.jcaName = jcaName;
    }

    @Override
    public String toString() { return jcaName; }
    public String getAuthSubName() { return authSubName; }
    public String getJCAName() { return jcaName; }

    private final String authSubName;
    private final String jcaName;
  }


  /**
   * Creates the request URL to be used to retrieve an AuthSub token.
   * <p>
   * On success, the user will be redirected to the next URL with the
   * AuthSub token appended to the URL.  Use {@link #getTokenFromReply(String)}
   * to retrieve the token from the reply.
   *
   * @param nextUrl the URL to redirect to on successful token retrieval
   * @param scope the scope of the requested AuthSub token
   * @param secure <code>true</code> if the token will be used securely
   * @param session <code>true</code> if the token will be exchanged for a
   *                session cookie
   * @return the URL to be used to retrieve the AuthSub token
   */
  public static String getRequestUrl(String nextUrl,
                                     String scope,
                                     boolean secure,
                                     boolean session) {

    return getRequestUrl(DEFAULT_PROTOCOL, DEFAULT_DOMAIN, nextUrl, scope,
                         secure, session);
  }


  /**
   * Creates the request URL to be used to retrieve an AuthSub token.
   * <p>
   * On success, the user will be redirected to the next URL with the
   * AuthSub token appended to the URL.  Use {@link #getTokenFromReply(String)}
   * to retrieve the token from the reply.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @param nextUrl the URL to redirect to on successful token retrieval
   * @param scope the scope of the requested AuthSub token
   * @param secure <code>true</code> if the token will be used securely
   * @param session <code>true</code> if the token will be exchanged for a
   *                session cookie
   * @return the URL to be used to retrieve the AuthSub token
   */
  public static String getRequestUrl(String protocol,
                                     String domain,
                                     String nextUrl,
                                     String scope,
                                     boolean secure,
                                     boolean session) {

    StringBuffer url = new StringBuffer(protocol).append("://");
    url.append(domain).append("/accounts/AuthSubRequest");
    addParameter(url, "next", nextUrl);
    addParameter(url, "scope", scope);
    addParameter(url, "secure", secure ? "1" : "0");
    addParameter(url, "session", session ? "1" : "0");
    return url.toString();
  }

  /**
   * Creates the request URL to be used to retrieve an AuthSub token for
   * hosted applications.
   * <p>
   * On success, the user will be redirected to the next URL with the
   * AuthSub token appended to the URL.  Use {@link #getTokenFromReply(String)}
   * to retrieve the token from the reply.
   *
   * @param hostedDomain hosted domain name, for example
   *                     <code>mydomain.example.com</code>
   * @param nextUrl the URL to redirect to on successful token retrieval
   * @param scope the scope of the requested AuthSub token
   * @param secure <code>true</code> if the token will be used securely
   * @param session <code>true</code> if the token will be exchanged for a
   *                session cookie
   * @return the URL to be used to retrieve the AuthSub token
   */
  public static String getRequestUrl(String hostedDomain,
                                     String nextUrl,
                                     String scope,
                                     boolean secure,
                                     boolean session) {
    return getRequestUrl(DEFAULT_PROTOCOL, DEFAULT_DOMAIN, hostedDomain,
                         nextUrl, scope, secure, session);
  }

  /**
   * Creates the request URL for to be used to retrieve an AuthSub token for
   * hosted applications.
   * <p>
   * On success, the user will be redirected to the next URL with the
   * AuthSub token appended to the URL.  Use {@link #getTokenFromReply(String)}
   * to retrieve the token from the reply.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @param hostedDomain hosted domain name, for example
   *                     <code>mydomain.example.com</code>
   * @param nextUrl the URL to redirect to on successful token retrieval
   * @param scope the scope of the requested AuthSub token
   * @param secure <code>true</code> if the token will be used securely
   * @param session <code>true</code> if the token will be exchanged for a
   *                session cookie
   * @return the URL to be used to retrieve the AuthSub token
   */
  public static String getRequestUrl(String protocol,
                                     String domain,
                                     String hostedDomain,
                                     String nextUrl,
                                     String scope,
                                     boolean secure,
                                     boolean session) {
    StringBuffer url = new StringBuffer(
        getRequestUrl(protocol, domain, nextUrl, scope, secure, session));
    addParameter(url, "hd", hostedDomain);
    return url.toString();
  }

  /**
   * Parses and returns the AuthSub token returned by Google on a successful
   * AuthSub login request.  The token will be appended as a query parameter
   * to the next URL specified while making the AuthSub request.
   *
   * @param url the redirected-to next URL with the token
   * @return the AuthSub token returned by Google
   */
  public static String getTokenFromReply(URL url) {

    return getTokenFromReply(url.getQuery());
  }


  /**
   * Parses and returns the AuthSub token returned by Google on a successful
   * AuthSub login request.  The token will be appended as a query parameter
   * to the next URL specified while making the AuthSub request.
   *
   * @param queryString the query portion of the redirected-to URL containing
   *                    the token
   * @return the AuthSub token returned by Google
   */
  public static String getTokenFromReply(String queryString) {

    // Parse the query parameters
    Map<String, String> params =
      StringUtil.string2Map(queryString, "&", "=", true /*stripEntry*/);
    params = StringUtil.lowercaseKeys(params);
    return params.get("token");
  }


  /**
   * Retrieves the private key from the specified keystore.
   *
   * @param keystore the path to the keystore file
   * @param keystorePass the password that protects the keystore file
   * @param keyAlias the alias under which the private key is stored
   * @param keyPass the password protecting the private key
   * @return the private key from the specified keystore
   * @throws GeneralSecurityException if the keystore cannot be loaded
   * @throws IOException if the file cannot be accessed
   */
  public static PrivateKey getPrivateKeyFromKeystore(String keystore,
                                                     String keystorePass,
                                                     String keyAlias,
                                                     String keyPass)
      throws IOException, GeneralSecurityException {

    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    FileInputStream keyStream = null;
    try {
      keyStream = new FileInputStream(keystore);
      keyStore.load(keyStream, keystorePass.toCharArray());
      return (PrivateKey) keyStore.getKey(keyAlias, keyPass.toCharArray());
    } finally {
      if (keyStream != null) {
        keyStream.close();
      }
    }
  }


  /**
   * Exchanges the one time use token returned in the URL for a session
   * token.
   * <p>
   * If the <code>key</code> is non-null, the token will be used securely
   * and the request to make the exchange will be signed.
   *
   * @param onetimeUseToken the one time use token sent by Google in the URL
   * @param key the private key to sign the request
   * @return the session token.  <code>null</code> if the request failed
   * @throws IOException if error in writing/reading the request
   * @throws GeneralSecurityException if error in signing the request
   * @throws AuthenticationException if one time use token is rejected
   */
  public static String exchangeForSessionToken(String onetimeUseToken,
                                               PrivateKey key)
      throws IOException, GeneralSecurityException, AuthenticationException {

    return exchangeForSessionToken(DEFAULT_PROTOCOL, DEFAULT_DOMAIN,
                                   onetimeUseToken, key);
  }

  /**
   * Exchanges the one time use token returned in the URL for a session
   * token.
   * <p>
   * If the <code>key</code> is non-null, the token will be used securely
   * and the request to make the exchange will be signed.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @param onetimeUseToken the one time use token sent by Google in the URL
   * @param key the private key to sign the request
   * @return the session token.  <code>null</code> if the request failed
   * @throws IOException if error in writing/reading the request
   * @throws GeneralSecurityException if error in signing the request
   * @throws AuthenticationException if one time use token is rejected
   */
  public static String exchangeForSessionToken(String protocol,
                                               String domain,
                                               String onetimeUseToken,
                                               PrivateKey key)
      throws IOException, GeneralSecurityException, AuthenticationException {

    String sessionUrl = getSessionTokenUrl(protocol, domain);
    URL url = new URL(sessionUrl);
    HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();

    String header = formAuthorizationHeader(onetimeUseToken, key, url, "GET");
    httpConn.setRequestProperty("Authorization", header);

    if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
      throw new AuthenticationException(httpConn.getResponseCode() + ": "
                                        + httpConn.getResponseMessage());
    }

    // Parse the response
    String body =
        Characters.toString(
            new InputStreamReader(
                httpConn.getInputStream(), Charsets.ISO_8859_1));
    Map<String, String> parsedTokens =
      StringUtil.string2Map(body, "\n", "=", true /*stripEntry*/);
    parsedTokens = StringUtil.lowercaseKeys(parsedTokens);
    return parsedTokens.get("token");
  }


  /**
   * Retrieves information about the AuthSub token.
   * <p>
   * If the <code>key</code> is non-null, the token will be used securely
   * and the request to revoke the token will be signed.
   *
   * @param token the AuthSub token for which to receive information
   * @param key the private key to sign the request
   * @throws IOException if error in writing/reading the request
   * @throws GeneralSecurityException if error in signing the request
   * @throws AuthenticationException if the token is rejected
   * @return the token information in the form of a Map from the name of the
   *          attribute to the value of the attribute.
   */
  public static Map<String, String> getTokenInfo(String token,
                                                 PrivateKey key)
      throws IOException, GeneralSecurityException, AuthenticationException {

    return getTokenInfo(DEFAULT_PROTOCOL, DEFAULT_DOMAIN, token, key);
  }


  /**
   * Retrieves information about the AuthSub token.
   * <p>
   * If the <code>key</code> is non-null, the token will be used securely
   * and the request to revoke the token will be signed.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @param token the AuthSub token for which to receive information
   * @param key the private key to sign the request
   * @throws IOException if error in writing/reading the request
   * @throws GeneralSecurityException if error in signing the request
   * @throws AuthenticationException if the token is rejected
   * @return the token information in the form of a Map from the name of the
   *          attribute to the value of the attribute.
   */
  public static Map<String, String> getTokenInfo(String protocol,
                                                 String domain,
                                                 String token,
                                                 PrivateKey key)
      throws IOException, GeneralSecurityException, AuthenticationException {

    String tokenInfoUrl = getTokenInfoUrl(protocol, domain);
    URL url = new URL(tokenInfoUrl);
    HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();

    String header = formAuthorizationHeader(token, key, url, "GET");
    httpConn.setRequestProperty("Authorization", header);

    if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
      throw new AuthenticationException(httpConn.getResponseCode() + ": "
                                        + httpConn.getResponseMessage());
    }

    String body =
      Characters.toString(
          new InputStreamReader(
              httpConn.getInputStream(), Charsets.ISO_8859_1));
    return StringUtil.string2Map(body.trim(), "\n", "=", true);
  }

 
  /**
   * Revokes the specified token.
   * <p>
   * If the <code>key</code> is non-null, the token will be used securely
   * and the request to revoke the token will be signed.
   *
   * @param token the AuthSub token to revoke
   * @param key the private key to sign the request
   * @throws IOException if error in writing/reading the request
   * @throws GeneralSecurityException if error in signing the request
   * @throws AuthenticationException if the token is rejected
   */
  public static void revokeToken(String token,
                                 PrivateKey key)
      throws IOException, GeneralSecurityException, AuthenticationException {

    revokeToken(DEFAULT_PROTOCOL, DEFAULT_DOMAIN, token, key);
  }

 
  /**
   * Revokes the specified token.
   * <p>
   * If the <code>key</code> is non-null, the token will be used securely
   * and the request to revoke the token will be signed.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @param token the AuthSub token to revoke
   * @param key the private key to sign the request
   * @throws IOException if error in writing/reading the request
   * @throws GeneralSecurityException if error in signing the request
   * @throws AuthenticationException if the token is rejected
   */
  public static void revokeToken(String protocol,
                                 String domain,
                                 String token,
                                 PrivateKey key)
      throws IOException, GeneralSecurityException, AuthenticationException {

    String revokeUrl = getRevokeTokenUrl(protocol, domain);
    URL url = new URL(revokeUrl);
    HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();

    String header = formAuthorizationHeader(token, key, url, "GET");
    httpConn.setRequestProperty("Authorization", header);

    if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
      throw new AuthenticationException(httpConn.getResponseCode() + ": "
                                        + httpConn.getResponseMessage());
    }
  }


  /**
   * Forms the AuthSub authorization header.
   * <p>
   * If the <code>key</code> is null, the token will be used in insecure mode.
   * If the <code>key</code> is non-null, the token will be used securely and
   * the header will contain a signature.
   *
   * @param token the AuthSub token to use in the header
   * @param key the private key used to sign the request
   * @param requestUrl the URL of the request being issued
   * @param requestMethod the HTTP method being used to issue the request
   * @return the authorization header
   * @throws GeneralSecurityException if error occurs while creating signature
   */
  public static String formAuthorizationHeader(String token,
                                               PrivateKey key,
                                               URL requestUrl,
                                               String requestMethod)
      throws GeneralSecurityException {

    if (key == null) {
      return String.format("AuthSub token=\"%s\"", token);
    } else {
      // Form signature for secure mode
      long timestamp = System.currentTimeMillis() / 1000;
      long nonce = RANDOM.nextLong();
      String dataToSign =
        String.format("%s %s %d %s", requestMethod, requestUrl.toExternalForm(),
                      timestamp, unsignedLongToString(nonce));
      SignatureAlgorithm sigAlg = getSigAlg(key);
      byte[] signature = sign(key, dataToSign, sigAlg);
      String encodedSignature = Base64.encode(signature);
      return String.format("AuthSub token=\"%s\" data=\"%s\" sig=\"%s\" " +
                           "sigalg=\"%s\"", token, dataToSign, encodedSignature,
                           sigAlg.getAuthSubName());
    }
  }


  /**
   * Adds the query parameter with the given name and value to the URL.
   */
  private static void addParameter(StringBuffer url,
                                   String name,
                                   String value) {

    name = CharEscapers.uriEscaper().escape(name);
    value = CharEscapers.uriEscaper().escape(value);

    // Make sure the url currently ends with the correct delimiter
    if (url.indexOf("?") == -1) {
      url.append('?');
    } else {
      switch (url.charAt(url.length() - 1)) {
        case '?':
        case '&':
          break;
        default:
          url.append('&');
      }
    }

    // Append the name/value pair
    url.append(name).append('=').append(value);
  }


  /**
   * Signs the data with the given key and the provided algorithm.
   */
  private static byte[] sign(PrivateKey key,
                             String data,
                             SignatureAlgorithm algorithm)
      throws GeneralSecurityException {

    Signature signature = Signature.getInstance(algorithm.getJCAName());
    signature.initSign(key);
    signature.update(data.getBytes());
    return signature.sign();
  }


  /**
   * Returns the signature algorithm to be used for the provided private key.
   */
  private static SignatureAlgorithm getSigAlg(PrivateKey key) {
    String algorithm = key.getAlgorithm();
    if ("dsa".equalsIgnoreCase(algorithm)) {
      return SignatureAlgorithm.DSA_SHA1;
    } else if ("rsa".equalsIgnoreCase(algorithm)) {
      return SignatureAlgorithm.RSA_SHA1;
    } else {
      throw new IllegalArgumentException("Unknown algorithm in private key.");
    }
  }


  /**
   * Returns the URL to use to exchange the one-time-use token for
   * a session token.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @return the URL to exchange for the session token
   */
  private static String getSessionTokenUrl(String protocol,
                                           String domain) {
    return protocol + "://" + domain + "/accounts/AuthSubSessionToken";
  }


  /**
   * Returns the URL that handles token revocation.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @return the URL that handles token revocation.
   */
  private static String getRevokeTokenUrl(String protocol,
                                          String domain) {
    return protocol + "://" + domain + "/accounts/AuthSubRevokeToken";
  }


  /**
   * Returns the URL that handles token revocation.
   *
   * @param protocol the protocol to use to communicate with the server
   * @param domain the domain at which the authentication server exists
   * @return the URL that handles token revocation.
   */
  private static String getTokenInfoUrl(String protocol,
                                        String domain) {
    return protocol + "://" + domain + "/accounts/AuthSubTokenInfo";
  }


  /**
   *  Treats the provided long as unsigned and converts it to a string.
   */
  private static String unsignedLongToString(long value) {
    if (value >= 0) {
      return Long.toString(value);
    } else {
      // Split into two unsigned halves.  As digits are printed out from
      // the bottom half, move data from the top half into the bottom
      // half
      int max_dig = 20;
      char[] cbuf = new char[max_dig];
      int radix = 10;
      int dst = max_dig;
      long top = value >>> 32;
      long bot = value & 0xffffffffl;
      bot += (top % radix) << 32;
      top /= radix;
      while (bot > 0 || top > 0) {
        cbuf[--dst] = Character.forDigit((int)(bot % radix), radix);
        bot = (bot / radix) + ((top % radix) << 32);
        top /= radix;
      }
      return new String(cbuf, dst, max_dig-dst);
    }
  }
}
TOP

Related Classes of com.google.gdata.client.http.AuthSubUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.