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

com.google.auth.oauth2.ExternalAccountAuthorizedUserCredentials Maven / Gradle / Ivy

There is a newer version: 1.30.0
Show newest version
/*
 * Copyright 2022 Google LLC
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *
 *    * Neither the name of Google LLC nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.google.auth.oauth2;

import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.UrlEncodedContent;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.Preconditions;
import com.google.auth.http.HttpTransportFactory;
import com.google.common.base.MoreObjects;
import com.google.common.io.BaseEncoding;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;

/**
 * OAuth2 credentials sourced using external identities through Workforce Identity Federation.
 *
 * 

Obtaining the initial access and refresh token can be done through the Google Cloud CLI. * *

 * Example credentials file:
 * {
 *   "type": "external_account_authorized_user",
 *   "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID",
 *   "refresh_token": "refreshToken",
 *   "token_url": "https://sts.googleapis.com/v1/oauthtoken",
 *   "token_info_url": "https://sts.googleapis.com/v1/introspect",
 *   "client_id": "clientId",
 *   "client_secret": "clientSecret"
 * }
 * 
*/ public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials { private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; private static final long serialVersionUID = -2181779590486283287L; static final String EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE = "external_account_authorized_user"; private final String transportFactoryClassName; private final String audience; private final String tokenUrl; private final String tokenInfoUrl; private final String revokeUrl; private final String clientId; private final String clientSecret; private String refreshToken; private transient HttpTransportFactory transportFactory; /** * Internal constructor. * * @param builder A builder for {@link ExternalAccountAuthorizedUserCredentials}. See {@link * ExternalAccountAuthorizedUserCredentials.Builder} */ private ExternalAccountAuthorizedUserCredentials(Builder builder) { super(builder); this.transportFactory = MoreObjects.firstNonNull( builder.transportFactory, getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); this.transportFactoryClassName = this.transportFactory.getClass().getName(); this.audience = builder.audience; this.refreshToken = builder.refreshToken; this.tokenUrl = builder.tokenUrl; this.tokenInfoUrl = builder.tokenInfoUrl; this.revokeUrl = builder.revokeUrl; this.clientId = builder.clientId; this.clientSecret = builder.clientSecret; Preconditions.checkState( getAccessToken() != null || canRefresh(), "ExternalAccountAuthorizedUserCredentials must be initialized with " + "an access token or fields to enable refresh: " + "('refresh_token', 'token_url', 'client_id', 'client_secret')."); } /** * Returns external account authorized user credentials defined by a JSON file stream. * * @param credentialsStream the stream with the credential definition * @return the credential defined by the credentialsStream * @throws IOException if the credential cannot be created from the stream */ public static ExternalAccountAuthorizedUserCredentials fromStream(InputStream credentialsStream) throws IOException { checkNotNull(credentialsStream); return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); } /** * Returns external account authorized user credentials defined by a JSON file stream. * * @param credentialsStream the stream with the credential definition * @param transportFactory the HTTP transport factory used to create the transport to get access * tokens * @return the credential defined by the credentialsStream * @throws IOException if the credential cannot be created from the stream */ public static ExternalAccountAuthorizedUserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { checkNotNull(credentialsStream); checkNotNull(transportFactory); JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); GenericJson fileContents = parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); try { return fromJson(fileContents, transportFactory); } catch (ClassCastException | IllegalArgumentException e) { throw new CredentialFormatException("Invalid input stream provided.", e); } } @Override public AccessToken refreshAccessToken() throws IOException { if (!canRefresh()) { throw new IllegalStateException( "Unable to refresh ExternalAccountAuthorizedUserCredentials. All of 'refresh_token'," + "'token_url', 'client_id', 'client_secret' are required to refresh."); } HttpResponse response; try { HttpRequest httpRequest = buildRefreshRequest(); response = httpRequest.execute(); } catch (HttpResponseException e) { throw OAuthException.createFromHttpResponseException(e); } // Parse response. GenericData responseData = response.parseAs(GenericData.class); response.disconnect(); // Required fields. String accessToken = OAuth2Utils.validateString(responseData, /* key= */ "access_token", PARSE_ERROR_PREFIX); int expiresInSeconds = OAuth2Utils.validateInt32(responseData, /* key= */ "expires_in", PARSE_ERROR_PREFIX); Date expiresAtMilliseconds = new Date(clock.currentTimeMillis() + expiresInSeconds * 1000L); // Set the new refresh token if returned. String refreshToken = OAuth2Utils.validateOptionalString( responseData, /* key= */ "refresh_token", PARSE_ERROR_PREFIX); if (refreshToken != null && refreshToken.trim().length() > 0) { this.refreshToken = refreshToken; } return AccessToken.newBuilder() .setExpirationTime(expiresAtMilliseconds) .setTokenValue(accessToken) .build(); } @Nullable public String getAudience() { return audience; } @Nullable public String getClientId() { return clientId; } @Nullable public String getClientSecret() { return clientSecret; } @Nullable public String getRevokeUrl() { return revokeUrl; } @Nullable public String getTokenUrl() { return tokenUrl; } @Nullable public String getTokenInfoUrl() { return tokenInfoUrl; } @Nullable public String getRefreshToken() { return refreshToken; } public static Builder newBuilder() { return new Builder(); } @Override public int hashCode() { return Objects.hash( super.hashCode(), getAccessToken(), clientId, clientSecret, refreshToken, tokenUrl, tokenInfoUrl, revokeUrl, audience, transportFactoryClassName, quotaProjectId); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("requestMetadata", getRequestMetadataInternal()) .add("temporaryAccess", getAccessToken()) .add("clientId", clientId) .add("clientSecret", clientSecret) .add("refreshToken", refreshToken) .add("tokenUrl", tokenUrl) .add("tokenInfoUrl", tokenInfoUrl) .add("revokeUrl", revokeUrl) .add("audience", audience) .add("transportFactoryClassName", transportFactoryClassName) .add("quotaProjectId", quotaProjectId) .toString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof ExternalAccountAuthorizedUserCredentials)) { return false; } ExternalAccountAuthorizedUserCredentials credentials = (ExternalAccountAuthorizedUserCredentials) obj; return super.equals(credentials) && Objects.equals(this.getAccessToken(), credentials.getAccessToken()) && Objects.equals(this.clientId, credentials.clientId) && Objects.equals(this.clientSecret, credentials.clientSecret) && Objects.equals(this.refreshToken, credentials.refreshToken) && Objects.equals(this.tokenUrl, credentials.tokenUrl) && Objects.equals(this.tokenInfoUrl, credentials.tokenInfoUrl) && Objects.equals(this.revokeUrl, credentials.revokeUrl) && Objects.equals(this.audience, credentials.audience) && Objects.equals(this.transportFactoryClassName, credentials.transportFactoryClassName) && Objects.equals(this.quotaProjectId, credentials.quotaProjectId); } @Override public Builder toBuilder() { return new Builder(this); } /** * Returns external account authorized user credentials defined by JSON contents using the format * supported by the Cloud SDK. * * @param json a map from the JSON representing the credentials * @param transportFactory HTTP transport factory, creates the transport used to get access tokens * @return the external account authorized user credentials defined by the JSON */ static ExternalAccountAuthorizedUserCredentials fromJson( Map json, HttpTransportFactory transportFactory) throws IOException { String audience = (String) json.get("audience"); String refreshToken = (String) json.get("refresh_token"); String tokenUrl = (String) json.get("token_url"); String tokenInfoUrl = (String) json.get("token_info_url"); String revokeUrl = (String) json.get("revoke_url"); String clientId = (String) json.get("client_id"); String clientSecret = (String) json.get("client_secret"); String quotaProjectId = (String) json.get("quota_project_id"); String universeDomain = (String) json.get("universe_domain"); return ExternalAccountAuthorizedUserCredentials.newBuilder() .setAudience(audience) .setRefreshToken(refreshToken) .setTokenUrl(tokenUrl) .setTokenInfoUrl(tokenInfoUrl) .setRevokeUrl(revokeUrl) .setClientId(clientId) .setClientSecret(clientSecret) .setRefreshToken(refreshToken) .setHttpTransportFactory(transportFactory) .setQuotaProjectId(quotaProjectId) .setUniverseDomain(universeDomain) .build(); } private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { input.defaultReadObject(); transportFactory = newInstance(transportFactoryClassName); } private boolean canRefresh() { return refreshToken != null && refreshToken.trim().length() > 0 && tokenUrl != null && tokenUrl.trim().length() > 0 && clientId != null && clientId.trim().length() > 0 && clientSecret != null && clientSecret.trim().length() > 0; } private HttpRequest buildRefreshRequest() throws IOException { GenericData tokenRequest = new GenericData(); tokenRequest.set("grant_type", "refresh_token"); tokenRequest.set("refresh_token", refreshToken); HttpRequest request = transportFactory .create() .createRequestFactory() .buildPostRequest(new GenericUrl(tokenUrl), new UrlEncodedContent(tokenRequest)); request.setParser(new JsonObjectParser(JSON_FACTORY)); HttpHeaders requestHeaders = request.getHeaders(); requestHeaders.setAuthorization( String.format( "Basic %s", BaseEncoding.base64() .encode( String.format("%s:%s", clientId, clientSecret) .getBytes(StandardCharsets.UTF_8)))); return request; } /** Builder for {@link ExternalAccountAuthorizedUserCredentials}. */ public static class Builder extends GoogleCredentials.Builder { private HttpTransportFactory transportFactory; private String audience; private String refreshToken; private String tokenUrl; private String tokenInfoUrl; private String revokeUrl; private String clientId; private String clientSecret; protected Builder() {} protected Builder(ExternalAccountAuthorizedUserCredentials credentials) { super(credentials); this.transportFactory = credentials.transportFactory; this.audience = credentials.audience; this.refreshToken = credentials.refreshToken; this.tokenUrl = credentials.tokenUrl; this.tokenInfoUrl = credentials.tokenInfoUrl; this.revokeUrl = credentials.revokeUrl; this.clientId = credentials.clientId; this.clientSecret = credentials.clientSecret; } /** * Sets the HTTP transport factory. * * @param transportFactory the {@code HttpTransportFactory} to set * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) { this.transportFactory = transportFactory; return this; } /** * Sets the optional audience, which is usually the fully specified resource name of the * workforce pool provider. * * @param audience the audience to set * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setAudience(String audience) { this.audience = audience; return this; } /** * Sets the token exchange endpoint. * * @param tokenUrl the token exchange url to set * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setTokenUrl(String tokenUrl) { this.tokenUrl = tokenUrl; return this; } /** * Sets the token introspection endpoint used to retrieve account related information. * * @param tokenInfoUrl the token info url to set * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setTokenInfoUrl(String tokenInfoUrl) { this.tokenInfoUrl = tokenInfoUrl; return this; } /** * Sets the token revocation endpoint. * * @param revokeUrl the revoke url to set * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setRevokeUrl(String revokeUrl) { this.revokeUrl = revokeUrl; return this; } /** * Sets the OAuth 2.0 refresh token. * * @param refreshToken the refresh token * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; return this; } /** * Sets the OAuth 2.0 client ID. * * @param clientId the client ID * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setClientId(String clientId) { this.clientId = clientId; return this; } /** * Sets the OAuth 2.0 client secret. * * @param clientSecret the client secret * @return this {@code Builder} object */ @CanIgnoreReturnValue public Builder setClientSecret(String clientSecret) { this.clientSecret = clientSecret; return this; } /** * Sets the optional project used for quota and billing purposes. * * @param quotaProjectId the quota and billing project id to set * @return this {@code Builder} object */ @Override @CanIgnoreReturnValue public Builder setQuotaProjectId(String quotaProjectId) { super.setQuotaProjectId(quotaProjectId); return this; } /** * Sets the optional access token. * * @param accessToken the access token * @return this {@code Builder} object */ @Override @CanIgnoreReturnValue public Builder setAccessToken(AccessToken accessToken) { super.setAccessToken(accessToken); return this; } /** * Sets the optional universe domain. The Google Default Universe is used when not provided. * * @param universeDomain the universe domain to set * @return this {@code Builder} object */ @CanIgnoreReturnValue @Override public Builder setUniverseDomain(String universeDomain) { super.setUniverseDomain(universeDomain); return this; } @Override public ExternalAccountAuthorizedUserCredentials build() { return new ExternalAccountAuthorizedUserCredentials(this); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy