Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ private void init() throws IOException {
this.signForApp = serviceClass.getMethod(SIGN_FOR_APP_METHOD, byte[].class);
Class<?> signingResultClass = forName(SIGNING_RESULT_CLASS);
this.getSignature = signingResultClass.getMethod(GET_SIGNATURE_METHOD);
this.name = GoogleCredentialsInfo.APP_ENGINE_CREDENTIALS.getCredentialName();
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static CloudShellCredentials create(int authPort) {
private CloudShellCredentials(Builder builder) {
super(builder);
this.authPort = builder.getAuthPort();
this.name = GoogleCredentialsInfo.CLOUD_SHELL_CREDENTIALS.getCredentialName();
}

protected int getAuthPort() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,26 @@ public class ComputeEngineCredentials extends GoogleCredentials
static final int MAX_COMPUTE_PING_TRIES = 3;
static final int COMPUTE_PING_CONNECTION_TIMEOUT_MS = 500;

private static final String METADATA_FLAVOR = "Metadata-Flavor";
private static final String GOOGLE = "Google";
private static final String WINDOWS = "windows";
private static final String LINUX = "linux";

private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
private static final String PARSE_ERROR_ACCOUNT = "Error parsing service account response. ";
private static final long serialVersionUID = -4113476462526554235L;

private final String transportFactoryClassName;

private final Collection<String> scopes;

private final GoogleAuthTransport transport;
private final BindingEnforcement bindingEnforcement;

private transient HttpTransportFactory transportFactory;

private String universeDomainFromMetadata = null;

/**
* Experimental Feature.
*
Expand Down Expand Up @@ -172,27 +192,6 @@ public String getLabel() {
}
}

private static final String METADATA_FLAVOR = "Metadata-Flavor";
private static final String GOOGLE = "Google";
private static final String WINDOWS = "windows";
private static final String LINUX = "linux";

private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
private static final String PARSE_ERROR_ACCOUNT = "Error parsing service account response. ";
private static final long serialVersionUID = -4113476462526554235L;

private final String transportFactoryClassName;

private final Collection<String> scopes;

private final GoogleAuthTransport transport;
private final BindingEnforcement bindingEnforcement;

private transient HttpTransportFactory transportFactory;
private transient String serviceAccountEmail;

private String universeDomainFromMetadata = null;

/**
* An internal constructor
*
Expand Down Expand Up @@ -220,6 +219,7 @@ private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
}
this.transport = builder.getGoogleAuthTransport();
this.bindingEnforcement = builder.getBindingEnforcement();
this.name = GoogleCredentialsInfo.COMPUTE_ENGINE_CREDENTIALS.getCredentialName();
}

@Override
Expand Down Expand Up @@ -344,6 +344,11 @@ private String getUniverseDomainFromMetadata() throws IOException {
/** Refresh the access token by getting it from the GCE metadata server */
@Override
public AccessToken refreshAccessToken() throws IOException {
// Retrieve the default service account email prior to retrieving the access token
if (principal == null) {
principal = getDefaultServiceAccount();
}

HttpResponse response =
getMetadataResponse(createTokenUrlWithScopes(), RequestType.ACCESS_TOKEN_REQUEST, true);
int statusCode = response.getStatusCode();
Expand Down Expand Up @@ -688,14 +693,14 @@ public static Builder newBuilder() {
@Override
// todo(#314) getAccount should not throw a RuntimeException
public String getAccount() {
if (serviceAccountEmail == null) {
if (principal == null) {
try {
serviceAccountEmail = getDefaultServiceAccount();
principal = getDefaultServiceAccount();
} catch (IOException ex) {
throw new RuntimeException("Failed to get service account", ex);
}
}
return serviceAccountEmail;
return principal;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(
throw new IOException("File does not exist.");
}
credentialsStream = readStream(credentialsFile);
credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory);
credentials =
GoogleCredentials.fromStream(credentialsStream, transportFactory)
.withSource(
String.format("Env Var %s set to %s", CREDENTIAL_ENV_VAR, credentialsPath));
} catch (IOException e) {
// Although it is also the cause, the message of the caught exception can have very
// important information for diagnosing errors, so include its message in the
Expand Down Expand Up @@ -176,7 +179,11 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(
"Attempting to load credentials from well known file: %s",
wellKnownFileLocation.getCanonicalPath()));
credentialsStream = readStream(wellKnownFileLocation);
credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory);
credentials =
GoogleCredentials.fromStream(credentialsStream, transportFactory)
.withSource(
String.format(
"Well Known File at %s", wellKnownFileLocation.getCanonicalPath()));
}
} catch (IOException e) {
throw new IOException(
Expand Down Expand Up @@ -210,6 +217,15 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(
if (credentials == null) {
LOGGER.log(Level.FINE, "Attempting to load credentials from GCE");
credentials = tryGetComputeCredentials(transportFactory);
// tryGetComputeCredentials can return a null value. This check won't set the source
// if the ComputeEngineCredentials is unable to be created
if (credentials != null) {
credentials =
credentials.withSource(
String.format(
"Metadata Server URL set to %s",
ComputeEngineCredentials.getMetadataServerUrl(this)));
}
}

if (credentials != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials

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;
Expand Down Expand Up @@ -117,6 +114,9 @@ private ExternalAccountAuthorizedUserCredentials(Builder builder) {
this.clientId = builder.clientId;
this.clientSecret = builder.clientSecret;

this.name =
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getCredentialName();

Preconditions.checkState(
getAccessToken() != null || canRefresh(),
"ExternalAccountAuthorizedUserCredentials must be initialized with "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials {
private static final String CLOUD_PLATFORM_SCOPE =
"https://www.googleapis.com/auth/cloud-platform";

static final String EXTERNAL_ACCOUNT_FILE_TYPE = "external_account";
static final String EXECUTABLE_SOURCE_KEY = "executable";

static final String DEFAULT_TOKEN_URL = "https://sts.{UNIVERSE_DOMAIN}/v1/token";
Expand Down Expand Up @@ -214,6 +213,7 @@ protected ExternalAccountCredentials(
}

this.metricsHandler = new ExternalAccountMetricsHandler(this);
this.name = GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getCredentialName();
}

/**
Expand Down Expand Up @@ -271,6 +271,8 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
builder.metricsHandler == null
? new ExternalAccountMetricsHandler(this)
: builder.metricsHandler;

this.name = GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getCredentialName();
}

ImpersonatedCredentials buildImpersonatedCredentials() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public class GdchCredentials extends GoogleCredentials {
this.caCertPath = builder.caCertPath;
this.apiAudience = builder.apiAudience;
this.lifetime = builder.lifetime;
this.name = GoogleCredentialsInfo.GDCH_CREDENTIALS.getCredentialName();
}

/**
Expand Down
122 changes: 106 additions & 16 deletions oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -60,9 +62,46 @@ public class GoogleCredentials extends OAuth2Credentials implements QuotaProject
private static final long serialVersionUID = -1522852442442473691L;

static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project";
static final String USER_FILE_TYPE = "authorized_user";
static final String SERVICE_ACCOUNT_FILE_TYPE = "service_account";
static final String GDCH_SERVICE_ACCOUNT_FILE_TYPE = "gdch_service_account";

/** Internal Enum info mapping for GoogleCredential subclasses */
enum GoogleCredentialsInfo {
USER_CREDENTIALS("User Credentials", "authorized_user"),
SERVICE_ACCOUNT_CREDENTIALS("Service Account Credentials", "service_account"),
GDCH_CREDENTIALS("GDCH Credentials", "gdch_service_account"),
EXTERNAL_ACCOUNT_CREDENTIALS("External Account Credentials", "external_account"),
EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS(
"External Account Authorized User Credentials", "external_account_authorized_user"),
IMPERSONATED_CREDENTIALS("Impersonated Credentials", "impersonated_service_account"),
APP_ENGINE_CREDENTIALS("App Engine Credentials", null),
CLOUD_SHELL_CREDENTIALS("Cloud Shell Credentials", null),
COMPUTE_ENGINE_CREDENTIALS("Compute Engine Credentials", null);

private final String credentialName;
@Nullable private final String fileType;

GoogleCredentialsInfo(String credentialName, String fileType) {
this.credentialName = credentialName;
this.fileType = fileType;
}

String getCredentialName() {
return credentialName;
}

@Nullable
String getFileType() {
return fileType;
}
}

// The following package-private fields to provide additional info for errors message
// Source of the credential (e.g. env var value or well know file location)
String source;
// User-friendly name of the Credential class
String name;
// Identity of the credential
// Note: This field may contain data such as serviceAccountEmail which should not be serialized
transient String principal;

private final String universeDomain;
private final boolean isExplicitUniverseDomain;
Expand Down Expand Up @@ -207,36 +246,36 @@ public static GoogleCredentials fromStream(
throw new IOException("Error reading credentials from stream, 'type' field not specified.");
}

if (USER_FILE_TYPE.equals(fileType)) {
if (fileType.equals(GoogleCredentialsInfo.USER_CREDENTIALS.getFileType())) {
return UserCredentials.fromJson(fileContents, transportFactory);
}
if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) {
if (fileType.equals(GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType())) {
return ServiceAccountCredentials.fromJson(fileContents, transportFactory);
}
if (GDCH_SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) {
if (fileType.equals(GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType())) {
return GdchCredentials.fromJson(fileContents);
}
if (ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE.equals(fileType)) {
if (fileType.equals(GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType())) {
return ExternalAccountCredentials.fromJson(fileContents, transportFactory);
}
if (ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE.equals(
fileType)) {
if (fileType.equals(
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType())) {
return ExternalAccountAuthorizedUserCredentials.fromJson(fileContents, transportFactory);
}
if (ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE.equals(fileType)) {
if (fileType.equals(GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType())) {
return ImpersonatedCredentials.fromJson(fileContents, transportFactory);
}
throw new IOException(
String.format(
"Error reading credentials from stream, 'type' value '%s' not recognized."
+ " Valid values are '%s', '%s', '%s', '%s', '%s', '%s'.",
fileType,
USER_FILE_TYPE,
SERVICE_ACCOUNT_FILE_TYPE,
GDCH_SERVICE_ACCOUNT_FILE_TYPE,
ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE,
ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE,
ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE));
GoogleCredentialsInfo.USER_CREDENTIALS.getFileType(),
GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType(),
GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType(),
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType(),
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType(),
GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType()));
}

/**
Expand Down Expand Up @@ -359,6 +398,8 @@ protected GoogleCredentials(Builder builder) {
this.universeDomain = builder.getUniverseDomain();
this.isExplicitUniverseDomain = true;
}

this.source = builder.source;
}

/**
Expand Down Expand Up @@ -497,9 +538,53 @@ public GoogleCredentials createDelegated(String user) {
return this;
}

/**
* Internal method meant to help provide information for how certain Credential objects were
* initialized by ADC (e.g. The well-known file location or env var)
*/
GoogleCredentials withSource(String source) {
return toBuilder().setSource(source).build();
}

/**
* Provides additional information regarding credential initialization source
*
* <ul>
* <li>credential source - Initialized via the GOOGLE_APPLICATION_CREDENTIALS env var or well
* known file type
* <li>credential name - The user-friendly name of the credential created
* <li>principal - Identity used for the credential
* </ul>
*
* Unknown field values (i.e. null) are not included in the mapping (e.g. ComputeCredentials may
* not know the principal value until after a call to MDS is made and the field will be excluded
* if `getCredentialInfo` is called prior to retrieving that value). A new map of the fields is
* created on every time this method is called as fields may be updated throughout the Credential
* lifecycle. This mapping is intended to provide information about the Credential that is created
* via ADC. Some fields may not be known if a Credential is directly created (e.g.
* `ServiceAccountCredential.fromStream(InputStream)` may not know the source of the file stream).
* These fields are populated on a best effort basis.
*
* @return ImmutableMap of information regarding how the Credential was initialized
*/
public Map<String, String> getCredentialInfo() {
Map<String, String> infoMap = new HashMap<>();
if (!Strings.isNullOrEmpty(source)) {
infoMap.put("Credential Source", source);
}
if (!Strings.isNullOrEmpty(name)) {
infoMap.put("Credential Name", name);
}
if (!Strings.isNullOrEmpty(principal)) {
infoMap.put("Principal", principal);
}
return ImmutableMap.copyOf(infoMap);
}

public static class Builder extends OAuth2Credentials.Builder {
@Nullable protected String quotaProjectId;
@Nullable protected String universeDomain;
@Nullable String source;

protected Builder() {}

Expand Down Expand Up @@ -541,6 +626,11 @@ public String getUniverseDomain() {
return this.universeDomain;
}

Builder setSource(String source) {
this.source = source;
return this;
}

@Override
@CanIgnoreReturnValue
public Builder setAccessToken(AccessToken token) {
Expand Down
Loading
Loading