/* * Copyright 2008-2009 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.consumer.client; import org.apache.commons.codec.DecoderException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth.common.OAuthCodec; import org.springframework.security.oauth.common.OAuthConsumerParameter; import org.springframework.security.oauth.common.OAuthProviderParameter; import org.springframework.security.oauth.common.StringSplitUtils; import org.springframework.security.oauth.common.signature.CoreOAuthSignatureMethodFactory; import org.springframework.security.oauth.common.signature.OAuthSignatureMethod; import org.springframework.security.oauth.common.signature.OAuthSignatureMethodFactory; import org.springframework.security.oauth.common.signature.UnsupportedSignatureMethodException; import org.springframework.security.oauth.consumer.InvalidOAuthRealmException; import org.springframework.security.oauth.consumer.OAuthConsumerSupport; import org.springframework.security.oauth.consumer.OAuthConsumerToken; import org.springframework.security.oauth.consumer.OAuthRequestFailedException; import org.springframework.security.oauth.consumer.ProtectedResourceDetails; import org.springframework.security.oauth.consumer.ProtectedResourceDetailsService; import org.springframework.security.oauth.consumer.UnverifiedRequestTokenException; import org.springframework.security.oauth.consumer.net.OAuthURLStreamHandlerFactory; import org.springframework.security.oauth.consumer.nonce.NonceFactory; import org.springframework.security.oauth.consumer.nonce.UUIDNonceFactory; import org.springframework.util.Assert; import java.io.*; import java.net.*; import java.util.*; import static org.springframework.security.oauth.common.OAuthCodec.oauthEncode; /** * Consumer-side support for OAuth. This support uses a {@link java.net.URLConnection} to interface with the * OAuth provider. A proxy will be selected, but it is assumed that the {@link javax.net.ssl.TrustManager}s * and other connection-related environment variables are already set up. * * @author Ryan Heaton * @author Andrew McCall */ public class CoreOAuthConsumerSupport implements OAuthConsumerSupport, InitializingBean { private OAuthURLStreamHandlerFactory streamHandlerFactory; private OAuthSignatureMethodFactory signatureFactory = new CoreOAuthSignatureMethodFactory(); private NonceFactory nonceFactory = new UUIDNonceFactory(); private ProtectedResourceDetailsService protectedResourceDetailsService; private ProxySelector proxySelector = ProxySelector.getDefault(); private int connectionTimeout = 1000 * 60; private int readTimeout = 1000 * 60; public CoreOAuthConsumerSupport() { try { this.streamHandlerFactory = ((OAuthURLStreamHandlerFactory) Class.forName("org.springframework.security.oauth.consumer.net.DefaultOAuthURLStreamHandlerFactory").newInstance()); } catch (Throwable e) { throw new IllegalStateException(e); } } public CoreOAuthConsumerSupport(OAuthURLStreamHandlerFactory streamHandlerFactory) { this.streamHandlerFactory = streamHandlerFactory; } public void afterPropertiesSet() throws Exception { Assert.notNull(protectedResourceDetailsService, "A protected resource details service is required."); Assert.notNull(streamHandlerFactory, "A stream handler factory is required."); } // Inherited. public OAuthConsumerToken getUnauthorizedRequestToken(String resourceId, String callback) throws OAuthRequestFailedException { ProtectedResourceDetails details = getProtectedResourceDetailsService().loadProtectedResourceDetailsById(resourceId); return getUnauthorizedRequestToken(details, callback); } public OAuthConsumerToken getUnauthorizedRequestToken(ProtectedResourceDetails details, String callback) throws OAuthRequestFailedException { URL requestTokenURL; try { requestTokenURL = new URL(details.getRequestTokenURL()); } catch (MalformedURLException e) { throw new IllegalStateException("Malformed URL for obtaining a request token.", e); } String httpMethod = details.getRequestTokenHttpMethod(); Map<String, String> additionalParameters = new TreeMap<String, String>(); if (details.isUse10a()) { additionalParameters.put(OAuthConsumerParameter.oauth_callback.toString(), callback); } Map<String, String> specifiedParams = details.getAdditionalParameters(); if (specifiedParams != null) { additionalParameters.putAll(specifiedParams); } return getTokenFromProvider(details, requestTokenURL, httpMethod, null, additionalParameters); } // Inherited. public OAuthConsumerToken getAccessToken(OAuthConsumerToken requestToken, String verifier) throws OAuthRequestFailedException { ProtectedResourceDetails details = getProtectedResourceDetailsService().loadProtectedResourceDetailsById(requestToken.getResourceId()); return getAccessToken(details, requestToken, verifier); } public OAuthConsumerToken getAccessToken(ProtectedResourceDetails details, OAuthConsumerToken requestToken, String verifier) { URL accessTokenURL; try { accessTokenURL = new URL(details.getAccessTokenURL()); } catch (MalformedURLException e) { throw new IllegalStateException("Malformed URL for obtaining an access token.", e); } String httpMethod = details.getAccessTokenHttpMethod(); Map<String, String> additionalParameters = new TreeMap<String, String>(); if (details.isUse10a()) { if (verifier == null) { throw new UnverifiedRequestTokenException("Unverified request token: " + requestToken); } additionalParameters.put(OAuthConsumerParameter.oauth_verifier.toString(), verifier); } Map<String, String> specifiedParams = details.getAdditionalParameters(); if (specifiedParams != null) { additionalParameters.putAll(specifiedParams); } return getTokenFromProvider(details, accessTokenURL, httpMethod, requestToken, additionalParameters); } // Inherited. public InputStream readProtectedResource(URL url, OAuthConsumerToken accessToken, String httpMethod) throws OAuthRequestFailedException { if (accessToken == null) { throw new OAuthRequestFailedException("A valid access token must be supplied."); } ProtectedResourceDetails resourceDetails = getProtectedResourceDetailsService().loadProtectedResourceDetailsById(accessToken.getResourceId()); if ((!resourceDetails.isAcceptsAuthorizationHeader()) && !"POST".equalsIgnoreCase(httpMethod) && !"PUT".equalsIgnoreCase(httpMethod)) { throw new IllegalArgumentException("Protected resource " + resourceDetails.getId() + " cannot be accessed with HTTP method " + httpMethod + " because the OAuth provider doesn't accept the OAuth Authorization header."); } return readResource(resourceDetails, url, httpMethod, accessToken, resourceDetails.getAdditionalParameters(), null); } /** * Read a resource. * * @param details The details of the resource. * @param url The URL of the resource. * @param httpMethod The http method. * @param token The token. * @param additionalParameters Any additional request parameters. * @param additionalRequestHeaders Any additional request parameters. * @return The resource. */ protected InputStream readResource(ProtectedResourceDetails details, URL url, String httpMethod, OAuthConsumerToken token, Map<String, String> additionalParameters, Map<String, String> additionalRequestHeaders) { url = configureURLForProtectedAccess(url, token, details, httpMethod, additionalParameters); String realm = details.getAuthorizationHeaderRealm(); boolean sendOAuthParamsInRequestBody = !details.isAcceptsAuthorizationHeader() && (("POST".equalsIgnoreCase(httpMethod) || "PUT".equalsIgnoreCase(httpMethod))); HttpURLConnection connection = openConnection(url); try { connection.setRequestMethod(httpMethod); } catch (ProtocolException e) { throw new IllegalStateException(e); } Map<String, String> reqHeaders = details.getAdditionalRequestHeaders(); if (reqHeaders != null) { for (Map.Entry<String, String> requestHeader : reqHeaders.entrySet()) { connection.setRequestProperty(requestHeader.getKey(), requestHeader.getValue()); } } if (additionalRequestHeaders != null) { for (Map.Entry<String, String> requestHeader : additionalRequestHeaders.entrySet()) { connection.setRequestProperty(requestHeader.getKey(), requestHeader.getValue()); } } int responseCode; String responseMessage; try { connection.setDoOutput(sendOAuthParamsInRequestBody); connection.connect(); if (sendOAuthParamsInRequestBody) { String queryString = getOAuthQueryString(details, token, url, httpMethod, additionalParameters); OutputStream out = connection.getOutputStream(); out.write(queryString.getBytes("UTF-8")); out.flush(); out.close(); } responseCode = connection.getResponseCode(); responseMessage = connection.getResponseMessage(); if (responseMessage == null) { responseMessage = "Unknown Error"; } } catch (IOException e) { throw new OAuthRequestFailedException("OAuth connection failed.", e); } if (responseCode >= 200 && responseCode < 300) { try { return connection.getInputStream(); } catch (IOException e) { throw new OAuthRequestFailedException("Unable to get the input stream from a successful response.", e); } } else if (responseCode == 400) { throw new OAuthRequestFailedException("OAuth authentication failed: " + responseMessage); } else if (responseCode == 401) { String authHeaderValue = connection.getHeaderField("WWW-Authenticate"); if (authHeaderValue != null) { Map<String, String> headerEntries = StringSplitUtils.splitEachArrayElementAndCreateMap(StringSplitUtils.splitIgnoringQuotes(authHeaderValue, ','), "=", "\""); String requiredRealm = headerEntries.get("realm"); if ((requiredRealm != null) && (!requiredRealm.equals(realm))) { throw new InvalidOAuthRealmException(String.format("Invalid OAuth realm. Provider expects \"%s\", when the resource details specify \"%s\".", requiredRealm, realm), requiredRealm); } } throw new OAuthRequestFailedException("OAuth authentication failed: " + responseMessage); } else { throw new OAuthRequestFailedException(String.format("Invalid response code %s (%s).", responseCode, responseMessage)); } } /** * Create a configured URL. If the HTTP method to access the resource is "POST" or "PUT" and the "Authorization" * header isn't supported, then the OAuth parameters will be expected to be sent in the body of the request. Otherwise, * you can assume that the given URL is ready to be used without further work. * * @param url The base URL. * @param accessToken The access token. * @param httpMethod The HTTP method. * @param additionalParameters Any additional request parameters. * @return The configured URL. */ public URL configureURLForProtectedAccess(URL url, OAuthConsumerToken accessToken, String httpMethod, Map<String, String> additionalParameters) throws OAuthRequestFailedException { return configureURLForProtectedAccess(url, accessToken, getProtectedResourceDetailsService().loadProtectedResourceDetailsById(accessToken.getResourceId()), httpMethod, additionalParameters); } /** * Internal use of configuring the URL for protected access, the resource details already having been loaded. * * @param url The URL. * @param requestToken The request token. * @param details The details. * @param httpMethod The http method. * @param additionalParameters Any additional request parameters. * @return The configured URL. */ protected URL configureURLForProtectedAccess(URL url, OAuthConsumerToken requestToken, ProtectedResourceDetails details, String httpMethod, Map<String, String> additionalParameters) { String file; if (!"POST".equalsIgnoreCase(httpMethod) && !"PUT".equalsIgnoreCase(httpMethod) && !details.isAcceptsAuthorizationHeader()) { StringBuilder fileb = new StringBuilder(url.getPath()); String queryString = getOAuthQueryString(details, requestToken, url, httpMethod, additionalParameters); fileb.append('?').append(queryString); file = fileb.toString(); } else { file = url.getFile(); } try { if ("http".equalsIgnoreCase(url.getProtocol())) { URLStreamHandler streamHandler = getStreamHandlerFactory().getHttpStreamHandler(details, requestToken, this, httpMethod, additionalParameters); return new URL(url.getProtocol(), url.getHost(), url.getPort(), file, streamHandler); } else if ("https".equalsIgnoreCase(url.getProtocol())) { URLStreamHandler streamHandler = getStreamHandlerFactory().getHttpsStreamHandler(details, requestToken, this, httpMethod, additionalParameters); return new URL(url.getProtocol(), url.getHost(), url.getPort(), file, streamHandler); } else { throw new OAuthRequestFailedException("Unsupported OAuth protocol: " + url.getProtocol()); } } catch (MalformedURLException e) { throw new IllegalStateException(e); } } // Inherited. public String getAuthorizationHeader(ProtectedResourceDetails details, OAuthConsumerToken accessToken, URL url, String httpMethod, Map<String, String> additionalParameters) { if (!details.isAcceptsAuthorizationHeader()) { return null; } else { Map<String, Set<CharSequence>> oauthParams = loadOAuthParameters(details, url, accessToken, httpMethod, additionalParameters); String realm = details.getAuthorizationHeaderRealm(); StringBuilder builder = new StringBuilder("OAuth "); boolean writeComma = false; if (realm != null) { //realm is optional. builder.append("realm=\"").append(realm).append('"'); writeComma = true; } for (Map.Entry<String, Set<CharSequence>> paramValuesEntry : oauthParams.entrySet()) { Set<CharSequence> paramValues = paramValuesEntry.getValue(); CharSequence paramValue = findValidHeaderValue(paramValues); if (paramValue != null) { if (writeComma) { builder.append(", "); } builder.append(paramValuesEntry.getKey()).append("=\"").append(oauthEncode(paramValue.toString())).append('"'); writeComma = true; } } return builder.toString(); } } /** * Finds a valid header value that is valid for the OAuth header. * * @param paramValues The possible values for the oauth header. * @return The selected value, or null if none were found. */ protected String findValidHeaderValue(Set<CharSequence> paramValues) { String selectedValue = null; if (paramValues != null && !paramValues.isEmpty()) { CharSequence value = paramValues.iterator().next(); if (!(value instanceof QueryParameterValue)) { selectedValue = value.toString(); } } return selectedValue; } // Inherited. public String getOAuthQueryString(ProtectedResourceDetails details, OAuthConsumerToken accessToken, URL url, String httpMethod, Map<String, String> additionalParameters) { Map<String, Set<CharSequence>> oauthParams = loadOAuthParameters(details, url, accessToken, httpMethod, additionalParameters); StringBuilder queryString = new StringBuilder(); if (details.isAcceptsAuthorizationHeader()) { //if the resource accepts the auth header, remove any parameters that will go in the header (don't pass them redundantly in the query string). for (OAuthConsumerParameter oauthParam : OAuthConsumerParameter.values()) { oauthParams.remove(oauthParam.toString()); } if (additionalParameters != null) { for (String additionalParam : additionalParameters.keySet()) { oauthParams.remove(additionalParam); } } } Iterator<String> parametersIt = oauthParams.keySet().iterator(); while (parametersIt.hasNext()) { String parameter = parametersIt.next(); queryString.append(parameter); Set<CharSequence> values = oauthParams.get(parameter); if (values != null) { Iterator<CharSequence> valuesIt = values.iterator(); while (valuesIt.hasNext()) { CharSequence parameterValue = valuesIt.next(); if (parameterValue != null) { queryString.append('=').append(urlEncode(parameterValue.toString())); } if (valuesIt.hasNext()) { queryString.append('&').append(parameter); } } } if (parametersIt.hasNext()) { queryString.append('&'); } } return queryString.toString(); } /** * Get the consumer token with the given parameters and URL. The determination of whether the retrieved token * is an access token depends on whether a request token is provided. * * @param details The resource details. * @param tokenURL The token URL. * @param httpMethod The http method. * @param requestToken The request token, or null if none. * @param additionalParameters The additional request parameter. * @return The token. */ protected OAuthConsumerToken getTokenFromProvider(ProtectedResourceDetails details, URL tokenURL, String httpMethod, OAuthConsumerToken requestToken, Map<String, String> additionalParameters) { boolean isAccessToken = requestToken != null; if (!isAccessToken) { //create an empty token to make a request for a new unauthorized request token. requestToken = new OAuthConsumerToken(); } TreeMap<String, String> requestHeaders = new TreeMap<String, String>(); if ("POST".equalsIgnoreCase(httpMethod)) { requestHeaders.put("Content-Type", "application/x-www-form-urlencoded"); } InputStream inputStream = readResource(details, tokenURL, httpMethod, requestToken, additionalParameters, requestHeaders); String tokenInfo; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = inputStream.read(buffer); while (len >= 0) { out.write(buffer, 0, len); len = inputStream.read(buffer); } tokenInfo = new String(out.toByteArray(), "UTF-8"); } catch (IOException e) { throw new OAuthRequestFailedException("Unable to read the token.", e); } StringTokenizer tokenProperties = new StringTokenizer(tokenInfo, "&"); Map<String, String> tokenPropertyValues = new TreeMap<String, String>(); while (tokenProperties.hasMoreElements()) { try { String tokenProperty = (String) tokenProperties.nextElement(); int equalsIndex = tokenProperty.indexOf('='); if (equalsIndex > 0) { String propertyName = OAuthCodec.oauthDecode(tokenProperty.substring(0, equalsIndex)); String propertyValue = OAuthCodec.oauthDecode(tokenProperty.substring(equalsIndex + 1)); tokenPropertyValues.put(propertyName, propertyValue); } else { tokenProperty = OAuthCodec.oauthDecode(tokenProperty); tokenPropertyValues.put(tokenProperty, null); } } catch (DecoderException e) { throw new OAuthRequestFailedException("Unable to decode token parameters."); } } String tokenValue = tokenPropertyValues.remove(OAuthProviderParameter.oauth_token.toString()); if (tokenValue == null) { throw new OAuthRequestFailedException("OAuth provider failed to return a token."); } String tokenSecret = tokenPropertyValues.remove(OAuthProviderParameter.oauth_token_secret.toString()); if (tokenSecret == null) { throw new OAuthRequestFailedException("OAuth provider failed to return a token secret."); } OAuthConsumerToken consumerToken = new OAuthConsumerToken(); consumerToken.setValue(tokenValue); consumerToken.setSecret(tokenSecret); consumerToken.setResourceId(details.getId()); consumerToken.setAccessToken(isAccessToken); if (!tokenPropertyValues.isEmpty()) { consumerToken.setAdditionalParameters(tokenPropertyValues); } return consumerToken; } /** * Loads the OAuth parameters for the given resource at the given URL and the given token. These parameters include * any query parameters on the URL since they are included in the signature. The oauth parameters are NOT encoded. * * @param details The resource details. * @param requestURL The request URL. * @param requestToken The request token. * @param httpMethod The http method. * @param additionalParameters Additional oauth parameters (outside of the core oauth spec). * @return The parameters. */ protected Map<String, Set<CharSequence>> loadOAuthParameters(ProtectedResourceDetails details, URL requestURL, OAuthConsumerToken requestToken, String httpMethod, Map<String, String> additionalParameters) { Map<String, Set<CharSequence>> oauthParams = new TreeMap<String, Set<CharSequence>>(); if (additionalParameters != null) { for (Map.Entry<String, String> additionalParam : additionalParameters.entrySet()) { Set<CharSequence> values = oauthParams.get(additionalParam.getKey()); if (values == null) { values = new HashSet<CharSequence>(); oauthParams.put(additionalParam.getKey(), values); } if (additionalParam.getValue() != null) { values.add(additionalParam.getValue()); } } } String query = requestURL.getQuery(); if (query != null) { StringTokenizer queryTokenizer = new StringTokenizer(query, "&"); while (queryTokenizer.hasMoreElements()) { String token = (String) queryTokenizer.nextElement(); CharSequence value = null; int equalsIndex = token.indexOf('='); if (equalsIndex < 0) { token = urlDecode(token); } else { value = new QueryParameterValue(urlDecode(token.substring(equalsIndex + 1))); token = urlDecode(token.substring(0, equalsIndex)); } Set<CharSequence> values = oauthParams.get(token); if (values == null) { values = new HashSet<CharSequence>(); oauthParams.put(token, values); } if (value != null) { values.add(value); } } } String tokenSecret = requestToken == null ? null : requestToken.getSecret(); String nonce = getNonceFactory().generateNonce(); oauthParams.put(OAuthConsumerParameter.oauth_consumer_key.toString(), Collections.singleton((CharSequence) details.getConsumerKey())); if ((requestToken != null) && (requestToken.getValue() != null)) { oauthParams.put(OAuthConsumerParameter.oauth_token.toString(), Collections.singleton((CharSequence) requestToken.getValue())); } oauthParams.put(OAuthConsumerParameter.oauth_nonce.toString(), Collections.singleton((CharSequence) nonce)); oauthParams.put(OAuthConsumerParameter.oauth_signature_method.toString(), Collections.singleton((CharSequence) details.getSignatureMethod())); oauthParams.put(OAuthConsumerParameter.oauth_timestamp.toString(), Collections.singleton((CharSequence) String.valueOf(System.currentTimeMillis() / 1000))); oauthParams.put(OAuthConsumerParameter.oauth_version.toString(), Collections.singleton((CharSequence) "1.0")); String signatureBaseString = getSignatureBaseString(oauthParams, requestURL, httpMethod); OAuthSignatureMethod signatureMethod; try { signatureMethod = getSignatureFactory().getSignatureMethod(details.getSignatureMethod(), details.getSharedSecret(), tokenSecret); } catch (UnsupportedSignatureMethodException e) { throw new OAuthRequestFailedException(e.getMessage(), e); } String signature = signatureMethod.sign(signatureBaseString); oauthParams.put(OAuthConsumerParameter.oauth_signature.toString(), Collections.singleton((CharSequence) signature)); return oauthParams; } /** * URL-encode a value. * * @param value The value to encode. * @return The URL-encoded value. */ protected String urlEncode(String value) { try { return URLEncoder.encode(value, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * URL-decode a token. * * @param token The token to URL-decode. * @return The decoded token. */ protected String urlDecode(String token) { try { return URLDecoder.decode(token, "utf-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * Open a connection to the given URL. * * @param requestTokenURL The request token URL. * @return The HTTP URL connection. */ protected HttpURLConnection openConnection(URL requestTokenURL) { try { HttpURLConnection connection = (HttpURLConnection) requestTokenURL.openConnection(selectProxy(requestTokenURL)); connection.setConnectTimeout(getConnectionTimeout()); connection.setReadTimeout(getReadTimeout()); return connection; } catch (IOException e) { throw new OAuthRequestFailedException("Failed to open an OAuth connection.", e); } } /** * Selects a proxy for the given URL. * * @param requestTokenURL The URL * @return The proxy. */ protected Proxy selectProxy(URL requestTokenURL) { try { List<Proxy> selectedProxies = getProxySelector().select(requestTokenURL.toURI()); return selectedProxies.isEmpty() ? Proxy.NO_PROXY : selectedProxies.get(0); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } /** * Get the signature base string for the specified parameters. It is presumed the parameters are NOT OAuth-encoded. * * @param oauthParams The parameters (NOT oauth-encoded). * @param requestURL The request URL. * @param httpMethod The http method. * @return The signature base string. */ protected String getSignatureBaseString(Map<String, Set<CharSequence>> oauthParams, URL requestURL, String httpMethod) { TreeMap<String, TreeSet<String>> sortedParameters = new TreeMap<String, TreeSet<String>>(); for (Map.Entry<String, Set<CharSequence>> param : oauthParams.entrySet()) { //first encode all parameter names and values (spec section 9.1) String key = oauthEncode(param.getKey()); //add the encoded parameters sorted according to the spec. TreeSet<String> sortedValues = sortedParameters.get(key); if (sortedValues == null) { sortedValues = new TreeSet<String>(); sortedParameters.put(key, sortedValues); } for (CharSequence value : param.getValue()) { sortedValues.add(oauthEncode(value.toString())); } } //now concatenate them into a single query string according to the spec. StringBuilder queryString = new StringBuilder(); Iterator<Map.Entry<String, TreeSet<String>>> sortedIt = sortedParameters.entrySet().iterator(); while (sortedIt.hasNext()) { Map.Entry<String, TreeSet<String>> sortedParameter = sortedIt.next(); for (Iterator<String> sortedParametersIterator = sortedParameter.getValue().iterator(); sortedParametersIterator.hasNext();) { String parameterValue = sortedParametersIterator.next(); if (parameterValue == null) { parameterValue = ""; } queryString.append(sortedParameter.getKey()).append('=').append(parameterValue); if (sortedIt.hasNext() || sortedParametersIterator.hasNext()) { queryString.append('&'); } } } StringBuilder url = new StringBuilder(requestURL.getProtocol().toLowerCase()).append("://").append(requestURL.getHost().toLowerCase()); if ((requestURL.getPort() >= 0) && (requestURL.getPort() != requestURL.getDefaultPort())) { url.append(":").append(requestURL.getPort()); } url.append(requestURL.getPath()); return new StringBuilder(httpMethod.toUpperCase()).append('&').append(oauthEncode(url.toString())).append('&').append(oauthEncode(queryString.toString())).toString(); } /** * The protected resource details service. * * @return The protected resource details service. */ public ProtectedResourceDetailsService getProtectedResourceDetailsService() { return protectedResourceDetailsService; } /** * The protected resource details service. * * @param protectedResourceDetailsService * The protected resource details service. */ @Autowired public void setProtectedResourceDetailsService(ProtectedResourceDetailsService protectedResourceDetailsService) { this.protectedResourceDetailsService = protectedResourceDetailsService; } /** * The URL stream handler factory for connections to an OAuth resource. * * @return The URL stream handler factory for connections to an OAuth resource. */ public OAuthURLStreamHandlerFactory getStreamHandlerFactory() { return streamHandlerFactory; } /** * The URL stream handler factory for connections to an OAuth resource. * * @param streamHandlerFactory The URL stream handler factory for connections to an OAuth resource. */ @Autowired (required = false) public void setStreamHandlerFactory(OAuthURLStreamHandlerFactory streamHandlerFactory) { this.streamHandlerFactory = streamHandlerFactory; } /** * The nonce factory. * * @return The nonce factory. */ public NonceFactory getNonceFactory() { return nonceFactory; } /** * The nonce factory. * * @param nonceFactory The nonce factory. */ @Autowired (required = false) public void setNonceFactory(NonceFactory nonceFactory) { this.nonceFactory = nonceFactory; } /** * The signature factory to use. * * @return The signature factory to use. */ public OAuthSignatureMethodFactory getSignatureFactory() { return signatureFactory; } /** * The signature factory to use. * * @param signatureFactory The signature factory to use. */ @Autowired (required = false) public void setSignatureFactory(OAuthSignatureMethodFactory signatureFactory) { this.signatureFactory = signatureFactory; } /** * The proxy selector to use. * * @return The proxy selector to use. */ public ProxySelector getProxySelector() { return proxySelector; } /** * The proxy selector to use. * * @param proxySelector The proxy selector to use. */ @Autowired (required = false) public void setProxySelector(ProxySelector proxySelector) { this.proxySelector = proxySelector; } /** * The connection timeout (default 60 seconds). * * @return The connection timeout. */ public int getConnectionTimeout() { return connectionTimeout; } /** * The connection timeout. * * @param connectionTimeout The connection timeout. */ public void setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; } /** * The read timeout (default 60 seconds). * * @return The read timeout. */ public int getReadTimeout() { return readTimeout; } /** * The read timeout. * * @param readTimeout The read timeout. */ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } /** * Marker class for an oauth parameter value that is a query parameter and should therefore not be included in the authorization header. */ public static class QueryParameterValue implements CharSequence { private final String value; public QueryParameterValue(String value) { this.value = value; } public int length() { return this.value.length(); } public char charAt(int index) { return this.value.charAt(index); } public CharSequence subSequence(int start, int end) { return this.value.subSequence(start, end); } @Override public String toString() { return this.value; } @Override public int hashCode() { return this.value.hashCode(); } @Override public boolean equals(Object obj) { return this.value.equals(obj); } } }