/* * Copyright 2008 Web Cohesion * * 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 org.springframework.security.oauth.provider.filter; import org.apache.commons.codec.DecoderException; import static org.springframework.security.oauth.common.OAuthCodec.oauthDecode; import static org.springframework.security.oauth.common.OAuthCodec.oauthEncode; import org.springframework.security.oauth.common.OAuthConsumerParameter; import org.springframework.security.oauth.common.StringSplitUtils; import org.springframework.security.oauth.provider.OAuthProviderSupport; import javax.servlet.http.HttpServletRequest; import java.util.*; import java.net.URL; import java.net.MalformedURLException; /** * Utility for common logic for supporting an OAuth provider. * * @author Ryan Heaton */ public class CoreOAuthProviderSupport implements OAuthProviderSupport { private final Set<String> supportedOAuthParameters; private String baseUrl = null; public CoreOAuthProviderSupport() { Set<String> supportedOAuthParameters = new TreeSet<String>(); for (OAuthConsumerParameter supportedParameter : OAuthConsumerParameter.values()) { supportedOAuthParameters.add(supportedParameter.toString()); } this.supportedOAuthParameters = supportedOAuthParameters; } // Inherited. public Map<String, String> parseParameters(HttpServletRequest request) { Map<String, String> parameters = parseHeaderParameters(request); if (parameters == null) { //if there is no header authorization parameters, then the oauth parameters are the supported OAuth request parameters. parameters = new HashMap<String, String>(); for (String supportedOAuthParameter : getSupportedOAuthParameters()) { String param = request.getParameter(supportedOAuthParameter); if (param != null) { parameters.put(supportedOAuthParameter, param); } } } return parameters; } /** * Parse the OAuth header parameters. The parameters will be oauth-decoded. * * @param request The request. * @return The parsed parameters, or null if no OAuth authorization header was supplied. */ protected Map<String, String> parseHeaderParameters(HttpServletRequest request) { String header = null; Enumeration<String> headers = request.getHeaders("Authorization"); while (headers.hasMoreElements()) { String value = headers.nextElement(); if ((value.toLowerCase().startsWith("oauth "))) { header = value; break; } } Map<String, String> parameters = null; if (header != null) { parameters = new HashMap<String, String>(); String authHeaderValue = header.substring(6); //create a map of the authorization header values per OAuth Core 1.0, section 5.4.1 String[] headerEntries = StringSplitUtils.splitIgnoringQuotes(authHeaderValue, ','); for (Object o : StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"").entrySet()) { Map.Entry entry = (Map.Entry) o; try { String key = oauthDecode((String) entry.getKey()); String value = oauthDecode((String) entry.getValue()); parameters.put(key, value); } catch (DecoderException e) { throw new IllegalStateException(e); } } } return parameters; } /** * Get the supported OAuth parameters. The default implementation supports only the OAuth core parameters. * * @return The OAuth core parameters. */ protected Set<String> getSupportedOAuthParameters() { return this.supportedOAuthParameters; } // Inherited. public String getSignatureBaseString(HttpServletRequest request) { SortedMap<String, SortedSet<String>> significantParameters = loadSignificantParametersForSignatureBaseString(request); //now concatenate them into a single query string according to the spec. StringBuilder queryString = new StringBuilder(); Iterator<Map.Entry<String, SortedSet<String>>> paramIt = significantParameters.entrySet().iterator(); while (paramIt.hasNext()) { Map.Entry<String, SortedSet<String>> sortedParameter = paramIt.next(); Iterator<String> valueIt = sortedParameter.getValue().iterator(); while (valueIt.hasNext()) { String parameterValue = valueIt.next(); queryString.append(sortedParameter.getKey()).append('=').append(parameterValue); if (paramIt.hasNext() || valueIt.hasNext()) { queryString.append('&'); } } } String url = getBaseUrl(request); if (url == null) { //if no URL is configured, then we'll attempt to reconstruct the URL. This may be inaccurate. url = request.getRequestURL().toString(); } url = normalizeUrl(url); url = oauthEncode(url); String method = request.getMethod().toUpperCase(); return new StringBuilder(method).append('&').append(url).append('&').append(oauthEncode(queryString.toString())).toString(); } /** * Normalize the URL for use in the signature. The OAuth spec says the URL protocol and host are to be lower-case, * and the query and fragments are to be stripped. * * @param url The URL. * @return The URL normalized for use in the signature. */ protected String normalizeUrl(String url) { try { URL requestURL = new URL(url); StringBuilder normalized = new StringBuilder(requestURL.getProtocol().toLowerCase()).append("://").append(requestURL.getHost().toLowerCase()); if ((requestURL.getPort() >= 0) && (requestURL.getPort() != requestURL.getDefaultPort())) { normalized.append(":").append(requestURL.getPort()); } normalized.append(requestURL.getPath()); return normalized.toString(); } catch (MalformedURLException e) { throw new IllegalStateException("Illegal URL for calculating the OAuth signature.", e); } } /** * Loads the significant parameters (name-to-value map) that are to be used to calculate the signature base string. * The parameters will be encoded, per the spec section 9.1. * * @param request The request. * @return The significan parameters. */ protected SortedMap<String, SortedSet<String>> loadSignificantParametersForSignatureBaseString(HttpServletRequest request) { //first collect the relevant parameters... SortedMap<String, SortedSet<String>> significantParameters = new TreeMap<String, SortedSet<String>>(); //first pull from the request... Enumeration parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String parameterName = (String) parameterNames.nextElement(); String[] values = request.getParameterValues(parameterName); if (values == null) { values = new String[]{ "" }; } parameterName = oauthEncode(parameterName); for (String parameterValue : values) { if (parameterValue == null) { parameterValue = ""; } parameterValue = oauthEncode(parameterValue); SortedSet<String> significantValues = significantParameters.get(parameterName); if (significantValues == null) { significantValues = new TreeSet<String>(); significantParameters.put(parameterName, significantValues); } significantValues.add(parameterValue); } } //then take into account the header parameter values... Map<String, String> oauthParams = parseParameters(request); oauthParams.remove("realm"); //remove the realm Set<String> parsedParams = oauthParams.keySet(); for (String parameterName : parsedParams) { String parameterValue = oauthParams.get(parameterName); if (parameterValue == null) { parameterValue = ""; } parameterName = oauthEncode(parameterName); parameterValue = oauthEncode(parameterValue); SortedSet<String> significantValues = significantParameters.get(parameterName); if (significantValues == null) { significantValues = new TreeSet<String>(); significantParameters.put(parameterName, significantValues); } significantValues.add(parameterValue); } //remove the oauth signature parameter value. significantParameters.remove(OAuthConsumerParameter.oauth_signature.toString()); return significantParameters; } /** * The configured base URL for this OAuth provider for the given HttpServletRequest. Default implementation return getBaseUrl() + request URI. * * @param request The HttpServletRequest currently processed * @return The configured base URL for this OAuth provider with respect to the supplied HttpServletRequest. */ protected String getBaseUrl(HttpServletRequest request) { String baseUrl = getBaseUrl(); if (baseUrl != null) { StringBuilder builder = new StringBuilder(baseUrl); String path = request.getRequestURI(); if (path != null && !"".equals(path)) { if (!baseUrl.endsWith("/") && !path.startsWith("/")) { builder.append('/'); } builder.append(path); } baseUrl = builder.toString(); } return baseUrl; } /** * The configured base URL for this OAuth provider. * * @return The configured base URL for this OAuth provider. */ public String getBaseUrl() { return baseUrl; } /** * The configured base URL for the OAuth provider. * * @param baseUrl The configured base URL for the OAuth provider. */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } }