Skip to content
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

feat: add support for Workforce Pools #729

Merged
merged 7 commits into from Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -293,22 +293,23 @@ public static ExternalAccountCredentials fromStream(
* @return the credentials defined by the JSON
*/
static ExternalAccountCredentials fromJson(
Map<String, Object> json, HttpTransportFactory transportFactory) {
Map<String, Object> json, HttpTransportFactory transportFactory) throws IOException {
checkNotNull(json);
checkNotNull(transportFactory);

String audience = (String) json.get("audience");
String subjectTokenType = (String) json.get("subject_token_type");
String tokenUrl = (String) json.get("token_url");
String serviceAccountImpersonationUrl = (String) json.get("service_account_impersonation_url");

Map<String, Object> credentialSourceMap = (Map<String, Object>) json.get("credential_source");

// Optional params.
String serviceAccountImpersonationUrl = (String) json.get("service_account_impersonation_url");
String tokenInfoUrl = (String) json.get("token_info_url");
String clientId = (String) json.get("client_id");
String clientSecret = (String) json.get("client_secret");
String quotaProjectId = (String) json.get("quota_project_id");
String userProject = (String) json.get("workforce_pool_user_project");

if (isAwsCredential(credentialSourceMap)) {
return new AwsCredentials(
Expand All @@ -325,19 +326,30 @@ static ExternalAccountCredentials fromJson(
/* scopes= */ null,
/* environmentProvider= */ null);
}
return new IdentityPoolCredentials(
transportFactory,
audience,
subjectTokenType,
tokenUrl,
new IdentityPoolCredentialSource(credentialSourceMap),
tokenInfoUrl,
serviceAccountImpersonationUrl,
quotaProjectId,
clientId,
clientSecret,
/* scopes= */ null,
/* environmentProvider= */ null);

// Validate that the userProject parameter, if present, is for a Workforce configuration.
// This doesn't validate the if the workforce audience is valid, only if it follows the
// workforce pattern.
Pattern workforceAudiencePattern =
lsirac marked this conversation as resolved.
Show resolved Hide resolved
Pattern.compile("^.+/locations/.+/workforcePools/.+/providers/.+$");
if (userProject != null && !workforceAudiencePattern.matcher(audience).matches()) {
throw new IOException(
"The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.");
}

return IdentityPoolCredentials.newBuilder()
lsirac marked this conversation as resolved.
Show resolved Hide resolved
.setWorkforcePoolUserProject(userProject)
.setHttpTransportFactory(transportFactory)
.setAudience(audience)
.setSubjectTokenType(subjectTokenType)
.setTokenUrl(tokenUrl)
.setTokenInfoUrl(tokenInfoUrl)
.setCredentialSource(new IdentityPoolCredentialSource(credentialSourceMap))
.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl)
.setQuotaProjectId(quotaProjectId)
.setClientId(clientId)
.setClientSecret(clientSecret)
.build();
}

private static boolean isAwsCredential(Map<String, Object> credentialSource) {
Expand All @@ -362,6 +374,7 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
StsRequestHandler requestHandler =
StsRequestHandler.newBuilder(
tokenUrl, stsTokenExchangeRequest, transportFactory.create().createRequestFactory())
.setInternalOptions(stsTokenExchangeRequest.getInternalOptions())
.build();

StsTokenExchangeResponse response = requestHandler.exchangeToken();
Expand Down
102 changes: 44 additions & 58 deletions oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java
Expand Up @@ -37,7 +37,6 @@
import com.google.api.client.http.HttpResponse;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonObjectParser;
import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.IdentityPoolCredentials.IdentityPoolCredentialSource.CredentialFormatType;
import com.google.common.io.CharStreams;
import java.io.BufferedReader;
Expand Down Expand Up @@ -155,39 +154,32 @@ private boolean hasHeaders() {

private final IdentityPoolCredentialSource identityPoolCredentialSource;

/**
* Internal constructor. See {@link
* ExternalAccountCredentials#ExternalAccountCredentials(HttpTransportFactory, String, String,
* String, CredentialSource, String, String, String, String, String, Collection,
* EnvironmentProvider)}
*/
IdentityPoolCredentials(
HttpTransportFactory transportFactory,
String audience,
String subjectTokenType,
String tokenUrl,
IdentityPoolCredentialSource credentialSource,
@Nullable String tokenInfoUrl,
@Nullable String serviceAccountImpersonationUrl,
@Nullable String quotaProjectId,
@Nullable String clientId,
@Nullable String clientSecret,
@Nullable Collection<String> scopes,
@Nullable EnvironmentProvider environmentProvider) {
// This is used for Workforce Pools. It is passed to STS during token exchange in the
// `options` param and will be embedded in the token by STS.
@Nullable private String workforcePoolUserProject;

/** Internal constructor. See {@link Builder}. */
IdentityPoolCredentials(Builder builder) {
super(
transportFactory,
audience,
subjectTokenType,
tokenUrl,
credentialSource,
tokenInfoUrl,
serviceAccountImpersonationUrl,
quotaProjectId,
clientId,
clientSecret,
scopes,
environmentProvider);
this.identityPoolCredentialSource = credentialSource;
builder.transportFactory,
builder.audience,
builder.subjectTokenType,
builder.tokenUrl,
builder.credentialSource,
builder.tokenInfoUrl,
builder.serviceAccountImpersonationUrl,
builder.quotaProjectId,
builder.clientId,
builder.clientSecret,
builder.scopes,
builder.environmentProvider);
this.identityPoolCredentialSource = (IdentityPoolCredentialSource) builder.credentialSource;
this.workforcePoolUserProject = builder.workforcePoolUserProject;
}

@Nullable
public String getWorkforcePoolUserProject() {
lsirac marked this conversation as resolved.
Show resolved Hide resolved
lsirac marked this conversation as resolved.
Show resolved Hide resolved
return workforcePoolUserProject;
}

@Override
Expand All @@ -202,6 +194,15 @@ public AccessToken refreshAccessToken() throws IOException {
stsTokenExchangeRequest.setScopes(new ArrayList<>(scopes));
}

// If the workforcePoolUserProject is set, we need to pass it to STS via the the internal
// options param.
if (workforcePoolUserProject != null && !workforcePoolUserProject.isEmpty()) {
lsirac marked this conversation as resolved.
Show resolved Hide resolved
lsirac marked this conversation as resolved.
Show resolved Hide resolved
GenericJson options = new GenericJson();
options.setFactory(OAuth2Utils.JSON_FACTORY);
options.put("userProject", workforcePoolUserProject);
stsTokenExchangeRequest.setInternalOptions(options.toString());
}

return exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest.build());
}

Expand Down Expand Up @@ -273,18 +274,7 @@ private String getSubjectTokenFromMetadataServer() throws IOException {
@Override
public IdentityPoolCredentials createScoped(Collection<String> newScopes) {
return new IdentityPoolCredentials(
transportFactory,
getAudience(),
getSubjectTokenType(),
getTokenUrl(),
identityPoolCredentialSource,
getTokenInfoUrl(),
getServiceAccountImpersonationUrl(),
getQuotaProjectId(),
getClientId(),
getClientSecret(),
newScopes,
getEnvironmentProvider());
(IdentityPoolCredentials.Builder) newBuilder(this).setScopes(newScopes));
}

public static Builder newBuilder() {
Expand All @@ -297,27 +287,23 @@ public static Builder newBuilder(IdentityPoolCredentials identityPoolCredentials

public static class Builder extends ExternalAccountCredentials.Builder {

@Nullable private String workforcePoolUserProject;

Builder() {}

Builder(IdentityPoolCredentials credentials) {
super(credentials);
setWorkforcePoolUserProject(credentials.getWorkforcePoolUserProject());
}

public Builder setWorkforcePoolUserProject(String workforcePoolUserProject) {
this.workforcePoolUserProject = workforcePoolUserProject;
return this;
}

@Override
public IdentityPoolCredentials build() {
return new IdentityPoolCredentials(
transportFactory,
audience,
subjectTokenType,
tokenUrl,
(IdentityPoolCredentialSource) credentialSource,
tokenInfoUrl,
serviceAccountImpersonationUrl,
quotaProjectId,
clientId,
clientSecret,
scopes,
environmentProvider);
return new IdentityPoolCredentials(this);
}
}
}
Expand Up @@ -51,6 +51,7 @@ final class StsTokenExchangeRequest {
@Nullable private final String resource;
@Nullable private final String audience;
@Nullable private final String requestedTokenType;
@Nullable private final String internalOptions;

private StsTokenExchangeRequest(
String subjectToken,
Expand All @@ -59,14 +60,16 @@ private StsTokenExchangeRequest(
@Nullable List<String> scopes,
@Nullable String resource,
@Nullable String audience,
@Nullable String requestedTokenType) {
@Nullable String requestedTokenType,
@Nullable String internalOptions) {
this.subjectToken = checkNotNull(subjectToken);
this.subjectTokenType = checkNotNull(subjectTokenType);
this.actingParty = actingParty;
this.scopes = scopes;
this.resource = resource;
this.audience = audience;
this.requestedTokenType = requestedTokenType;
this.internalOptions = internalOptions;
}

public static Builder newBuilder(String subjectToken, String subjectTokenType) {
Expand Down Expand Up @@ -110,6 +113,11 @@ public ActingParty getActingParty() {
return actingParty;
}

@Nullable
public String getInternalOptions() {
return internalOptions;
}

public boolean hasResource() {
return resource != null && !resource.isEmpty();
}
Expand Down Expand Up @@ -139,6 +147,7 @@ public static class Builder {
@Nullable private String requestedTokenType;
@Nullable private List<String> scopes;
@Nullable private ActingParty actingParty;
@Nullable private String internalOptions;

private Builder(String subjectToken, String subjectTokenType) {
this.subjectToken = subjectToken;
Expand Down Expand Up @@ -170,6 +179,11 @@ public StsTokenExchangeRequest.Builder setActingParty(ActingParty actingParty) {
return this;
}

public StsTokenExchangeRequest.Builder setInternalOptions(String internalOptions) {
this.internalOptions = internalOptions;
return this;
}

public StsTokenExchangeRequest build() {
return new StsTokenExchangeRequest(
subjectToken,
Expand All @@ -178,7 +192,8 @@ public StsTokenExchangeRequest build() {
scopes,
resource,
audience,
requestedTokenType);
requestedTokenType,
internalOptions);
}
}
}