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

com.paypal.base.rest.OAuthTokenCredential Maven / Gradle / Ivy

There is a newer version: 1.14.0
Show newest version
package com.paypal.base.rest;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.paypal.base.*;
import com.paypal.base.codec.binary.Base64;
import com.paypal.base.exception.ClientActionRequiredException;
import com.paypal.base.exception.HttpErrorException;
import com.paypal.base.sdk.info.SDKVersionImpl;
import com.paypal.base.util.UserAgentHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * OAuthTokenCredential is used for generation of OAuth Token used by PayPal
 * REST API service. ClientID and ClientSecret are required by the class to
 * generate OAuth Token, the resulting token is of the form "Bearer xxxxxx". The
 * class has two constructors, one of it taking an additional configuration map
 * used for dynamic configuration. When using the constructor with out
 * configuration map the endpoint is fetched from the configuration that is used
 * during initialization. See {@link PayPalResource} for configuring the system.
 * When using a configuration map the class expects an entry by the name
 * "oauth.EndPoint" or "service.EndPoint" to retrieve the value of the endpoint
 * for the OAuth Service. If either are not present the configuration should
 * have a entry by the name "mode" with values sandbox or live wherein the
 * corresponding endpoints are default to PayPal endpoints.
 */
public final class OAuthTokenCredential {

	private static final Logger log = LoggerFactory.getLogger(OAuthTokenCredential.class);

	private static Map ACCESS_TOKENS = new ConcurrentHashMap();

	/**
	 * OAuth URI path parameter
	 */
	private static String OAUTH_TOKEN_PATH = "/v1/oauth2/token";

	/**
	 * Client ID for OAuth
	 */
	private String clientID;

	/**
	 * Client Secret for OAuth
	 */
	private String clientSecret;

	/**
	 * Headers
	 */
	private Map headers = new HashMap();

	private String refreshToken;

	/**
	 * @deprecated This field is only used when OAuthTokenCredential is initialized with access token only.
	 * If the access token is directly passed in as a part of constructor, we will always pass this back.
	 */
	@Deprecated
	private final AccessToken __accessToken;

	/**
	 * Map used for dynamic configuration
	 */
	private Map configurationMap;

	/**
	 * {@link SDKVersion} instance
	 */
	private SDKVersion sdkVersion;

	/**
	 * Sets the URI path for the OAuth Token service. If not set it defaults to
	 * "/v1/oauth2/token"
	 * 
	 * @param oauthTokenPath
	 *            the URI part to set
	 */
	public static void setOAUTH_TOKEN_PATH(String oauthTokenPath) {
		OAUTH_TOKEN_PATH = oauthTokenPath;
	}
	
	/**
	 * Constructor that takes in Access Token. Only used internally. Please do not use for external integrations.
	 * 
	 * @param accessToken
	 */
	@Deprecated
	OAuthTokenCredential(String accessToken) {
		__accessToken = new AccessToken(accessToken, 1);
	}

	/**
	 * Pass clientId and secret to OAuthTokenCredential. 
	 * 
	 * @param clientID
	 *            Client ID for the OAuth
	 * @param clientSecret
	 *            Client Secret for OAuth
	 */
	public OAuthTokenCredential(String clientID, String clientSecret) {
		super();
		this.clientID = clientID;
		this.clientSecret = clientSecret;
		this.configurationMap = SDKUtil.combineDefaultMap(ConfigManager.getInstance().getConfigurationMap());
		this.sdkVersion = new SDKVersionImpl();
		this.__accessToken = null;
	}

	/**
	 * Configuration Constructor for dynamic configuration
	 * 
	 * @param clientID
	 *            Client ID for the OAuth
	 * @param clientSecret
	 *            Client Secret for OAuth
	 * @param configurationMap
	 *            Dynamic configuration map which should have an entry for
	 *            'oauth.EndPoint' or 'service.EndPoint'. If either are not
	 *            present then there should be entry for 'mode' with values
	 *            sandbox/live, wherein PayPals endpoints are used.
	 */
	public OAuthTokenCredential(String clientID, String clientSecret, Map configurationMap) {
		super();
		this.clientID = clientID;
		this.clientSecret = clientSecret;
		this.configurationMap = SDKUtil.combineDefaultMap(configurationMap);
		this.sdkVersion = new SDKVersionImpl();
		this.__accessToken = null;
	}

	/**
	 * Sets refresh token to be used for third party OAuth operations. This is commonly used for 
	 * third party invoicing and future payments. 
	 * This method is for internal use only. Please use {@link APIContext#setRefreshToken(String)} for your integration needs.
	 * 
	 * @param refreshToken
	 * @return {@link OAuthTokenCredential}
	 */
	synchronized OAuthTokenCredential setRefreshToken(String refreshToken) {
		if (!this.hasCredentials()) {
			throw new IllegalArgumentException("ClientID and Secret are required. Please use OAuthTokenCredential(String clientID, String clientSecret)");
		}
		this.refreshToken = refreshToken;
		return this;
	}

	/**
	 * Checks if clientID and secret are set.
	 * 
	 * @return {@link Boolean}
	 */
	public boolean hasCredentials() {
		return (this.clientID != null) && (this.clientSecret != null);
	}

	/**
	 * Sets Headers for every calls.
	 * 
	 * @param headers
	 * @return {@link OAuthTokenCredential}
	 */
	public OAuthTokenCredential setHeaders(Map headers) {
		this.headers = headers;
		return this;
	}
	
	/**
	 * Adds headers.
	 * 
	 * @param headers
	 * @return {@link OAuthTokenCredential}
	 */
	public OAuthTokenCredential addHeaders(Map headers) {
		this.headers.putAll(headers);
		return this;
	}
	
	/**
	 * Adds header to existing list of headers.
	 * 
	 * @param key
	 * @param value
	 * @return {@link OAuthTokenCredential}
	 */
	public OAuthTokenCredential addHeader(String key, String value) {
		this.headers.put(key, value);
		return this;
	}
	
	/**
	 * Returns the list of headers
	 * 
	 * @return {@link Map} of headers
	 */
	public Map getHeaders() {
		return this.headers;
	}

	/**
	 * Returns the header value
	 *
	 * @return {@link String} value of header
	 */
	public String getHeader(String key) {
		return this.headers.get(key);
	}

	/**
	 * Computes Access Token by placing a call to OAuth server using ClientID
	 * and ClientSecret. The token is appended to the token type (Bearer).
	 *
	 * @return the accessToken
	 * @throws PayPalRESTException
	 */
	public String getAccessToken() throws PayPalRESTException {
		if (__accessToken != null) {
			return __accessToken.getAccessToken();
		}
		synchronized (ACCESS_TOKENS) {
			if (isRegenerationRequired()) {
				generateAccessToken();
			}
			return ACCESS_TOKENS.get(getCacheTokenKey()).getAccessToken();
		}
	}

	/**
	 * Checks if the access token is expired or null.
	 *
	 * @return true if expired or null, and can be regenerated.
	 * false otherwise
	 */
	private boolean isRegenerationRequired() {
		AccessToken token = ACCESS_TOKENS.get(getCacheTokenKey());
		if (token == null) {
			return hasCredentials();
		} else {
			return (token.getAccessToken() == null || (hasCredentials() && token.expiresIn() <= 0));
		}
	}

	/**
	 * Computes Access Token by doing a Base64 encoding on the ClientID and
	 * ClientSecret. The token is appended to the String "Basic ".
	 * 
	 * @return the accessToken
	 * @throws PayPalRESTException
	 */
	public String getAuthorizationHeader() throws PayPalRESTException {
		String base64EncodedString = generateBase64String(clientID + ":" + clientSecret);
		return "Basic " + base64EncodedString;
	}
	
	/**
	 * Returns clientID
	 * 
	 * @return {@link String} containing clientID
	 */
	public String getClientID() {
		return this.clientID;
	}

	/**
	 * Returns clientSecret
	 * 
	 * @return {@link String} containing clientSecret
	 */
	public String getClientSecret() {
		return this.clientSecret;
	}

	/**
	 * Specifies how long this token can be used for placing API calls. The
	 * remaining lifetime is given in seconds.
	 *
	 * @return remaining lifetime of this access token in seconds
	 */
	public long expiresIn() {
		AccessToken token = ACCESS_TOKENS.get(getCacheTokenKey());
		return token != null ? token.getExpires() : -1;
	}

	/**
	 * Adds configuration to list of configurations.
	 * 
	 * @param key
	 * @param value
	 * @return {@link OAuthTokenCredential}
	 */
	public OAuthTokenCredential addConfiguration(String key, String value) {
		if (this.configurationMap == null) {
			this.configurationMap = new HashMap();
		}
		this.configurationMap.put(key, value);
		return this;
	}
	
	/**
	 * Adds configurations to list of configurations.
	 * @param configurations
	 * @return {@link OAuthTokenCredential}
	 */
	public OAuthTokenCredential addConfigurations(Map configurations) {
		if (this.configurationMap == null) {
			this.configurationMap = new HashMap();
		}
		this.configurationMap.putAll(configurations);
		return this;
	}
	
	/**
	 * Replaces existing configurations with provided map of configurations.
	 * 
	 * @param configurations
	 * @return {@link OAuthTokenCredential}
	 */
	public OAuthTokenCredential setConfigurations(Map configurations) {
		this.configurationMap = configurations;
		return this;
	}
	
	/**
	 * Returns list of configurations.
	 * 
	 * @return {@link Map} of configurations
	 */
	public Map getConfigurations() {
		return this.configurationMap;
	}
	
	/**
	 * Returns specific configuration.
	 * 
	 * @param key
	 * @return {@link String} value of configuration
	 */
	public String getConfiguration(String key) {
		if (this.configurationMap != null) {
			return this.configurationMap.get(key);
		}
		return null;
	}

	private synchronized void generateAccessToken() throws PayPalRESTException {
		HttpConnection connection;
		HttpConfiguration httpConfiguration;
		String base64ClientID = generateBase64String(clientID + ":" + clientSecret);
		try {
			connection = ConnectionManager.getInstance().getConnection();
			httpConfiguration = getOAuthHttpConfiguration();
			connection.createAndconfigureHttpConnection(httpConfiguration);
			this.headers.put(Constants.AUTHORIZATION_HEADER, "Basic " + base64ClientID);

			// Accept only json output
			this.headers.put(Constants.HTTP_ACCEPT_HEADER, Constants.HTTP_CONTENT_TYPE_JSON);
			this.headers.put(Constants.HTTP_CONTENT_TYPE_HEADER, Constants.HTTP_CONFIG_DEFAULT_CONTENT_TYPE);
			UserAgentHeader userAgentHeader = new UserAgentHeader(sdkVersion != null ? sdkVersion.getSDKId() : null,
					sdkVersion != null ? sdkVersion.getSDKVersion() : null);
			this.headers.putAll(userAgentHeader.getHeader());
			String postRequest = getRequestPayload();

			// log request
			String mode = configurationMap.get(Constants.MODE);
			if (Constants.LIVE.equalsIgnoreCase(mode) && log.isDebugEnabled()) {
				log.warn("Log level cannot be set to DEBUG in " + Constants.LIVE
						+ " mode. Skipping request/response logging...");
			}
			if (!Constants.LIVE.equalsIgnoreCase(mode)) {
				log.debug("request header: " + this.headers.toString());
				log.debug("request body: " + postRequest);
			}

			// send request and get & log response
			String jsonResponse = connection.execute("", postRequest, this.headers);
			if (!Constants.LIVE.equalsIgnoreCase(mode)) {
				log.debug("response header: " + connection.getResponseHeaderMap().toString());
				log.debug("response: " + jsonResponse);
			}

			// parse response as JSON object
			JsonParser parser = new JsonParser();
			JsonElement jsonElement = parser.parse(jsonResponse);
			String accessToken = jsonElement.getAsJsonObject().get("token_type").getAsString() + " "
					+ jsonElement.getAsJsonObject().get("access_token").getAsString();
			// Save expiry date
			long tokenLifeTime = jsonElement.getAsJsonObject().get("expires_in").getAsLong();
			long expires = new Date().getTime() / 1000 + tokenLifeTime;
			ACCESS_TOKENS.put(getCacheTokenKey(), new AccessToken(accessToken, expires));
		} catch (ClientActionRequiredException e) {
			throw PayPalRESTException.createFromHttpErrorException(e);
		} catch (HttpErrorException e) {
			throw PayPalRESTException.createFromHttpErrorException(e);
		} catch (Exception e) {
			throw new PayPalRESTException(e.getMessage(), e);
		} finally {
			// Replace the headers back to JSON for any future use.
			this.headers.put(Constants.HTTP_CONTENT_TYPE_HEADER, Constants.HTTP_CONTENT_TYPE_JSON);
		}
	}

	private String getCacheTokenKey() {
		return clientID + ":" + clientSecret + ":" + refreshToken;
	}

	/*
	 * Generate a Base64 encoded String from clientID & clientSecret
	 */
	private String generateBase64String(String clientCredentials) throws PayPalRESTException {
		String base64ClientID = null;
		byte[] encoded = null;
		try {
			encoded = Base64.encodeBase64(clientCredentials.getBytes("UTF-8"));
			base64ClientID = new String(encoded, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new PayPalRESTException(e.getMessage(), e);
		}
		return base64ClientID;
	}

	/**
	 * Returns the request payload for OAuth Service. Override this method to
	 * alter the payload
	 * 
	 * @return Payload as String
	 */
	protected String getRequestPayload() {
		if (this.refreshToken != null) {
			return String.format("grant_type=refresh_token&refresh_token=%s", this.refreshToken);
		} else {
			return "grant_type=client_credentials";
		}
	}

	/*
	 * Get HttpConfiguration object for OAuth server
	 */
	private HttpConfiguration getOAuthHttpConfiguration() throws MalformedURLException {
		HttpConfiguration httpConfiguration = new HttpConfiguration();
		httpConfiguration
				.setHttpMethod(Constants.HTTP_CONFIG_DEFAULT_HTTP_METHOD);
		
		/*
		 * Check for property 'mode' property in the configuration, if not
		 * found, check for 'oauth.EndPoint' property in the configuration and default
		 * endpoint to PayPal sandbox or live endpoints. Throw exception if the
		 * above rules fail
		 */
		String mode = this.configurationMap.get(Constants.MODE);
		// Default to Endpoint param.
		String endPointUrl = this.configurationMap.get(Constants.OAUTH_ENDPOINT);
		if (Constants.SANDBOX.equalsIgnoreCase(mode)) {
			endPointUrl = Constants.REST_SANDBOX_ENDPOINT;
		} else if (Constants.LIVE.equalsIgnoreCase(mode)) {
			endPointUrl = Constants.REST_LIVE_ENDPOINT;
		} else if (endPointUrl == null || endPointUrl.length() <= 0) {
			// Default to Normal endpoint
			endPointUrl = this.configurationMap.get(Constants.ENDPOINT);
		} 
		// If none of the option works, throw exception.
		if (endPointUrl == null || endPointUrl.length() <= 0) {
			throw new MalformedURLException(
					"service.EndPoint not set (OR) mode not configured to sandbox/live ");
		}
		
		if (Boolean
				.parseBoolean(configurationMap.get(Constants.USE_HTTP_PROXY))) {
			httpConfiguration.setProxySet(true);
			httpConfiguration.setProxyHost(configurationMap
					.get(Constants.HTTP_PROXY_HOST));
			httpConfiguration.setProxyPort(Integer.parseInt(configurationMap
					.get(Constants.HTTP_PROXY_PORT)));

			String proxyUserName = configurationMap
					.get(Constants.HTTP_PROXY_USERNAME);
			String proxyPassword = configurationMap
					.get(Constants.HTTP_PROXY_PASSWORD);

			if (proxyUserName != null && proxyPassword != null) {
				httpConfiguration.setProxyUserName(proxyUserName);
				httpConfiguration.setProxyPassword(proxyPassword);
			}
		}
		endPointUrl = (endPointUrl.endsWith("/")) ? endPointUrl.substring(0,
				endPointUrl.length() - 1) : endPointUrl;
		endPointUrl += OAUTH_TOKEN_PATH;
		httpConfiguration.setEndPointUrl(endPointUrl);
		httpConfiguration
				.setGoogleAppEngine(Boolean.parseBoolean(configurationMap
						.get(Constants.GOOGLE_APP_ENGINE)));
		return httpConfiguration;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy