com.sangupta.jerry.oauth.OAuthUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.sangupta.jerry.oauth.OAuthUtils.java

Source

/**
 *
 * jerry-oauth : Common Java OAuth functionality
 * Copyright (c) 2012-2014, Sandeep Gupta
 * 
 * http://sangupta.com/projects/jerry-oauth
 * 
 * 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.sangupta.jerry.oauth;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sangupta.jerry.encoder.Base64Encoder;
import com.sangupta.jerry.exceptions.NotImplementedException;
import com.sangupta.jerry.http.HttpHeaderName;
import com.sangupta.jerry.http.WebForm;
import com.sangupta.jerry.http.WebInvoker;
import com.sangupta.jerry.http.WebRequest;
import com.sangupta.jerry.http.WebRequestMethod;
import com.sangupta.jerry.oauth.domain.KeySecretPair;
import com.sangupta.jerry.oauth.domain.OAuthConstants;
import com.sangupta.jerry.oauth.domain.OAuthSignatureMethod;
import com.sangupta.jerry.oauth.nonce.NonceUtils;
import com.sangupta.jerry.util.AssertUtils;
import com.sangupta.jerry.util.StringUtils;
import com.sangupta.jerry.util.UriUtils;

/**
 * Utility methods to work with OAuth based requests.
 * 
 * @author sangupta
 * @since 1.0
 */
public class OAuthUtils {

    /**
     * Logger to be used
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(OAuthUtils.class);

    /**
     * Sign the given {@link WebRequest} with the given application
     * {@link KeySecretPair} and user's {@link KeySecretPair}. The signature
     * thus computed is added to the <code>authorizationParameters</code>
     * supplied in over here. The {@link WebForm} values are NOT appended to the
     * {@link WebRequest} supplied, and callee's should add it themselves
     * depending if the params are added in body or as a header, and with what
     * header name.
     * 
     * @param request
     *            the request that needs to be signed
     * 
     * @param keySecretPair
     *            the application specific key-secret pair to use
     * 
     * @param userSecretPair
     *            the user-specific key-secret pair to use
     * 
     * @param oAuthSignatureMethod
     *            the signature method to use for computing the signatures
     * 
     * @param authorizationParameters
     *            the authorization parameters that need to be used while
     *            signing. The OAuth signature param will be added to this list
     *            of parameters
     * 
     */
    public static void signRequest(WebRequest request, KeySecretPair keySecretPair, KeySecretPair userSecretPair,
            OAuthSignatureMethod oAuthSignatureMethod, WebForm authorizationParameters) {
        StringBuilder builder = new StringBuilder();

        // first the HTTP VERB
        builder.append(request.getVerb().toString().toUpperCase());
        builder.append("&");

        // then the end point without any query parameters or fragment
        URI uri = request.getURI();
        builder.append(getSignableBase(uri));
        builder.append("&");

        // collect all parameters
        TreeMap<String, String> requestParams = extractURIParameters(uri);
        String paramString = buildParamString(requestParams, authorizationParameters);
        builder.append(UriUtils.encodeURIComponent(paramString));

        // now build up the signing string
        final String signable = builder.toString();
        LOGGER.debug("Signable string generated as: {}", signable);

        // compute the signature
        String signature = generateSignature(keySecretPair, userSecretPair, signable, oAuthSignatureMethod);
        LOGGER.debug("Signature generated as: {}", signature);

        authorizationParameters.addParam(OAuthConstants.SIGNATURE, signature);
    }

    /**
     * Build authorization parameters as request query params.
     * 
     * @param request
     *            the request to which the authorization header is added.
     * 
     * @param webForm
     *            the form containing various parameters for authorization. Only
     *            the parameters starting with <code>oauth_</code> prefix are
     *            added to the header
     * 
     * @throws IllegalArgumentException
     *             if the request, or webForm is <code>null</code>
     */
    public static void buildAuthorizationQuery(WebRequest request, WebForm webForm) {
        if (request == null) {
            throw new IllegalArgumentException("WebRequest to sign cannot be null");
        }

        if (webForm == null) {
            throw new IllegalArgumentException("WebForm containing signing parameters cannot be null");
        }

        throw new NotImplementedException();
    }

    /**
     * Build and Add an authorization header for the given request and the
     * {@link WebForm} parameters. The header name used is
     * {@link HttpHeaderName#AUTHORIZATION} and the prefix used is
     * {@link OAuthConstants#OAUTH_AUTHORIZATION_HEADER_PREFIX}.
     * 
     * @param request
     *            the request to which the authorization header is added.
     * 
     * @param webForm
     *            the form containing various parameters for authorization. Only
     *            the parameters starting with <code>oauth_</code> prefix are
     *            added to the header
     */
    public static void buildAuthorizationHeader(WebRequest request, WebForm webForm) {
        OAuthUtils.buildAuthorizationHeader(request, webForm, HttpHeaderName.AUTHORIZATION,
                OAuthConstants.OAUTH_AUTHORIZATION_HEADER_PREFIX);
    }

    /**
     * Build and Add an authorization header for the given request and the
     * {@link WebForm} parameters. The header name used is
     * {@link HttpHeaderName#AUTHORIZATION} and the prefix used is
     * {@link OAuthConstants#OAUTH_AUTHORIZATION_HEADER_PREFIX}.
     * 
     * @param request
     *            the request to which the authorization header is added.
     * 
     * @param webForm
     *            the form containing various parameters for authorization. Only
     *            the parameters starting with <code>oauth_</code> prefix are
     *            added to the header
     * 
     * @param authorizationHeaderName
     *            the header name to be used.
     * 
     * @param authorizationHeaderPrefix
     *            the header value prefix to to be used
     * 
     * @throws IllegalArgumentException
     *             if the request, webForm, or authorizationHeaderName are null
     *             or empty
     */
    public static void buildAuthorizationHeader(WebRequest request, WebForm webForm, String authorizationHeaderName,
            String authorizationHeaderPrefix) {
        if (request == null) {
            throw new IllegalArgumentException("WebRequest to sign cannot be null");
        }

        if (webForm == null) {
            throw new IllegalArgumentException("WebForm containing signing parameters cannot be null");
        }

        if (AssertUtils.isEmpty(authorizationHeaderName)) {
            throw new IllegalArgumentException("Authorization header name to use cannot be null/empty");
        }

        StringBuilder builder = new StringBuilder(1024);
        if (AssertUtils.isNotBlank(authorizationHeaderPrefix)) {
            builder.append(authorizationHeaderPrefix);
            builder.append(' ');
        }

        // start adding all authorization params
        List<NameValuePair> pairs = webForm.build();
        for (int index = 0; index < pairs.size(); index++) {
            NameValuePair pair = pairs.get(index);

            if (index > 0) {
                builder.append(", ");
            }

            builder.append(pair.getName());
            builder.append("=\"");
            builder.append(UriUtils.encodeURIComponent(pair.getValue()));
            builder.append("\"");
        }

        String headerValue = builder.toString();
        LOGGER.debug("OAuth header built as: {}: {}", authorizationHeaderName, headerValue);
        request.addHeader(authorizationHeaderName, headerValue);
    }

    /**
     * Given a list of parameters (including the OAuth parameters) build the
     * unique parameter string that is used to generate the signable string.
     * 
     * @param params
     *            the request parameters if any
     * 
     * @param oauthParams
     *            the OAuth params
     * 
     * @return the parameters string to be used to generate the signable string
     */
    public static String buildParamString(TreeMap<String, String> params, WebForm oauthParams) {
        StringBuilder builder = new StringBuilder(1024);

        // add all to the list of params
        for (NameValuePair pair : oauthParams.build()) {
            if (pair.getName().startsWith("oauth_")) {
                params.put(pair.getName(), pair.getValue());
            }
        }

        // build the string
        boolean first = true;
        for (String key : params.keySet()) {
            if (!first) {
                builder.append('&');
            } else {
                first = false;
            }

            builder.append(key);
            builder.append('=');
            builder.append(UriUtils.encodeURIComponent(params.get(key)));
        }

        return builder.toString();
    }

    /**
     * Extract all the query parameters from the URI.
     * 
     * @param uri
     *            the {@link URI} from which the params need to be extracted
     * 
     * @return a {@link TreeMap} containing all query parameters. Never returns
     *         a <code>null</code>
     * 
     * @throws NullPointerException
     *             if {@link URI} presented is <code>null</code>
     */
    public static TreeMap<String, String> extractURIParameters(URI uri) {
        final TreeMap<String, String> params = new TreeMap<String, String>();

        String query = uri.getQuery();
        if (AssertUtils.isEmpty(query)) {
            return params;
        }

        String[] pairs = query.split("&");
        for (String pair : pairs) {
            String[] tokens = pair.split("=");
            params.put(tokens[0], tokens[1]);
        }

        return params;
    }

    /**
     * Return the base string ready to be included in signable-string. The
     * difference between this method and {@link #getSigningBaseURL(String)} is
     * that the return value will be percent-encoded, if needed.
     * 
     * @param url
     *            the URL from which the signable base string needs to be
     *            constructed
     * 
     * @return the base string that can be signed
     * 
     * @throws URISyntaxException
     *             if the url is not in proper format
     */
    public static String getSignableBase(String url) throws URISyntaxException {
        return UriUtils.encodeURIComponent(getSigningBaseURL(url), true);
    }

    /**
     * Return the base string ready to be included in signable-string. The
     * difference between this method and {@link #getSigningBaseURL(URI)} is
     * that the return value will be percent-encoded, if needed.
     * 
     * @param uri
     *            the {@link URI} from which the signable base string is
     *            contructed
     * 
     * @return the base string that can be signed
     * 
     * @throws IllegalArgumentException
     *             is {@link URI} presented is <code>null</code>
     */
    public static String getSignableBase(URI uri) {
        return UriUtils.encodeURIComponent(getSigningBaseURL(uri), true);
    }

    /**
     * Return the signing base URL that is appended after the HTTP VERB in OAuth
     * header.
     * 
     * @param url
     *            the {@link String} based URL from which the signing base URL
     *            is extracted
     * 
     * @return the signing base extracted from the URL
     * 
     * @throws URISyntaxException
     *             if the URL is malformed
     */
    public static String getSigningBaseURL(String url) throws URISyntaxException {
        if (AssertUtils.isEmpty(url)) {
            throw new IllegalArgumentException("URL cannot be null/empty");
        }

        return getSigningBaseURL(new URI(url));
    }

    /**
     * Return the signing base URL that is appended after the HTTP VERB in OAuth
     * header.
     * 
     * @param uri
     *            the {@link URI} instance from which the signing base is
     *            extracted
     * 
     * @return the extracted signing base from the {@link URI} instance
     */
    public static String getSigningBaseURL(URI uri) {
        if (uri == null) {
            throw new IllegalArgumentException("URI cannot be null");
        }

        StringBuilder builder = new StringBuilder();
        builder.append(uri.getScheme().toLowerCase());
        builder.append("://");
        builder.append(uri.getHost().toLowerCase());

        int port = uri.getPort();
        if (!(port == 80 || port == -1)) {
            builder.append(':');
            builder.append(String.valueOf(port));
        }

        builder.append(uri.getPath());

        return builder.toString();
    }

    /**
     * 
     * @param endPoint
     * @param method
     * @param oAuthHeaderName
     * @param consumerKey
     * @param consumerSecret
     * @param signatureMethod
     * @param oAuthVersion
     * @param requestParams
     * @param includeOAuthParamsInBody
     * @return
     */
    public static WebRequest createOAuthRequest(String endPoint, WebRequestMethod method,
            OAuthSignatureMethod signatureMethod, String oAuthVersion, String oAuthHeaderName, String consumerKey,
            String consumerSecret, String timestamp, String nonce, Map<String, String> requestParams,
            boolean includeOAuthParamsInBody) {

        StringBuilder builder = new StringBuilder();
        builder.append(method.toString().toUpperCase());
        builder.append("&");

        builder.append(UriUtils.encodeURIComponent(endPoint, true));

        TreeMap<String, String> params = new TreeMap<String, String>();
        params.put(OAuthConstants.CONSUMER_KEY, consumerKey);
        params.put(OAuthConstants.NONCE, NonceUtils.getNonce());
        params.put(OAuthConstants.SIGNATURE_METHOD, signatureMethod.getOAuthName());
        params.put(OAuthConstants.TIMESTAMP, String.valueOf(System.currentTimeMillis()));
        params.put(OAuthConstants.VERSION, oAuthVersion);

        if (AssertUtils.isNotEmpty(requestParams)) {
            for (Entry<String, String> entry : requestParams.entrySet()) {
                params.put(entry.getKey(), entry.getValue());
            }
        }

        String paramString = generateParamString(params, true);

        builder.append("&");
        builder.append(UriUtils.encodeURIComponent(paramString, true));

        LOGGER.debug("Signable: {}", builder.toString());

        String signature = generateSignature(consumerSecret, "", builder.toString(), signatureMethod);
        params.put(OAuthConstants.SIGNATURE, signature);

        // build oauth header
        WebRequest request = WebInvoker.getWebRequest(endPoint, method);
        if (oAuthHeaderName != null) {
            request.addHeader(oAuthHeaderName, "OAuth " + getAllOAuthParams(params));
        }

        request.bodyForm(getBodyParams(params, includeOAuthParamsInBody));

        return request;
    }

    /**
     * @param endPoint
     * @param method
     * @param signatureMethod
     * @param oAuthVersion
     * @param authorizationHeader
     * @param consumerKey
     * @param consumerSecret
     * @param tokenKey
     * @param tokenSecret
     * @param params
     * @param includeOAuthParamsInBody
     * @return
     */
    public static WebRequest createUserSignedOAuthRequest(String endPoint, WebRequestMethod method,
            OAuthSignatureMethod signatureMethod, String oAuthVersion, String oAuthHeaderName, String consumerKey,
            String consumerSecret, String tokenKey, String tokenSecret, String timestamp, String nonce,
            Map<String, String> requestParams, boolean includeOAuthParamsInBody) {

        StringBuilder builder = new StringBuilder();
        builder.append(method.toString().toUpperCase());
        builder.append("&");

        builder.append(UriUtils.encodeURIComponent(endPoint, true));

        TreeMap<String, String> params = new TreeMap<String, String>();
        params.put(OAuthConstants.CONSUMER_KEY, consumerKey);
        params.put(OAuthConstants.NONCE, NonceUtils.getNonce());
        params.put(OAuthConstants.SIGNATURE_METHOD, signatureMethod.getOAuthName());
        params.put(OAuthConstants.TIMESTAMP, String.valueOf(System.currentTimeMillis()));
        params.put(OAuthConstants.VERSION, oAuthVersion);
        params.put(OAuthConstants.TOKEN, tokenKey);

        if (AssertUtils.isNotEmpty(requestParams)) {
            for (Entry<String, String> entry : requestParams.entrySet()) {
                params.put(entry.getKey(), entry.getValue());
            }
        }

        String paramString = generateParamString(params, true);

        builder.append("&");
        builder.append(UriUtils.encodeURIComponent(paramString, true));

        LOGGER.debug("Signable: {}", builder.toString());

        String signature = generateSignature(consumerSecret, tokenSecret, builder.toString(), signatureMethod);
        params.put(OAuthConstants.SIGNATURE, signature);

        // build oauth header
        WebRequest request = WebInvoker.getWebRequest(endPoint, method);
        if (oAuthHeaderName != null) {
            request.addHeader(oAuthHeaderName, "OAuth " + getAllOAuthParams(params));
        }

        List<NameValuePair> pairs = getBodyParams(params, includeOAuthParamsInBody);
        if (pairs != null && !pairs.isEmpty()) {
            request.bodyForm(pairs);
        }

        return request;
    }

    /**
     * Get a list of all non-aouth params from the given map.
     * 
     * @param params
     * @return
     */
    private static List<NameValuePair> getBodyParams(TreeMap<String, String> params,
            boolean includeOAuthParamsInBody) {
        final List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

        for (Entry<String, String> entry : params.entrySet()) {
            String key = entry.getKey();
            if (!includeOAuthParamsInBody && key.startsWith("oauth_")) {
                continue;
            }

            nameValuePairs.add(new BasicNameValuePair(key, entry.getValue()));
        }

        return nameValuePairs;
    }

    /**
     * Read all oauth params from the given map of parameters.
     * 
     * @param params
     * @param signature
     * @return
     */
    private static String getAllOAuthParams(TreeMap<String, String> params) {
        StringBuilder builder = new StringBuilder();
        boolean first = true;

        for (String key : params.keySet()) {
            if (!key.startsWith("oauth_")) {
                continue;
            }

            if (!first) {
                builder.append(',');
            } else {
                first = false;
            }

            builder.append(key);
            builder.append("=\"");
            builder.append(UriUtils.encodeURIComponent(params.get(key), true));
            builder.append('"');
        }

        return builder.toString();
    }

    /**
     * 
     * @param consumer
     * @param userToken
     * @param signable
     * @param signingMethod
     * @return
     */
    public static String generateSignature(KeySecretPair consumer, KeySecretPair userToken, String signable,
            OAuthSignatureMethod signingMethod) {
        if (userToken == null) {
            return generateSignature(consumer.getSecret(), null, signable, signingMethod);
        }

        return generateSignature(consumer.getSecret(), userToken.getSecret(), signable, signingMethod);
    }

    /**
     * Generate an OAUTH signature for the given signature string.
     * 
     * @param consumerSecret
     *            the consumer or application specific secret to use
     * 
     * @param tokenSecret
     *            the user specific secret to use
     * 
     * @param signable
     *            the string to be signed
     * 
     * @param signingMethod
     *            the signing method to use
     * 
     * @return the signature generated, or <code>null</code> if some exception
     *         occurs
     * 
     * @throws NullPointerException
     *             if the signable string is <code>null</code>/empty; or, if the
     *             consumer secret is <code>null</code>/empty.
     */
    public static String generateSignature(String consumerSecret, String tokenSecret, String signable,
            OAuthSignatureMethod signingMethod) {
        if (AssertUtils.isEmpty(consumerSecret)) {
            throw new IllegalArgumentException("Consumer secret cannot be null/empty");
        }

        final String signingKey;
        if (AssertUtils.isNotEmpty(tokenSecret)) {
            signingKey = UriUtils.encodeURIComponent(consumerSecret, false) + "&"
                    + UriUtils.encodeURIComponent(tokenSecret, false);
        } else {
            signingKey = UriUtils.encodeURIComponent(consumerSecret, false) + "&";
        }

        return createSignature(signable, signingKey, signingMethod);
    }

    /**
     * Generate the signature using the given signing method for the signable
     * using the key string. For OAuth the key string should already be
     * URI-percent-encoded if need be.
     * 
     * @param signable
     *            the string for which the signature needs to be generated
     * 
     * @param keyString
     *            the key string to be used
     * 
     * @param signingMethod
     *            the signing method to be used
     * 
     * @return the signature generated, or <code>null</code> if some exception
     *         occurs
     * 
     * @throws NullPointerException
     *             if the signable string is <code>null</code>/empty.
     */
    public static String createSignature(String signable, String keyString, OAuthSignatureMethod signingMethod) {
        if (AssertUtils.isEmpty(signable)) {
            throw new IllegalArgumentException("Signable string cannot be null/empty");
        }

        if (signingMethod == OAuthSignatureMethod.PLAIN_TEXT) {
            return keyString;
        }

        SecretKeySpec key = new SecretKeySpec((keyString).getBytes(StringUtils.CHARSET_UTF8),
                signingMethod.getAlgorithmName());
        Mac mac;
        try {
            mac = Mac.getInstance(signingMethod.getAlgorithmName());
            mac.init(key);
            byte[] bytes = mac.doFinal(signable.getBytes(StringUtils.CHARSET_UTF8));
            return Base64Encoder.encodeToString(bytes, false);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Generate a sorted parameter string for the given parameters. All
     * parameters are appended into a string form.
     * 
     * @param params
     *            the request parameters that need to be appended
     * 
     * @param encodeParamValues
     *            whether to URL encode the parameters or not
     * 
     * @return the URL query string representation of all parameters
     */
    public static String generateParamString(TreeMap<String, String> params, boolean encodeParamValues) {
        if (AssertUtils.isEmpty(params)) {
            return StringUtils.EMPTY_STRING;
        }

        StringBuilder builder = new StringBuilder();
        boolean first = true;

        for (String key : params.keySet()) {
            if (first) {
                first = false;
            } else {
                builder.append('&');
            }

            builder.append(key);
            builder.append("=");
            if (encodeParamValues) {
                builder.append(UriUtils.encodeURIComponent(params.get(key), true));
            } else {
                builder.append(params.get(key));
            }
        }

        return builder.toString();
    }

}