/** * Copyright (C) 2015 Zalando SE (http://tech.zalando.com) * * 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.zalando.stups.tokens; import java.io.IOException; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zalando.stups.tokens.mcb.MCB; import org.zalando.stups.tokens.util.Metrics; import org.zalando.stups.tokens.util.Objects; class AccessTokenRefresher extends AbstractAccessTokenRefresher implements AccessTokens, Runnable { private static final Logger LOG = LoggerFactory.getLogger(AccessTokenRefresher.class); private static final String METRICS_KEY_PREFIX = "tokens.refresher"; private final MCB mcb; private final Set<Object> invalidTokens = Collections.newSetFromMap(new ConcurrentHashMap<Object, Boolean>()); private final TokenVerifyRunner verifyRunner; private final MetricsListener metricsListener; public AccessTokenRefresher(final TokenRefresherConfiguration configuration) { super(configuration); this.metricsListener = configuration.getMetricsListener(); this.verifyRunner = new TokenVerifyRunner(configuration, accessTokens, invalidTokens); this.mcb = new MCB(configuration.getTokenRefresherMcbConfig()); } @Override public void start() { initializeFixedTokensFromEnvironment(); LOG.info("Starting to refresh tokens regularly..."); run(); // #10, increase 'period' to 5 to avoid flooding the endpoint scheduler.scheduleAtFixedRate(this, 1, configuration.getSchedulingPeriod(), configuration.getSchedulingTimeUnit()); // #36 scheduler.scheduleAtFixedRate(verifyRunner, 5, configuration.getTokenVerifierSchedulingPeriod(), configuration.getTokenVerifierSchedulingTimeUnit()); } static int percentLeft(final AccessToken token) { final long now = System.currentTimeMillis(); final long validUntil = token.getValidUntil().getTime(); final long hundredPercentSeconds = token.getInitialValidSeconds(); final long secondsLeft = (validUntil - now) / 1000; return (int) ((double) secondsLeft / (double) hundredPercentSeconds * 100); } static boolean shouldRefresh(final AccessToken token, final TokenRefresherConfiguration configuration) { return percentLeft(token) <= configuration.getRefreshPercentLeft(); } static boolean shouldWarn(final AccessToken token, final TokenRefresherConfiguration configuration) { return percentLeft(token) <= configuration.getWarnPercentLeft(); } protected boolean isInvalid(final AccessToken token) { if (token == null) { return false; } return invalidTokens.contains(token); } @Override public void run() { if (mcb.isClosed()) { for (final AccessTokenConfiguration tokenConfig : configuration.getAccessTokenConfigurations()) { try { final AccessToken oldToken = accessTokens.get(tokenConfig.getTokenId()); if (oldToken == null || shouldRefresh(oldToken, configuration) || isInvalid(oldToken)) { try { LOG.trace("Refreshing access token {}...", tokenConfig.getTokenId()); final AccessToken newToken = createToken(tokenConfig); // validate Objects.notNull("newToken", newToken); accessTokens.put(tokenConfig.getTokenId(), newToken); if (oldToken != null) { invalidTokens.remove(oldToken); } mcb.onSuccess(); LOG.info("Refreshed access token {}.", tokenConfig.getTokenId()); } catch (final Throwable t) { if (oldToken == null || shouldWarn(oldToken, configuration)) { LOG.warn("Cannot refresh access token " + tokenConfig.getTokenId(), t); } else { LOG.info("Cannot refresh access token {}", tokenConfig.getTokenId(), t); } mcb.onError(); } } } catch (Throwable t) { mcb.onError(); LOG.warn("Unexpected problem during token refresh run! TokenId: " + tokenConfig.getTokenId(), t); } } } else { LOG.debug("{} is open, skip refresh", mcb.getName()); } } private AccessToken createToken(final AccessTokenConfiguration tokenConfig) { final ClientCredentials clientCredentials = configuration.getClientCredentialsProvider().get(); UserCredentials userCredentials = null; if (configuration.getUserCredentialsProvider() != null) { userCredentials = configuration.getUserCredentialsProvider().get(); } long start = System.currentTimeMillis(); boolean success = true; try (final HttpProvider httpProvider = buildHttpProvider(clientCredentials, userCredentials)) { return httpProvider.createToken(tokenConfig); } catch (RuntimeException | IOException e) { success = false; throw new AccessTokenEndpointException(e.getMessage(), e); } finally { long diff = System.currentTimeMillis() - start; metricsListener.submitToTimer(Metrics.buildMetricsKey(METRICS_KEY_PREFIX, success), diff); } } private HttpProvider buildHttpProvider(ClientCredentials clientCredentials, UserCredentials userCredentials) { HttpProviderFactory providerFactory = configuration.getHttpProviderFactory(); HttpProvider httpProvider = providerFactory.create(clientCredentials, userCredentials, configuration.getAccessTokenUri(), configuration.getHttpConfig()); return httpProvider; } }