-
Notifications
You must be signed in to change notification settings - Fork 265
feat: Implement ClientSideCredentialAccessBoundaryFactory #1562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
d9574da
6b04cc1
5633cc1
c50a10f
62b501c
76d30b9
bd01ab3
a64cebd
272db77
2056ca3
5911813
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| package com.google.auth.credentialaccessboundary; | ||
|
|
||
| import static com.google.auth.oauth2.OAuth2Credentials.getFromServiceLoader; | ||
| import static com.google.common.base.MoreObjects.firstNonNull; | ||
| import static com.google.common.base.Preconditions.checkNotNull; | ||
|
|
||
| import com.google.auth.Credentials; | ||
| import com.google.auth.http.HttpTransportFactory; | ||
| import com.google.auth.oauth2.AccessToken; | ||
| import com.google.auth.oauth2.GoogleCredentials; | ||
| import com.google.auth.oauth2.OAuth2Utils; | ||
| import com.google.auth.oauth2.StsRequestHandler; | ||
| import com.google.auth.oauth2.StsTokenExchangeRequest; | ||
| import com.google.auth.oauth2.StsTokenExchangeResponse; | ||
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||
| import java.io.IOException; | ||
|
|
||
| public final class ClientSideCredentialAccessBoundaryFactory { | ||
| private final GoogleCredentials sourceCredential; | ||
| private final transient HttpTransportFactory transportFactory; | ||
| private final String tokenExchangeEndpoint; | ||
| private String acceessBoundarySessionKey; | ||
| private AccessToken intermediaryAccessToken; | ||
|
|
||
| private ClientSideCredentialAccessBoundaryFactory(Builder builder) { | ||
nbayati marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| this.transportFactory = | ||
| firstNonNull( | ||
| builder.transportFactory, | ||
| getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); | ||
| this.sourceCredential = checkNotNull(builder.sourceCredential); | ||
|
|
||
| // Default to GDU when not supplied. | ||
| String universeDomain; | ||
| if (builder.universeDomain == null || builder.universeDomain.trim().isEmpty()) { | ||
nbayati marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; | ||
| } else { | ||
| universeDomain = builder.universeDomain; | ||
| } | ||
|
|
||
| // Ensure source credential's universe domain matches. | ||
| try { | ||
| if (!universeDomain.equals(sourceCredential.getUniverseDomain())) { | ||
| throw new IllegalArgumentException( | ||
| "The client side access boundary credential's universe domain must be the same as the source " | ||
| + "credential."); | ||
| } | ||
| } catch (IOException e) { | ||
| // Throwing an IOException would be a breaking change, so wrap it here. | ||
| throw new IllegalStateException( | ||
| "Error occurred when attempting to retrieve source credential universe domain.", e); | ||
| } | ||
| String TOKEN_EXCHANGE_URL_FORMAT = "https://sts.{universe_domain}/v1/token"; | ||
| this.tokenExchangeEndpoint = | ||
| TOKEN_EXCHANGE_URL_FORMAT.replace("{universe_domain}", universeDomain); | ||
nbayati marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public void fetchCredentials() throws IOException { | ||
nbayati marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try { | ||
| this.sourceCredential.refreshIfExpired(); | ||
| } catch (IOException e) { | ||
| throw new IOException("Unable to refresh the provided source credential.", e); | ||
nbayati marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| AccessToken sourceAccessToken = sourceCredential.getAccessToken(); | ||
| if (sourceAccessToken == null || sourceAccessToken.getTokenValue() == null) { | ||
nbayati marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| throw new IOException("The source credential does not have an access token."); | ||
| } | ||
|
|
||
| StsTokenExchangeRequest request = | ||
| StsTokenExchangeRequest.newBuilder( | ||
| sourceAccessToken.getTokenValue(), OAuth2Utils.TOKEN_TYPE_ACCESS_TOKEN) | ||
| .setRequestTokenType(OAuth2Utils.TOKEN_TYPE_ACCESS_BOUNDARY_INTERMEDIARY_TOKEN) | ||
| .build(); | ||
|
|
||
| StsRequestHandler handler = | ||
| StsRequestHandler.newBuilder( | ||
| tokenExchangeEndpoint, request, transportFactory.create().createRequestFactory()) | ||
| .build(); | ||
|
|
||
| StsTokenExchangeResponse response = handler.exchangeToken(); | ||
| this.acceessBoundarySessionKey = response.getAccessBoundarySessionKey(); | ||
| this.intermediaryAccessToken = response.getAccessToken(); | ||
|
|
||
| // The STS endpoint will only return the expiration time for the intermediary token | ||
| // if the original access token represents a service account. | ||
| // The intermediary token's expiration time will always match the source credential expiration. | ||
| // When no expires_in is returned, we can copy the source credential's expiration time. | ||
| if (response.getAccessToken().getExpirationTime() == null) { | ||
| if (sourceAccessToken.getExpirationTime() != null) { | ||
| this.intermediaryAccessToken = | ||
| new AccessToken( | ||
| response.getAccessToken().getTokenValue(), sourceAccessToken.getExpirationTime()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static Builder newBuilder() { | ||
| return new Builder(); | ||
| } | ||
|
|
||
| public static class Builder { | ||
| private GoogleCredentials sourceCredential; | ||
| private HttpTransportFactory transportFactory; | ||
| private String universeDomain; | ||
|
|
||
| private Builder() {} | ||
|
|
||
| /** | ||
| * Sets the required source credential used to acquire the intermediary credential. | ||
| * | ||
| * @param sourceCredential the {@code GoogleCredentials} to set | ||
| * @return this {@code Builder} object | ||
| */ | ||
| public Builder setSourceCredential(GoogleCredentials sourceCredential) { | ||
| this.sourceCredential = sourceCredential; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * 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 universe domain. | ||
| * | ||
| * @param universeDomain the universe domain to set | ||
| * @return this {@code Builder} object | ||
| */ | ||
| @CanIgnoreReturnValue | ||
| public Builder setUniverseDomain(String universeDomain) { | ||
| this.universeDomain = universeDomain; | ||
| return this; | ||
| } | ||
|
|
||
| public ClientSideCredentialAccessBoundaryFactory build() { | ||
| return new ClientSideCredentialAccessBoundaryFactory(this); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,11 +70,15 @@ | |
| import java.util.Set; | ||
|
|
||
| /** Internal utilities for the com.google.auth.oauth2 namespace. */ | ||
| class OAuth2Utils { | ||
| public class OAuth2Utils { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if it makes sense for this to be public @lqiu96
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One option for this to see if we can keep this package-private and move some of these constants to STSRequestHandler. It looks like
Otherwise, I think I'm ok with making it public given the module constraints we have.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to leave this comment open and keep it as-is for now so I can merge this PR and unblock #1571. I'll address this comment in the next PR. |
||
|
|
||
| static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; | ||
|
|
||
| static final String TOKEN_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"; | ||
| public static final String TOKEN_TYPE_ACCESS_TOKEN = | ||
| "urn:ietf:params:oauth:token-type:access_token"; | ||
| static final String TOKEN_TYPE_TOKEN_EXCHANGE = "urn:ietf:params:oauth:token-type:token-exchange"; | ||
| public static final String TOKEN_TYPE_ACCESS_BOUNDARY_INTERMEDIARY_TOKEN = | ||
| "urn:ietf:params:oauth:token-type:access_boundary_intermediary_token"; | ||
| static final String GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer"; | ||
|
|
||
| // generateIdToken endpoint is to be formatted with universe domain and client email | ||
|
|
@@ -93,7 +97,8 @@ class OAuth2Utils { | |
|
|
||
| static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); | ||
|
|
||
| static final HttpTransportFactory HTTP_TRANSPORT_FACTORY = new DefaultHttpTransportFactory(); | ||
| public static final HttpTransportFactory HTTP_TRANSPORT_FACTORY = | ||
| new DefaultHttpTransportFactory(); | ||
|
|
||
| static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.