/* 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.authn.oauth; import com.google.gdata.util.common.base.CharEscapers; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; /** * A general container for methods related to OAuth. * * */ public class OAuthUtil { private OAuthUtil() { } /** * Get a nonce for an OAuth request. OAuth defines the nonce as "a random * string, uniquely generated for each request. The nonce allows the Service * Provider to verify that a request has never been made before and helps * prevent replay attacks when requests are made over a non-secure channel * (such as HTTP)." * * @return the nonce string to use in an OAuth request */ public static String getNonce() { return Long.toString(System.nanoTime()); } /** * Get a timestamp for an OAuth request. OAuth defines the timestamp as * "the number of seconds since January 1, 1970 00:00:00 GMT. The timestamp * value MUST be a positive integer and MUST be equal or greater than the * timestamp used in previous requests." * * @return the timestamp string to use in an OAuth request. */ public static String getTimestamp() { return Long.toString(System.currentTimeMillis() / 1000); } /** * Calculates the signature base url as per section 9.1 of the OAuth Spec. * This is a concatenation of http method, request url, and other request * parameters. * * @see <a href="http://oauth.net/core/1.0/#anchor14">9.1 Signature Base * String</a> * * @param requestUrl the url of the request * @param httpMethod the http method, for example "GET" or "PUT" * @param baseParameters the request parameters (see section 9.1.3) * @return the base string to be used in the OAuth signature * @throws OAuthException if the input url is not formatted properly */ public static String getSignatureBaseString(String requestUrl, String httpMethod, Map<String, String> baseParameters) throws OAuthException { return encode(httpMethod.toUpperCase()) + '&' + encode(normalizeUrl(requestUrl)) + '&' + encode(normalizeParameters(requestUrl, baseParameters)); } /** * Calculates the normalized request url, as per section 9.1.2 of the OAuth * Spec. This removes the querystring from the url and the port (if it is * the standard http or https port). * * @see <a href="http://oauth.net/core/1.0/#rfc.section.9.1.2">9.1.2 * Construct Request URL</a> * * @param requestUrl the request url to normalize (not <code>null</code>) * @return the normalized request url, as per the rules in the link above * @throws OAuthException if the input url is not formatted properly */ public static String normalizeUrl(String requestUrl) throws OAuthException { // validate the request url if ((requestUrl == null) || (requestUrl.length() == 0)) { throw new OAuthException("Request Url cannot be empty"); } // parse the url into its constituent parts. URI uri; try { uri = new URI(requestUrl); } catch (URISyntaxException e) { throw new OAuthException(e); } String authority = uri.getAuthority(); String scheme = uri.getScheme(); if (authority == null || scheme == null) { throw new OAuthException("Invalid Request Url"); } authority = authority.toLowerCase(); scheme = scheme.toLowerCase(); // if this url contains the standard port, remove it if ((scheme.equals("http") && uri.getPort() == 80) || (scheme.equals("https") && uri.getPort() == 443)) { int index = authority.lastIndexOf(":"); if (index >= 0) { authority = authority.substring(0, index); } } // piece together the url without the querystring return scheme + "://" + authority + uri.getRawPath(); } /** * Calculates the normalized request parameters string to use in the base * string, as per section 9.1.1 of the OAuth Spec. * * @see <a href="http://oauth.net/core/1.0/#rfc.section.9.1.1">9.1.1 * Normalize Request Parameters</a> * * @param requestUrl the request url to normalize (not <code>null</code>) * @param requestParameters key/value pairs of parameters in the request * @return the parameters normalized to a string */ public static String normalizeParameters( String requestUrl, Map<String, String> requestParameters) { // use a TreeMap to alphabetize the parameters by key TreeMap<String, String> alphaParams = new TreeMap<String, String>(requestParameters); // add the querystring to the base string (if one exists) if (requestUrl.indexOf('?') > 0) { Map<String, String> queryParameters = parseQuerystring(requestUrl.substring(requestUrl.indexOf('?')+1)); alphaParams.putAll(queryParameters); } // piece together the base string, encoding each key and value StringBuilder paramString = new StringBuilder(); for (Map.Entry<String, String> e : alphaParams.entrySet()) { if (e.getValue().length() == 0) { continue; } if (paramString.length() > 0) { paramString.append("&"); } paramString.append(encode(e.getKey())).append("=") .append(encode(e.getValue())); } return paramString.toString(); } /** * Parse a querystring into a map of key/value pairs. * * @param queryString the string to parse (without the '?') * @return key/value pairs mapping to the items in the querystring */ public static Map<String, String> parseQuerystring(String queryString) { Map<String, String> map = new HashMap<String, String>(); if ((queryString == null) || (queryString.equals(""))) { return map; } String[] params = queryString.split("&"); for (String param : params) { try { String[] keyValuePair = param.split("=", 2); String name = URLDecoder.decode(keyValuePair[0], "UTF-8"); if (name == "") { continue; } String value = keyValuePair.length > 1 ? URLDecoder.decode(keyValuePair[1], "UTF-8") : ""; map.put(name, value); } catch (UnsupportedEncodingException e) { // ignore this parameter if it can't be decoded } } return map; } /** * Formats the input string for inclusion in a url. Account for the * differences in how OAuth encodes certain characters (such as the * space character). * * @param stringToEncode the string to encode * @return the url-encoded string */ public static String encode(String stringToEncode) { return CharEscapers.uriEscaper().escape(stringToEncode).replace("+", "%20") .replace("*", "%2A").replace("%7E", "~"); } }