com.covisint.platform.oauth.client.token.sdk.TokenSupplier Maven / Gradle / Ivy
/*
* Copyright 2015 Covisint
*
* 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.covisint.platform.oauth.client.token.sdk;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.covisint.core.http.service.client.BaseResourceClientFactory;
import com.covisint.core.http.service.client.ClientInitializationContext;
import com.covisint.core.support.constraint.Nonnull;
import com.covisint.core.support.constraint.NotEmpty;
import com.covisint.platform.oauth.client.token.sdk.TokenSDK.TokenClient;
import com.covisint.platform.oauth.core.token.Token;
import com.google.common.base.Supplier;
/**
* Supplier of access tokens retrieved from the {@link TokenClient}. This supplier is configured to use an
* interchangeable {@link AuthConfigurationProvider} through the use of
* {@link SwappableCredentialSupport#swapCredentials(String, String, String)}. This adds support for pluggable
* credentials during runtime, which may be useful during cases where app-level access tokens need to be replaced with
* user-level access tokens.
*
* @see SwappableCredentialSupport#swapCredentials(String, String, String)
*/
public class TokenSupplier implements SwappableCredentialSupport, Supplier {
/**
* The number of milliseconds to execute a token refresh in advance of the existing token's expiration instant. For
* example, if the expiration instant is x and the advance refresh window is y, the refresh instant will be (x - y).
*/
private static final long EXPIRATION_ADVANCE_REFRESH_MILLIS = 60000;
/** Default refresh window of 15 min. */
private static final long DEFAULT_REFRESH_DELAY = 60000 * 15;
/** The name to use for the timer thread. */
private static final String TIMER_NAME = "Access Token Refresher";
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(TokenSupplier.class);
/** The token client used to fetch new access tokens. */
private final TokenClient tokenClient;
/** Timer used to schedule token refreshes. */
private final Timer timer;
/** The auth service base URL. */
private final URL authServiceBaseUrl;
/** The auth config provider, needed when using the token client to retrieve tokens. */
private AuthConfigurationProvider authConfigurationProvider;
/** The currently active token. */
private Token token;
/**
* Initializing constructor.
*
* @param provider The auth config provider to set.
*/
public TokenSupplier(@Nonnull AuthConfigurationProvider provider) {
authConfigurationProvider = provider;
timer = new Timer(TIMER_NAME, true);
final String authUrl = provider.getAuthServiceBaseUrl().toString();
final BaseResourceClientFactory clientFactory = ClientInitializationContext.getClientFactory();
final TokenSDK tokenSdk = new TokenSDK(authUrl);
tokenSdk.setAuthConfigProvider(provider);
if (clientFactory != null) {
copyConfig(tokenSdk, clientFactory);
}
tokenClient = tokenSdk.newClient();
try {
authServiceBaseUrl = new URL(authUrl);
} catch (MalformedURLException e) {
throw new IllegalStateException("Bad auth service URL", e);
}
refreshToken();
}
/**
* Copies the client configuration settings from source
to target
.
*
* @param target The target.
* @param source The source.
*/
private void copyConfig(@Nonnull BaseResourceClientFactory target, @Nonnull BaseResourceClientFactory source) {
if (source.getHttpProxySpec() != null) {
target.setHttpProxySpec(source.getHttpProxySpec());
}
if (source.getSourceAddress() != null) {
target.setSourceAddress(source.getSourceAddress());
}
if (source.getContentCompressionEnabled() != null) {
target.setContentCompressionEnabled(source.getContentCompressionEnabled());
}
if (source.getConnectionTimeout() != null) {
target.setConnectionTimeout(source.getConnectionTimeout());
}
if (source.getSocketTimeout() != null) {
target.setSocketTimeout(source.getSocketTimeout());
}
if (source.getContentCharSet() != null) {
target.setContentCharSet(source.getContentCharSet());
}
}
/**
* Refreshes the current access token (which may be null) and schedules the next refresh based on calculated
* expiration time.
*/
private void refreshToken() {
final String clientId = authConfigurationProvider.getClientId();
final String clientSecret = authConfigurationProvider.getClientSecret();
log.debug("Fetching new token for client id {}", clientId);
token = tokenClient.getToken(clientId, clientSecret).checkedGet();
scheduleNextRefresh();
}
/** Schedules the next refresh in advance of the current token's expiration instant. */
private void scheduleNextRefresh() {
long refreshDelay = token.getExpirationTime() - System.currentTimeMillis() - EXPIRATION_ADVANCE_REFRESH_MILLIS;
if (refreshDelay < EXPIRATION_ADVANCE_REFRESH_MILLIS) {
log.warn("Invalid refresh delay: {}. Using default: {}", refreshDelay, DEFAULT_REFRESH_DELAY);
refreshDelay = DEFAULT_REFRESH_DELAY;
}
timer.schedule(new TokenRefresher(), refreshDelay);
}
/** {@inheritDoc} */
public String get() {
return token.getEncodedToken();
}
/** {@inheritDoc} */
public void swapCredentials(@Nonnull @NotEmpty String clientId, @Nonnull @NotEmpty String secret, String appId) {
synchronized (authConfigurationProvider) {
authConfigurationProvider = new BasicAuthConfigurationProvider(clientId, secret, appId, authServiceBaseUrl);
}
}
/** Timer task responsible for refreshing the access token. */
private final class TokenRefresher extends TimerTask {
/** {@inheritDoc} */
public void run() {
refreshToken();
}
}
}