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 all 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 @@ -280,7 +280,7 @@ public static ExternalAccountCredentials fromStream(
parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
try {
return fromJson(fileContents, transportFactory);
} catch (ClassCastException e) {
} catch (ClassCastException | IllegalArgumentException e) {
throw new CredentialFormatException("An invalid input stream was provided.", e);
}
}
Expand All @@ -300,15 +300,16 @@ static ExternalAccountCredentials fromJson(
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,20 @@ 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);

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 +364,7 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
StsRequestHandler requestHandler =
StsRequestHandler.newBuilder(
tokenUrl, stsTokenExchangeRequest, transportFactory.create().createRequestFactory())
.setInternalOptions(stsTokenExchangeRequest.getInternalOptions())
.build();

StsTokenExchangeResponse response = requestHandler.exchangeToken();
Expand Down
121 changes: 63 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 All @@ -54,6 +53,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -155,39 +155,37 @@ 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;

if (workforcePoolUserProject != null && !isWorkforcePoolConfiguration()) {
throw new IllegalArgumentException(
"The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.");
}
}

@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 +200,15 @@ public AccessToken refreshAccessToken() throws IOException {
stsTokenExchangeRequest.setScopes(new ArrayList<>(scopes));
}

// If this credential was initialized with a Workforce configuration then the
// workforcePoolUserProject must passed to STS via the the internal options param.
if (isWorkforcePoolConfiguration()) {
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 @@ -269,22 +276,24 @@ private String getSubjectTokenFromMetadataServer() throws IOException {
}
}

/**
* Returns whether or not the current configuration is for Workforce Pools (which enable 3p user
* identities, rather than workloads).
lsirac marked this conversation as resolved.
Show resolved Hide resolved
*/
public boolean isWorkforcePoolConfiguration() {
lsirac marked this conversation as resolved.
Show resolved Hide resolved
Pattern workforceAudiencePattern =
Pattern.compile(
"^//iam.googleapis.com/projects/.+/locations/.+/workforcePools/.+/providers/.+$");
return workforcePoolUserProject != null
lsirac marked this conversation as resolved.
Show resolved Hide resolved
&& !workforcePoolUserProject.isEmpty()
&& workforceAudiencePattern.matcher(getAudience()).matches();
}

/** Clones the IdentityPoolCredentials with the specified scopes. */
@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 +306,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);
}
}
}