All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.social.oauth1.OAuth1Template Maven / Gradle / Ivy

Go to download

Foundational module containing the ServiceProvider Connect Framework and Service API invocation support.

The newest version!
/*
 * Copyright 2015 the original author or authors.
 *
 * 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.social.oauth1;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.social.support.ClientHttpRequestFactorySelector;
import org.springframework.social.support.LoggingErrorHandler;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * OAuth10Operations implementation that uses REST-template to make the OAuth calls.
 * @author Keith Donald
 */
public class OAuth1Template implements OAuth1Operations {

	private final String consumerKey;

	private final String consumerSecret;

	private final URI requestTokenUrl;

	private final String authenticateUrl;
	
	private final String authorizeUrl;

	private final URI accessTokenUrl;

	private final RestTemplate restTemplate;

	private final OAuth1Version version;
	
	private final SigningSupport signingUtils;

	public OAuth1Template(String consumerKey, String consumerSecret, String requestTokenUrl, String authorizeUrl, String accessTokenUrl) {
		this(consumerKey, consumerSecret, requestTokenUrl, authorizeUrl, accessTokenUrl, OAuth1Version.CORE_10_REVISION_A);
	}

	public OAuth1Template(String consumerKey, String consumerSecret, String requestTokenUrl, String authorizeUrl, String accessTokenUrl, OAuth1Version version) {
		this(consumerKey, consumerSecret, requestTokenUrl, authorizeUrl, null, accessTokenUrl, version);
	}

	public OAuth1Template(String consumerKey, String consumerSecret, String requestTokenUrl, String authorizeUrl, String authenticateUrl, String accessTokenUrl) {
		this(consumerKey, consumerSecret, requestTokenUrl, authorizeUrl, authenticateUrl, accessTokenUrl, OAuth1Version.CORE_10_REVISION_A);
	}
	
	public OAuth1Template(String consumerKey, String consumerSecret, String requestTokenUrl, String authorizeUrl, String authenticateUrl, String accessTokenUrl, OAuth1Version version) {
		Assert.notNull(consumerKey, "The consumerKey property cannot be null");
		Assert.notNull(consumerSecret, "The consumerSecret property cannot be null");
		Assert.notNull(requestTokenUrl, "The requestTokenUrl property cannot be null");
		Assert.notNull(authorizeUrl, "The authorizeUrl property cannot be null");
		Assert.notNull(accessTokenUrl, "The accessTokenUrl property cannot be null");
		this.consumerKey = consumerKey;
		this.consumerSecret = consumerSecret;
		this.requestTokenUrl = encodeTokenUri(requestTokenUrl);
		this.authorizeUrl = authorizeUrl;
		this.authenticateUrl = authenticateUrl;
		this.accessTokenUrl = encodeTokenUri(accessTokenUrl);
		this.version = version;
		this.restTemplate = createRestTemplate();
		this.signingUtils = new SigningSupport();
	}

	/**
	 * Set the request factory on the underlying RestTemplate.
	 * This can be used to plug in a different HttpClient to do things like configure custom SSL settings.
	 * @param requestFactory the request factory on the underlying RestTemplate.
	 */
	public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
		Assert.notNull(requestFactory, "The requestFactory property cannot be null");
		restTemplate.setRequestFactory(requestFactory);
	}
	
	// implementing OAuth1Operations
	
	public OAuth1Version getVersion() {
		return version;
	}
	
	public OAuthToken fetchRequestToken(String callbackUrl, MultiValueMap additionalParameters) {
		Map oauthParameters = new HashMap(1, 1);
		if (version == OAuth1Version.CORE_10_REVISION_A) {
			oauthParameters.put("oauth_callback", callbackUrl);
		}
		return exchangeForToken(requestTokenUrl, oauthParameters, additionalParameters, null);
	}

	public String buildAuthorizeUrl(String requestToken, OAuth1Parameters parameters) {
		return buildAuthUrl(authorizeUrl, requestToken, parameters);
	}
	
	public String buildAuthenticateUrl(String requestToken, OAuth1Parameters parameters) {
		return authenticateUrl != null ? buildAuthUrl(authenticateUrl, requestToken, parameters) : buildAuthorizeUrl(requestToken, parameters);
	}

	public OAuthToken exchangeForAccessToken(AuthorizedRequestToken requestToken, MultiValueMap additionalParameters) {
		Map tokenParameters = new HashMap(2, 1);
		tokenParameters.put("oauth_token", requestToken.getValue());
		if (version == OAuth1Version.CORE_10_REVISION_A) {
			tokenParameters.put("oauth_verifier", requestToken.getVerifier());
		}
		return exchangeForToken(accessTokenUrl, tokenParameters, additionalParameters, requestToken.getSecret());
	}

	// subclassing hooks

	/**
	 * Exposes the consumer key to be read by subclasses.
	 * This may be useful when overriding {@link #addCustomAuthorizationParameters(MultiValueMap)} and the consumer key is required in the authorization request.
	 * @return the consumer key to be read by subclasses.
	 */
	protected String getConsumerKey() {
		return consumerKey;
	}
	
	/**
	 * Creates an {@link OAuthToken} given the response from the request token or access token exchange with the provider.
	 * May be overridden to create a custom {@link OAuthToken}.
	 * @param tokenValue the token value received from the provider.
	 * @param tokenSecret the token secret received from the provider.
	 * @param response all parameters from the response received in the request/access token exchange.
	 * @return an {@link OAuthToken}
	 */
	protected OAuthToken createOAuthToken(String tokenValue, String tokenSecret, MultiValueMap response) {
		return new OAuthToken(tokenValue, tokenSecret);
	}

	/**
	 * Subclassing hook to add custom authorization parameters to the authorization URL.
	 * Default implementation adds no parameters.
	 * @param parameters custom parameters for authorization
	 */
	protected void addCustomAuthorizationParameters(MultiValueMap parameters) {
	}
	
	// internal helpers

	private RestTemplate createRestTemplate() {
		RestTemplate restTemplate = new RestTemplate(ClientHttpRequestFactorySelector.getRequestFactory());
		List> converters = new ArrayList>(1);
		converters.add(new FormHttpMessageConverter() {
			public boolean canRead(Class clazz, MediaType mediaType) {
				// always read MultiValueMaps as x-www-url-formencoded even if contentType not set properly by provider				
				return MultiValueMap.class.isAssignableFrom(clazz);
			}
		});
		restTemplate.setMessageConverters(converters);
		restTemplate.setErrorHandler(new LoggingErrorHandler());
		return restTemplate;
	}
	
	private URI encodeTokenUri(String url) {
		return UriComponentsBuilder.fromUriString(url).build().toUri();
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private OAuthToken exchangeForToken(URI tokenUrl, Map tokenParameters, MultiValueMap additionalParameters, String tokenSecret) {
		HttpHeaders headers = new HttpHeaders();
		headers.add("Authorization", buildAuthorizationHeaderValue(tokenUrl, tokenParameters, additionalParameters, tokenSecret));
		ResponseEntity response = restTemplate.exchange(tokenUrl, HttpMethod.POST, new HttpEntity>(additionalParameters, headers), MultiValueMap.class);
		MultiValueMap body = response.getBody();
		return createOAuthToken(body.getFirst("oauth_token"), body.getFirst("oauth_token_secret"), body);
	}

	private String buildAuthorizationHeaderValue(URI tokenUrl, Map tokenParameters, MultiValueMap additionalParameters, String tokenSecret) {
		Map oauthParameters = signingUtils.commonOAuthParameters(consumerKey);
		oauthParameters.putAll(tokenParameters);
		if (additionalParameters == null) {
			additionalParameters = EmptyMultiValueMap.instance();
		}
		return signingUtils.buildAuthorizationHeaderValue(HttpMethod.POST, tokenUrl, oauthParameters, additionalParameters, consumerSecret, tokenSecret);
	}

	private String buildAuthUrl(String baseAuthUrl, String requestToken, OAuth1Parameters parameters) {
		StringBuilder authUrl = new StringBuilder(baseAuthUrl).append('?').append("oauth_token").append('=').append(formEncode(requestToken));
		addCustomAuthorizationParameters(parameters);
		if (parameters != null) {
			for (Iterator>> additionalParams = parameters.entrySet().iterator(); additionalParams.hasNext();) {
				Entry> param = additionalParams.next();
				String name = formEncode(param.getKey());
				for (Iterator values = param.getValue().iterator(); values.hasNext();) {
					authUrl.append('&').append(name).append('=').append(formEncode(values.next()));
				}
			}
		}		
		return authUrl.toString();
	}
	
	private String formEncode(String data) {
		try {
			return URLEncoder.encode(data, "UTF-8");
		}
		catch (UnsupportedEncodingException ex) {
			// should not happen, UTF-8 is always supported
			throw new IllegalStateException(ex);
		}
	}
	
	// testing hooks
	RestTemplate getRestTemplate() {
		return restTemplate;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy