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

com.rt.storage.auth.oauth2.OAuth2Credentials Maven / Gradle / Ivy

package com.rt.storage.auth.oauth2;

import com.rt.storage.api.client.util.Clock;
import com.rt.storage.auth.Credentials;
import com.rt.storage.auth.RequestMetadataCallback;
import com.rt.storage.auth.http.AuthHttpConstants;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.Executor;

/** Base type for Credentials using OAuth2. */
public class OAuth2Credentials extends Credentials {

  private static final long serialVersionUID = 4556936364828217687L;
  private static final long MINIMUM_TOKEN_MILLISECONDS = 60000L * 5L;

  // byte[] is serializable, so the lock variable can be final
  private final Object lock = new byte[0];
  private Map> requestMetadata;
  private AccessToken temporaryAccess;

  // Change listeners are not serialized
  private transient List changeListeners;
  // Until we expose this to the users it can remain transient and non-serializable
  @VisibleForTesting transient Clock clock = Clock.SYSTEM;

  /**
   * Returns the credentials instance from the given access token.
   *
   * @param accessToken the access token
   * @return the credentials instance
   */
  public static OAuth2Credentials create(AccessToken accessToken) {
    return OAuth2Credentials.newBuilder().setAccessToken(accessToken).build();
  }

  /** Default constructor. */
  protected OAuth2Credentials() {
    this(null);
  }

  /**
   * Constructor with explicit access token.
   *
   * @param accessToken initial or temporary access token
   */
  protected OAuth2Credentials(AccessToken accessToken) {
    if (accessToken != null) {
      useAccessToken(accessToken);
    }
  }

  @Override
  public String getAuthenticationType() {
    return "OAuth2";
  }

  @Override
  public boolean hasRequestMetadata() {
    return true;
  }

  @Override
  public boolean hasRequestMetadataOnly() {
    return true;
  }

  /**
   * Returns the cached access token.
   *
   * 

If not set, you should call {@link #refresh()} to fetch and cache an access token. * * @return The cached access token. */ public final AccessToken getAccessToken() { return temporaryAccess; } @Override public void getRequestMetadata( final URI uri, Executor executor, final RequestMetadataCallback callback) { Map> metadata; synchronized (lock) { if (shouldRefresh()) { // The base class implementation will do a blocking get in the executor. super.getRequestMetadata(uri, executor, callback); return; } metadata = Preconditions.checkNotNull(requestMetadata, "cached requestMetadata"); } callback.onSuccess(metadata); } /** * Provide the request metadata by ensuring there is a current access token and providing it as an * authorization bearer token. */ @Override public Map> getRequestMetadata(URI uri) throws IOException { synchronized (lock) { if (shouldRefresh()) { refresh(); } return Preconditions.checkNotNull(requestMetadata, "requestMetadata"); } } /** Refresh the token by discarding the cached token and metadata and requesting the new ones. */ @Override public void refresh() throws IOException { synchronized (lock) { requestMetadata = null; temporaryAccess = null; useAccessToken(Preconditions.checkNotNull(refreshAccessToken(), "new access token")); if (changeListeners != null) { for (CredentialsChangedListener listener : changeListeners) { listener.onChanged(this); } } } } /** * Refresh these credentials only if they have expired or are expiring imminently. * * @throws IOException during token refresh. */ public void refreshIfExpired() throws IOException { synchronized (lock) { if (shouldRefresh()) { refresh(); } } } // Must be called under lock private void useAccessToken(AccessToken token) { this.temporaryAccess = token; this.requestMetadata = Collections.singletonMap( AuthHttpConstants.AUTHORIZATION, Collections.singletonList(OAuth2Utils.BEARER_PREFIX + token.getTokenValue())); } // Must be called under lock // requestMetadata will never be null if false is returned. private boolean shouldRefresh() { Long expiresIn = getExpiresInMilliseconds(); return requestMetadata == null || expiresIn != null && expiresIn <= MINIMUM_TOKEN_MILLISECONDS; } /** * Method to refresh the access token according to the specific type of credentials. * *

Throws IllegalStateException if not overridden since direct use of OAuth2Credentials is only * for temporary or non-refreshing access tokens. * * @return Refreshed access token. * @throws IOException from derived implementations */ public AccessToken refreshAccessToken() throws IOException { throw new IllegalStateException( "OAuth2Credentials instance does not support refreshing the" + " access token. An instance with a new access token should be used, or a derived type" + " that supports refreshing."); } /** * Adds a listener that is notified when the Credentials data changes. * *

This is called when token content changes, such as when the access token is refreshed. This * is typically used by code caching the access token. * * @param listener The listener to be added. */ public final void addChangeListener(CredentialsChangedListener listener) { synchronized (lock) { if (changeListeners == null) { changeListeners = new ArrayList<>(); } changeListeners.add(listener); } } /** * Removes a listener that was added previously. * * @param listener The listener to be removed. */ public final void removeChangeListener(CredentialsChangedListener listener) { synchronized (lock) { if (changeListeners != null) { changeListeners.remove(listener); } } } /** * Return the remaining time the current access token will be valid, or null if there is no token * or expiry information. Must be called under lock. */ private Long getExpiresInMilliseconds() { if (temporaryAccess == null) { return null; } Date expirationTime = temporaryAccess.getExpirationTime(); if (expirationTime == null) { return null; } return (expirationTime.getTime() - clock.currentTimeMillis()); } /** * Listener for changes to credentials. * *

This is called when token content changes, such as when the access token is refreshed. This * is typically used by code caching the access token. */ public interface CredentialsChangedListener { /** * Notifies that the credentials have changed. * *

This is called when token content changes, such as when the access token is refreshed. * This is typically used by code caching the access token. * * @param credentials The updated credentials instance * @throws IOException My be thrown by listeners if saving credentials fails. */ void onChanged(OAuth2Credentials credentials) throws IOException; } @Override public int hashCode() { return Objects.hash(requestMetadata, temporaryAccess); } protected Map> getRequestMetadataInternal() { return requestMetadata; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("requestMetadata", requestMetadata) .add("temporaryAccess", temporaryAccess) .toString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof OAuth2Credentials)) { return false; } OAuth2Credentials other = (OAuth2Credentials) obj; return Objects.equals(this.requestMetadata, other.requestMetadata) && Objects.equals(this.temporaryAccess, other.temporaryAccess); } private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { input.defaultReadObject(); clock = Clock.SYSTEM; } @SuppressWarnings("unchecked") protected static T newInstance(String className) throws IOException, ClassNotFoundException { try { return (T) Class.forName(className).newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new IOException(e); } } protected static T getFromServiceLoader(Class clazz, T defaultInstance) { return Iterables.getFirst(ServiceLoader.load(clazz), defaultInstance); } public static Builder newBuilder() { return new Builder(); } public Builder toBuilder() { return new Builder(this); } public static class Builder { private AccessToken accessToken; protected Builder() {} protected Builder(OAuth2Credentials credentials) { this.accessToken = credentials.getAccessToken(); } public Builder setAccessToken(AccessToken token) { this.accessToken = token; return this; } public AccessToken getAccessToken() { return accessToken; } public OAuth2Credentials build() { return new OAuth2Credentials(accessToken); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy