diff --git a/google-auth-library-java/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java b/google-auth-library-java/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java index 05f81d57825f..3830d62dda20 100644 --- a/google-auth-library-java/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java +++ b/google-auth-library-java/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java @@ -36,8 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.auth.Credentials; import com.google.auth.oauth2.AccessToken; @@ -135,11 +135,7 @@ void createScoped_clonesWithScopes() throws IOException { .setAppIdentityService(appIdentity) .build(); assertTrue(credentials.createScopedRequired()); - try { - credentials.getRequestMetadata(CALL_URI); - fail("Should not be able to use credential without scopes."); - } catch (Exception expected) { - } + assertThrows(IOException.class, () -> credentials.getRequestMetadata(CALL_URI)); assertEquals(0, appIdentity.getGetAccessTokenCallCount()); GoogleCredentials scopedCredentials = credentials.createScoped(SCOPES); diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java b/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java new file mode 100644 index 000000000000..0d34cf271986 --- /dev/null +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/MtlsUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright 2026 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.mtls; + +import com.google.api.core.InternalApi; +import com.google.auth.oauth2.EnvironmentProvider; +import com.google.auth.oauth2.PropertyProvider; +import com.google.common.base.Strings; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +/** + * Utility class for mTLS related operations. + * + *

For internal use only. + */ +@InternalApi +public class MtlsUtils { + static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; + static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; + static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; + + private MtlsUtils() { + // Prevent instantiation for Utility class + } + + /** + * Returns the path to the client certificate file specified by the loaded workload certificate + * configuration. + * + * @return The path to the certificate file. + * @throws IOException if the certificate configuration cannot be found or loaded. + */ + public static String getCertificatePath( + EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride) + throws IOException { + String certPath = + getWorkloadCertificateConfiguration(envProvider, propProvider, certConfigPathOverride) + .getCertPath(); + if (Strings.isNullOrEmpty(certPath)) { + throw new CertificateSourceUnavailableException( + "Certificate configuration loaded successfully, but does not contain a 'certificate_file' path."); + } + return certPath; + } + + /** + * Resolves and loads the workload certificate configuration. + * + *

The configuration file is resolved in the following order of precedence: 1. The provided + * certConfigPathOverride (if not null). 2. The path specified by the + * GOOGLE_API_CERTIFICATE_CONFIG environment variable. 3. The well-known certificate configuration + * file in the gcloud config directory. + * + * @param envProvider the environment provider to use for resolving environment variables + * @param propProvider the property provider to use for resolving system properties + * @param certConfigPathOverride optional override path for the configuration file + * @return the loaded WorkloadCertificateConfiguration + * @throws IOException if the configuration file cannot be found, read, or parsed + */ + static WorkloadCertificateConfiguration getWorkloadCertificateConfiguration( + EnvironmentProvider envProvider, PropertyProvider propProvider, String certConfigPathOverride) + throws IOException { + File certConfig; + if (certConfigPathOverride != null) { + certConfig = new File(certConfigPathOverride); + } else { + String envCredentialsPath = envProvider.getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); + if (!Strings.isNullOrEmpty(envCredentialsPath)) { + certConfig = new File(envCredentialsPath); + } else { + certConfig = getWellKnownCertificateConfigFile(envProvider, propProvider); + } + } + + if (!certConfig.isFile()) { + throw new CertificateSourceUnavailableException( + "Certificate configuration file does not exist or is not a file: " + + certConfig.getAbsolutePath()); + } + try (InputStream certConfigStream = new FileInputStream(certConfig)) { + return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); + } + } + + private static File getWellKnownCertificateConfigFile( + EnvironmentProvider envProvider, PropertyProvider propProvider) throws IOException { + File cloudConfigPath; + String envPath = envProvider.getEnv("CLOUDSDK_CONFIG"); + if (envPath != null) { + cloudConfigPath = new File(envPath); + } else { + String osName = propProvider.getProperty("os.name", "").toLowerCase(Locale.US); + if (osName.indexOf("windows") >= 0) { + String appData = envProvider.getEnv("APPDATA"); + if (Strings.isNullOrEmpty(appData)) { + throw new CertificateSourceUnavailableException( + "APPDATA environment variable is not set on Windows."); + } + File appDataPath = new File(appData); + cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); + } else { + File configPath = new File(propProvider.getProperty("user.home", ""), ".config"); + cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); + } + } + return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); + } +} diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/X509Provider.java index 7ff490f0f147..4127b1492460 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/X509Provider.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -31,15 +31,17 @@ package com.google.auth.mtls; import com.google.api.client.util.SecurityUtils; -import com.google.common.base.Strings; +import com.google.api.core.InternalApi; +import com.google.auth.oauth2.EnvironmentProvider; +import com.google.auth.oauth2.PropertyProvider; +import com.google.auth.oauth2.SystemEnvironmentProvider; +import com.google.auth.oauth2.SystemPropertyProvider; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.security.KeyStore; -import java.util.Locale; /** * This class implements {@link MtlsProvider} for the Google Auth library transport layer via {@link @@ -47,11 +49,10 @@ * libraries, and the public facing methods may be changed without notice, and have no guarantee of * backwards compatibility. */ +@InternalApi public class X509Provider implements MtlsProvider { - static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; - static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; - static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; - + private final EnvironmentProvider envProvider; + private final PropertyProvider propProvider; private final String certConfigPathOverride; /** @@ -59,12 +60,32 @@ public class X509Provider implements MtlsProvider { * normal checks for the well known certificate configuration file path and environment variable. * This is meant for internal Google Cloud usage and behavior may be changed without warning. * + * @param envProvider environment provider used for environment variables + * @param propProvider property provider used for system properties * @param certConfigPathOverride the path to read the certificate configuration from. */ - public X509Provider(String certConfigPathOverride) { + @InternalApi + public X509Provider( + EnvironmentProvider envProvider, + PropertyProvider propProvider, + String certConfigPathOverride) { + this.envProvider = envProvider; + this.propProvider = propProvider; this.certConfigPathOverride = certConfigPathOverride; } + /** + * Creates an X509 provider with an override path for the certificate configuration. + * + * @param certConfigPathOverride the path to read the certificate configuration from. + */ + public X509Provider(String certConfigPathOverride) { + this( + SystemEnvironmentProvider.getInstance(), + SystemPropertyProvider.getInstance(), + certConfigPathOverride); + } + /** * Creates a new X.509 provider that will check the environment variable path and the well known * Gcloud certificate configuration location. This is meant for internal Google Cloud usage and @@ -74,29 +95,6 @@ public X509Provider() { this(null); } - /** - * Returns the path to the client certificate file specified by the loaded workload certificate - * configuration. - * - *

If the configuration has not been loaded yet (e.g., if {@link #getKeyStore()} has not been - * called), this method will attempt to load it first by searching the override path, environment - * variable, and well-known locations. - * - * @return The path to the certificate file. - * @throws IOException if the certificate configuration cannot be found or loaded, or if the - * configuration file does not specify a certificate path. - * @throws CertificateSourceUnavailableException if the configuration file is not found. - */ - public String getCertificatePath() throws IOException { - String certPath = getWorkloadCertificateConfiguration().getCertPath(); - if (Strings.isNullOrEmpty(certPath)) { - // Ensure the loaded configuration actually contains the required path. - throw new CertificateSourceUnavailableException( - "Certificate configuration loaded successfully, but does not contain a 'certificate_file' path."); - } - return certPath; - } - /** * Finds the certificate configuration file, then builds a Keystore using the X.509 certificate * and private key pointed to by the configuration. This will check the following locations in @@ -115,12 +113,14 @@ public String getCertificatePath() throws IOException { */ @Override public KeyStore getKeyStore() throws CertificateSourceUnavailableException, IOException { - WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration(); + WorkloadCertificateConfiguration workloadCertConfig = + MtlsUtils.getWorkloadCertificateConfiguration( + envProvider, propProvider, certConfigPathOverride); // Read the certificate and private key file paths into streams. - try (InputStream certStream = createInputStream(new File(workloadCertConfig.getCertPath())); + try (InputStream certStream = new FileInputStream(new File(workloadCertConfig.getCertPath())); InputStream privateKeyStream = - createInputStream(new File(workloadCertConfig.getPrivateKeyPath())); + new FileInputStream(new File(workloadCertConfig.getPrivateKeyPath())); SequenceInputStream certAndPrivateKeyStream = new SequenceInputStream(certStream, privateKeyStream)) { @@ -149,74 +149,4 @@ public boolean isAvailable() throws IOException { } return true; } - - private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration() - throws IOException { - File certConfig; - if (this.certConfigPathOverride != null) { - certConfig = new File(certConfigPathOverride); - } else { - String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); - if (!Strings.isNullOrEmpty(envCredentialsPath)) { - certConfig = new File(envCredentialsPath); - } else { - certConfig = getWellKnownCertificateConfigFile(); - } - } - InputStream certConfigStream = null; - try { - if (!isFile(certConfig)) { - // Path will be put in the message from the catch block below - throw new CertificateSourceUnavailableException("File does not exist."); - } - certConfigStream = createInputStream(certConfig); - return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); - } finally { - if (certConfigStream != null) { - certConfigStream.close(); - } - } - } - - /* - * Start of methods to allow overriding in the test code to isolate from the environment. - */ - boolean isFile(File file) { - return file.isFile(); - } - - InputStream createInputStream(File file) throws FileNotFoundException { - return new FileInputStream(file); - } - - String getEnv(String name) { - return System.getenv(name); - } - - String getOsName() { - return getProperty("os.name", "").toLowerCase(Locale.US); - } - - String getProperty(String property, String def) { - return System.getProperty(property, def); - } - - /* - * End of methods to allow overriding in the test code to isolate from the environment. - */ - - private File getWellKnownCertificateConfigFile() { - File cloudConfigPath; - String envPath = getEnv("CLOUDSDK_CONFIG"); - if (envPath != null) { - cloudConfigPath = new File(envPath); - } else if (getOsName().indexOf("windows") >= 0) { - File appDataPath = new File(getEnv("APPDATA")); - cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); - } else { - File configPath = new File(getProperty("user.home", ""), ".config"); - cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); - } - return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); - } } diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java index 3085765317b7..bb943fcdf3b2 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @@ -196,11 +196,6 @@ String getRegionalCredentialVerificationUrl() { return this.regionalCredentialVerificationUrl; } - @VisibleForTesting - String getEnv(String name) { - return System.getenv(name); - } - @VisibleForTesting AwsSecurityCredentialsSupplier getAwsSecurityCredentialsSupplier() { return this.awsSecurityCredentialsSupplier; diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java index 5c77ecc65764..26373f4cd729 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @@ -1,6 +1,43 @@ +/* + * Copyright 2026 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; -/** Interface for an environment provider. */ -interface EnvironmentProvider { +import com.google.api.core.InternalApi; + +/** + * Interface for an environment provider. + * + *

For internal use only. + */ +@InternalApi +public interface EnvironmentProvider { String getEnv(String name); } diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 7f9f0c20774b..e92c64bed90e 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -97,6 +97,7 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials { @Nullable protected ImpersonatedCredentials impersonatedCredentials; private EnvironmentProvider environmentProvider; + private PropertyProvider propertyProvider; private int connectTimeout; private int readTimeout; @@ -205,6 +206,7 @@ protected ExternalAccountCredentials( : scopes; this.environmentProvider = environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider; + this.propertyProvider = SystemPropertyProvider.getInstance(); this.workforcePoolUserProject = null; this.serviceAccountImpersonationOptions = new ServiceAccountImpersonationOptions(new HashMap()); @@ -253,6 +255,10 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder) builder.environmentProvider == null ? SystemEnvironmentProvider.getInstance() : builder.environmentProvider; + this.propertyProvider = + builder.propertyProvider == null + ? SystemPropertyProvider.getInstance() + : builder.propertyProvider; this.serviceAccountImpersonationOptions = builder.serviceAccountImpersonationOptions == null ? new ServiceAccountImpersonationOptions(new HashMap()) @@ -657,6 +663,10 @@ EnvironmentProvider getEnvironmentProvider() { return environmentProvider; } + PropertyProvider getPropertyProvider() { + return propertyProvider; + } + /** * @return whether the current configuration is for Workforce Pools (which enable 3p user * identities, rather than workloads) @@ -772,6 +782,7 @@ public abstract static class Builder extends GoogleCredentials.Builder { protected String tokenInfoUrl; protected CredentialSource credentialSource; protected EnvironmentProvider environmentProvider; + protected PropertyProvider propertyProvider; protected HttpTransportFactory transportFactory; @Nullable protected String serviceAccountImpersonationUrl; @@ -806,6 +817,7 @@ protected Builder(ExternalAccountCredentials credentials) { this.clientSecret = credentials.clientSecret; this.scopes = credentials.scopes; this.environmentProvider = credentials.environmentProvider; + this.propertyProvider = credentials.propertyProvider; this.workforcePoolUserProject = credentials.workforcePoolUserProject; this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions; this.metricsHandler = credentials.metricsHandler; @@ -1029,6 +1041,18 @@ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) { return this; } + /** + * Sets the optional Property Provider. + * + * @param propertyProvider the {@code PropertyProvider} to set + * @return this {@code Builder} object + */ + @CanIgnoreReturnValue + Builder setPropertyProvider(PropertyProvider propertyProvider) { + this.propertyProvider = propertyProvider; + return this; + } + @Override public abstract ExternalAccountCredentials build(); } diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java index a71a8c39029e..e8b81c71c35d 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java @@ -37,18 +37,25 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebSignature; import com.google.api.client.json.webtoken.JsonWebToken; +import com.google.api.client.util.Clock; import com.google.api.client.util.GenericData; +import com.google.api.client.util.PemReader; +import com.google.api.client.util.SecurityUtils; +import com.google.api.client.util.StringUtils; +import com.google.api.core.ObsoleteApi; import com.google.auth.http.HttpTransportFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.File; import java.io.FileInputStream; @@ -56,10 +63,19 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; +import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.util.Base64; import java.util.Date; import java.util.Map; import java.util.Objects; @@ -67,8 +83,23 @@ public class GdchCredentials extends GoogleCredentials { private static final LoggerProvider LOGGER_PROVIDER = LoggerProvider.forClazz(GdchCredentials.class); - static final String SUPPORTED_FORMAT_VERSION = "1"; private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; + + /** + * The expected format version for GDCH credential profiles. Version "1" indicates the initial and + * currently supported JSON format for these credentials. See go/gdch-python-auth-lib for more + * info. + */ + @VisibleForTesting static final String SUPPORTED_JSON_FORMAT_VERSION = "1"; + + // Custom URN used by GDCH to identify service account tokens in token exchange requests. + // See go/gdch-python-auth-lib for more information. + private static final String SERVICE_ACCOUNT_TOKEN_TYPE = + "urn:k8s:params:oauth:token-type:serviceaccount"; + + private static final String PRIVATE_KEY_PEM_TITLE = "PRIVATE KEY"; + private static final String EC_PRIVATE_KEY_PEM_TITLE = "EC PRIVATE KEY"; + private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; private final PrivateKey privateKey; @@ -76,7 +107,7 @@ public class GdchCredentials extends GoogleCredentials { private final String projectId; private final String serviceIdentityName; private final URI tokenServerUri; - private final URI apiAudience; + private final String apiAudience; private final int lifetime; private final String transportFactoryClassName; private final String caCertPath; @@ -181,15 +212,15 @@ static GdchCredentials fromJson(Map json, HttpTransportFactory t String formatVersion = validateField((String) json.get("format_version"), "format_version"); String projectId = validateField((String) json.get("project"), "project"); String privateKeyId = validateField((String) json.get("private_key_id"), "private_key_id"); - String privateKeyPkcs8 = validateField((String) json.get("private_key"), "private_key"); + String privateKeyPem = validateField((String) json.get("private_key"), "private_key"); String serviceIdentityName = validateField((String) json.get("name"), "name"); String tokenServerUriStringFromCreds = validateField((String) json.get("token_uri"), "token_uri"); String caCertPath = (String) json.get("ca_cert_path"); - if (!SUPPORTED_FORMAT_VERSION.equals(formatVersion)) { + if (!SUPPORTED_JSON_FORMAT_VERSION.equals(formatVersion)) { throw new IOException( - String.format("Only format version %s is supported.", SUPPORTED_FORMAT_VERSION)); + String.format("Only format version %s is supported.", SUPPORTED_JSON_FORMAT_VERSION)); } URI tokenServerUriFromCreds = null; @@ -208,32 +239,71 @@ static GdchCredentials fromJson(Map json, HttpTransportFactory t .setCaCertPath(caCertPath) .setHttpTransportFactory(transportFactory); - return fromPkcs8(privateKeyPkcs8, builder); + return fromPem(privateKeyPem, builder); } /** - * Internal constructor. + * Reads a private key from a PEM encoded string, supporting both PKCS#8 and SEC1 formats. * - * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format. + *

If the key is labeled with "PRIVATE KEY", it is parsed as PKCS#8 as per RFC 7468 Section 10. + * If it is labeled with "EC PRIVATE KEY", it is parsed as SEC1 as per RFC 5915 Section 3. + * + * @see RFC 7468 Section 10 + * @see RFC 5915 Section 3 + * @param privateKeyPem EC private key object for the service account in PEM format (PKCS#8 or + * SEC1). * @param builder A builder for GdchCredentials. * @return an instance of GdchCredentials. */ - static GdchCredentials fromPkcs8(String privateKeyPkcs8, GdchCredentials.Builder builder) + static GdchCredentials fromPem(String privateKeyPem, GdchCredentials.Builder builder) throws IOException { - PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8); + Reader reader = new StringReader(privateKeyPem); + // Read the first section regardless of title + PemReader.Section section = PemReader.readFirstSectionAndClose(reader); + + if (section == null) { + throw new GoogleAuthException(false, 0, "Invalid key data: no PEM section found.", null); + } + + String title = section.getTitle(); + PrivateKey privateKey; + + if (PRIVATE_KEY_PEM_TITLE.equals(title)) { + privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPem, OAuth2Utils.Pkcs8Algorithm.EC); + } else if (EC_PRIVATE_KEY_PEM_TITLE.equals(title)) { + privateKey = privateKeyFromSec1(section.getBase64DecodedBytes()); + } else { + throw new GoogleAuthException(false, 0, "Unsupported key type: " + title, null); + } + builder.setPrivateKey(privateKey); return new GdchCredentials(builder); } /** - * Create a copy of GDCH credentials with the specified audience. + * This method is obsolete. Please use {@link #createWithGdchAudience(String)}} instead. Create a + * copy of GDCH credentials with the specified audience. * * @param apiAudience The intended audience for GDCH credentials. */ - public GdchCredentials createWithGdchAudience(URI apiAudience) throws IOException { + @ObsoleteApi("Use createWithGdchAudience(String) instead.") + public GdchCredentials createWithGdchAudience(URI apiAudience) { Preconditions.checkNotNull( apiAudience, "Audience are not configured for GDCH service account credentials."); + return this.toBuilder().setGdchAudience(apiAudience.toString()).build(); + } + + /** + * Create a copy of GDCH credentials with the specified audience. + * + * @param apiAudience The intended audience for GDCH credentials. + */ + public GdchCredentials createWithGdchAudience(String apiAudience) { + if (Strings.isNullOrEmpty(apiAudience)) { + throw new IllegalArgumentException( + "Audience cannot be null or empty for GDCH service account credentials."); + } return this.toBuilder().setGdchAudience(apiAudience).build(); } @@ -248,17 +318,22 @@ public GdchCredentials createWithGdchAudience(URI apiAudience) throws IOExceptio public AccessToken refreshAccessToken() throws IOException { Preconditions.checkNotNull( this.apiAudience, - "Audience are not configured for GDCH service account. Specify the " - + "audience by calling createWithGDCHAudience."); + "Audience cannot be null or empty for GDCH service account credentials. " + + "Specify the audience by calling createWithGdchAudience."); - JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; - long currentTime = clock.currentTimeMillis(); - String assertion = createAssertion(jsonFactory, currentTime, getApiAudience()); + JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); + + long currentTime = Clock.SYSTEM.currentTimeMillis(); + String assertion = createAssertion(jsonFactory, currentTime); GenericData tokenRequest = new GenericData(); + tokenRequest.set("audience", apiAudience); tokenRequest.set("grant_type", OAuth2Utils.TOKEN_TYPE_TOKEN_EXCHANGE); - tokenRequest.set("assertion", assertion); - UrlEncodedContent content = new UrlEncodedContent(tokenRequest); + tokenRequest.set("requested_token_type", OAuth2Utils.TOKEN_TYPE_ACCESS_TOKEN); + tokenRequest.set("subject_token", assertion); + tokenRequest.set("subject_token_type", SERVICE_ACCOUNT_TOKEN_TYPE); + + JsonHttpContent content = new JsonHttpContent(jsonFactory, tokenRequest); HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content); @@ -280,8 +355,8 @@ public AccessToken refreshAccessToken() throws IOException { String message = String.format(errorTemplate, re.getMessage(), getServiceIdentityName()); throw GoogleAuthException.createWithTokenEndpointResponseException(re, message); } catch (IOException e) { - throw GoogleAuthException.createWithTokenEndpointIOException( - e, String.format(errorTemplate, e.getMessage(), getServiceIdentityName())); + String message = String.format(errorTemplate, e.getMessage(), getServiceIdentityName()); + throw GoogleAuthException.createWithTokenEndpointIOException(e, message); } GenericData responseData = response.parseAs(GenericData.class); @@ -302,10 +377,9 @@ public AccessToken refreshAccessToken() throws IOException { * (tokenServerUri), not for API call. It uses the serviceIdentityName as the `iss` and `sub` * claim, and the tokenServerUri as the `aud` claim. The JWT is signed with the privateKey. */ - String createAssertion(JsonFactory jsonFactory, long currentTime, URI apiAudience) - throws IOException { + String createAssertion(JsonFactory jsonFactory, long currentTime) throws IOException { JsonWebSignature.Header header = new JsonWebSignature.Header(); - header.setAlgorithm("RS256"); + header.setAlgorithm("ES256"); header.setType("JWT"); header.setKeyId(privateKeyId); @@ -314,15 +388,14 @@ String createAssertion(JsonFactory jsonFactory, long currentTime, URI apiAudienc payload.setSubject(getIssuerSubjectValue(projectId, serviceIdentityName)); payload.setIssuedAtTimeSeconds(currentTime / 1000); payload.setExpirationTimeSeconds(currentTime / 1000 + this.lifetime); - payload.setAudience(getTokenServerUri().toString()); + payload.setAudience(tokenServerUri.toString()); String assertion; try { - payload.set("api_audience", apiAudience.toString()); - assertion = JsonWebSignature.signUsingRsaSha256(privateKey, jsonFactory, header, payload); + assertion = signUsingEsSha256(privateKey, jsonFactory, header, payload); } catch (GeneralSecurityException e) { - throw new IOException( - "Error signing service account access token request with private key.", e); + throw new GoogleAuthException( + false, 0, "Error signing service account access token request with private key.", e); } return assertion; @@ -363,10 +436,35 @@ public final URI getTokenServerUri() { return tokenServerUri; } - public final URI getApiAudience() { + /** + * Returns the underlying audience string set for this credentials object. + * + * @return the audience string, or null if no audience has been set. + */ + public final String getGdchAudience() { return apiAudience; } + /** + * NOTE: This method is obsolete, please use {@link #getGdchAudience()} instead. Returns a URI + * representation of the underlying audience string set for this credentials object. This method + * may fail if the underlying audience string does not conform to a URI format. + * + * @return a URI object representing the audience of the credentials, or null if no audience has + * been set or if the audience string is not a valid URI. + */ + @ObsoleteApi("Use getGdchAudience() instead.") + public final URI getApiAudience() { + if (Strings.isNullOrEmpty(apiAudience)) { + return null; + } + try { + return new URI(apiAudience); + } catch (URISyntaxException e) { + return null; + } + } + public final HttpTransportFactory getTransportFactory() { return transportFactory; } @@ -446,7 +544,7 @@ public static class Builder extends GoogleCredentials.Builder { private PrivateKey privateKey; private String serviceIdentityName; private URI tokenServerUri; - private URI apiAudience; + private String apiAudience; private HttpTransportFactory transportFactory; private String caCertPath; private int lifetime = DEFAULT_LIFETIME_IN_SECONDS; @@ -506,8 +604,19 @@ public Builder setCaCertPath(String caCertPath) { return this; } + /** + * Sets the intended audience for GDCH credentials. + * + * @param apiAudience The audience string. Cannot be null or empty. + * @return this builder. + * @throws IllegalArgumentException if the audience is null or empty. + */ @CanIgnoreReturnValue - public Builder setGdchAudience(URI apiAudience) { + public Builder setGdchAudience(String apiAudience) { + if (Strings.isNullOrEmpty(apiAudience)) { + throw new IllegalArgumentException( + "Audience cannot be null or empty for GDCH service account credentials."); + } this.apiAudience = apiAudience; return this; } @@ -563,13 +672,16 @@ private static String validateField(String field, String fieldName) throws IOExc /* * Internal HttpTransportFactory for GDCH credentials. * - *

GDCH authentication server could use a self-signed certificate, thus the client could + *

GDCH authentication server could use a self-signed certificate, thus the + * client could * provide the CA certificate path through the `ca_cert_path` in GDCH JSON file. * - *

The TransportFactoryForGdch subclass would read the certificate and create a trust store, + *

The TransportFactoryForGdch subclass would read the certificate and + * create a trust store, * then use the trust store to create a transport. * - *

If the GDCH authentication server uses well known CA certificate, then a regular transport + *

If the GDCH authentication server uses well known CA certificate, then a + * regular transport * would be set. */ static class TransportFactoryForGdch implements HttpTransportFactory { @@ -604,4 +716,231 @@ private void setTransport(String caCertPath) throws IOException { } } } + + /** + * Signs the JWS header and payload using the ES256 algorithm (ECDSA with SHA-256). + * + *

The ES256 algorithm is defined in RFC 7518 Section 3.4. This method + * follows the JWS Compact Serialization format described in RFC 7515 Section 3.1. + * + *

Unlike RSA signatures, ECDSA signatures produced by the Java Cryptography Architecture (JCA) + * are DER-encoded. This method transcodes the DER-encoded signature into the concatenated R|S + * format required by the JWS standard, as specified in RFC 7515 Appendix A.3. + * + * @param privateKey The Elliptic Curve private key used for signing. + * @param jsonFactory The JSON factory to serialize header and payload. + * @param header The JWS header (e.g., containing "alg": "ES256"). + * @param payload The JWS payload containing claims like "iss", "sub", and "aud". + * @return A complete, signed JWS string in the format {@code [header].[payload].[signature]}. + * @throws GeneralSecurityException If signing fails due to cryptographic errors. + * @throws IOException If serialization or transcoding fails. + */ + @VisibleForTesting + static String signUsingEsSha256( + PrivateKey privateKey, + JsonFactory jsonFactory, + JsonWebSignature.Header header, + JsonWebToken.Payload payload) + throws GeneralSecurityException, GoogleAuthException { + + try { + // 1. Construct the JWS Signing Input: Base64URL(UTF8(Header)) + '.' + + // Base64URL(UTF8(Payload)) + String content = + Base64.getUrlEncoder().withoutPadding().encodeToString(jsonFactory.toByteArray(header)) + + "." + + Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(jsonFactory.toByteArray(payload)); + byte[] contentBytes = StringUtils.getBytesUtf8(content); + + // 2. Create the digital signature using SHA256withECDSA. + byte[] signature = + SecurityUtils.sign(SecurityUtils.getEs256SignatureAlgorithm(), privateKey, contentBytes); + + // 3. Transcode the signature from DER to Concatenated R|S. + byte[] jwsSignature = transcodeDerToConcat(signature, 64); + + // 4. Return final JWS: [Signing Input] + '.' + Base64URL(Signature) + return content + "." + Base64.getUrlEncoder().withoutPadding().encodeToString(jwsSignature); + } catch (IOException e) { + throw new GoogleAuthException(false, 0, "Error serializing or transcoding JWT.", e); + } + } + + /** + * Transcodes a DER-encoded ECDSA signature into the concatenated R|S format. + * + *

DER format (ASN.1): {@code SEQUENCE { r INTEGER, s INTEGER }} + * + *

Concatenated format: {@code r | s} (where {@code |} is concatenation). + * + * @param derSignature The raw bytes of the DER-encoded signature. + * @param outputLength The total expected length of the concatenated signature (64 bytes for + * ES256). + * @return The signature in concatenated R|S format. + * @throws IOException If the DER format is invalid. + */ + @VisibleForTesting + static byte[] transcodeDerToConcat(byte[] derSignature, int outputLength) + throws GoogleAuthException { + // Validate basic ASN.1 DER structure (0x30 = SEQUENCE) + if (derSignature.length < 8 || derSignature[0] != 0x30) { + throw new GoogleAuthException(false, 0, "Invalid DER signature format.", null); + } + + int offset = 2; + int seqLength = derSignature[1] & 0xFF; + // Handle long-form length encoding for the sequence + if (seqLength == 0x81) { + offset = 3; + seqLength = derSignature[2] & 0xFF; + } + + if (derSignature.length - offset != seqLength) { + throw new GoogleAuthException(false, 0, "Invalid DER signature length.", null); + } + + // Parse Integer R (0x02 = INTEGER) + if (derSignature[offset++] != 0x02) { + throw new GoogleAuthException(false, 0, "Expected INTEGER for R.", null); + } + int rLength = derSignature[offset++]; + // Skip leading zero byte if it exists (DER integers are signed; zero is added to stay positive) + if (derSignature[offset] == 0x00 && rLength > 1 && (derSignature[offset + 1] & 0x80) != 0) { + offset++; + rLength--; + } + byte[] r = new byte[rLength]; + System.arraycopy(derSignature, offset, r, 0, rLength); + offset += rLength; + + // Parse Integer S + if (derSignature[offset++] != 0x02) { + throw new GoogleAuthException(false, 0, "Expected INTEGER for S.", null); + } + int sLength = derSignature[offset++]; + if (derSignature[offset] == 0x00 && sLength > 1 && (derSignature[offset + 1] & 0x80) != 0) { + offset++; + sLength--; + } + byte[] s = new byte[sLength]; + System.arraycopy(derSignature, offset, s, 0, sLength); + + // Concatenate r and s into fixed-length segments (32 bytes each for ES256) + int keySizeBytes = outputLength / 2; + if (r.length > keySizeBytes || s.length > keySizeBytes) { + throw new GoogleAuthException( + false, + 0, + String.format( + "Invalid R or S length. R: %d, S: %d, Expected: %d", + r.length, s.length, keySizeBytes), + null); + } + + byte[] result = new byte[outputLength]; + System.arraycopy(r, 0, result, keySizeBytes - r.length, r.length); + System.arraycopy(s, 0, result, outputLength - s.length, s.length); + + return result; + } + + /** + * Parses an EC private key in SEC1 format using fixed prefix verification. + * + *

This function assumes that standard SEC1 keys for P-256 generated by OpenSSL have a known, + * stable structure of bytes at the beginning. This "fingerprint" allows us to verify the format + * without complete ASN.1 parsing. If the fingerprint matches, we can safely extract the private + * key value using fixed offsets. + * + * @param bytes The raw bytes of the SEC1 key. + * @return The PrivateKey object. + * @throws GoogleAuthException If parsing fails or the key format is unsupported. + */ + private static PrivateKey privateKeyFromSec1(byte[] bytes) throws IOException { + if (!hasStandardSec1P256Prefix(bytes)) { + throw new GoogleAuthException( + false, 0, "Unsupported SEC1 key format: standard prefix not found.", null); + } + BigInteger s = extractPrivateKeyValue(bytes); + return createEcPrivateKey(s); + } + + /** + * Verifies if the bytes start with the standard SEC1 P-256 prefix. + * + *

The prefix is derived from the standard DER encoding of the ECPrivateKey structure defined + * in RFC 5915 Section 3. For P-256 with named curve parameters and public key included, the + * prefix is stable: [0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20] + * + * @see RFC 5915 Section 3 + * @param bytes The raw bytes of the key. + * @return true if the prefix matches. + */ + private static boolean hasStandardSec1P256Prefix(byte[] bytes) { + if (bytes.length < 7) return false; + return bytes[0] == 0x30 // Sequence + && bytes[1] == 0x77 // Length + && bytes[2] == 0x02 // Integer + && bytes[3] == 0x01 // Length + && bytes[4] == 0x01 // Version + && bytes[5] == 0x04 // Octet String + && bytes[6] == 0x20; // Length 32 + } + + /** + * Extracts the private key value 's' from the SEC1 bytes using fixed offset. + * + *

Assumes the prefix has already been verified by {@link #hasStandardSec1P256Prefix(byte[])}. + * + * @param bytes The raw bytes of the key. + * @return The BigInteger value of 's'. + */ + private static BigInteger extractPrivateKeyValue(byte[] bytes) { + // P-256 private key size is 32 bytes as per RFC 5915 Section 3. + byte[] sBytes = new byte[32]; + // Copy 32 bytes starting at offset 7 (after the 7-byte metadata prefix verified by + // hasStandardSec1P256Prefix). + System.arraycopy(bytes, 7, sBytes, 0, 32); + // Use signum 1 to ensure the byte array is interpreted as a positive integer. + return new BigInteger(1, sBytes); + } + + /** + * Creates an EC PrivateKey from the private key value 's' using P-256 parameters. + * + *

Algorithm steps: 1. Get an instance of AlgorithmParameters for "EC". 2. Initialize it with + * secp256r1 curve spec (requirement as per GDCH supported curve). 3. Extract ECParameterSpec from + * parameters. 4. Create ECPrivateKeySpec with the extracted private key value and parameters. 5. + * Generate PrivateKey using KeyFactory. + * + * @param s The private key value. + * @return The PrivateKey object. + * @throws GoogleAuthException If key creation fails. + */ + private static PrivateKey createEcPrivateKey(BigInteger s) throws IOException { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("EC"); + + params.init(new ECGenParameterSpec("secp256r1")); + + ECParameterSpec ecParams = params.getParameterSpec(ECParameterSpec.class); + + ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, ecParams); + + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + + return keyFactory.generatePrivate(keySpec); + } catch (GeneralSecurityException e) { + throw new GoogleAuthException( + false, + 0, + "Failed to create EC Private Key for GDCH. Please ensure the private key data is valid and represents a P-256 private key.", + e); + } + } } diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java index ee5df1466c36..0ff9da853e2d 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java @@ -33,6 +33,7 @@ import com.google.auth.http.HttpTransportFactory; import com.google.auth.mtls.MtlsHttpTransportFactory; +import com.google.auth.mtls.MtlsUtils; import com.google.auth.mtls.X509Provider; import com.google.auth.oauth2.IdentityPoolCredentialSource.IdentityPoolCredentialSourceType; import com.google.common.annotations.VisibleForTesting; @@ -166,28 +167,37 @@ private IdentityPoolSubjectTokenSupplier createCertificateSubjectTokenSupplier( this.transportFactory = new MtlsHttpTransportFactory(mtlsKeyStore); // Initialize the subject token supplier with the certificate path. - credentialSource.setCredentialLocation(x509Provider.getCertificatePath()); + String explicitCertConfigPath = getExplicitCertConfigPath(credentialSource); + credentialSource.setCredentialLocation( + MtlsUtils.getCertificatePath( + getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath)); return new CertificateIdentityPoolSubjectTokenSupplier(credentialSource); } private X509Provider getX509Provider( Builder builder, IdentityPoolCredentialSource credentialSource) { - final IdentityPoolCredentialSource.CertificateConfig certConfig = - credentialSource.getCertificateConfig(); - // Use the provided X509Provider if available, otherwise initialize a default one. X509Provider x509Provider = builder.x509Provider; if (x509Provider == null) { // Determine the certificate path based on the configuration. - String explicitCertConfigPath = - certConfig.useDefaultCertificateConfig() - ? null - : certConfig.getCertificateConfigLocation(); - x509Provider = new X509Provider(explicitCertConfigPath); + String explicitCertConfigPath = getExplicitCertConfigPath(credentialSource); + x509Provider = + new X509Provider(getEnvironmentProvider(), getPropertyProvider(), explicitCertConfigPath); } return x509Provider; } + private static String getExplicitCertConfigPath(IdentityPoolCredentialSource credentialSource) { + IdentityPoolCredentialSource.CertificateConfig certConfig = + credentialSource.getCertificateConfig(); + if (certConfig == null) { + return null; + } + return certConfig.useDefaultCertificateConfig() + ? null + : certConfig.getCertificateConfigLocation(); + } + public static class Builder extends ExternalAccountCredentials.Builder { private IdentityPoolSubjectTokenSupplier subjectTokenSupplier; @@ -212,6 +222,7 @@ public static class Builder extends ExternalAccountCredentials.Builder { * @return this {@code Builder} object */ @CanIgnoreReturnValue + @VisibleForTesting Builder setX509Provider(X509Provider x509Provider) { this.x509Provider = x509Provider; return this; diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java index b4a933963fe8..e17714c3eee8 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java @@ -86,7 +86,7 @@ public class OAuth2Credentials extends Credentials { // Change listeners are not serialized private transient List changeListeners; // Until we expose this to the users it can remain transient and non-serializable - @VisibleForTesting transient Clock clock = Clock.SYSTEM; + transient Clock clock = Clock.SYSTEM; /** * Returns the credentials instance from the given access token. diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java index 7efec082fe16..643c3dc7dc65 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java @@ -40,7 +40,6 @@ import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.util.PemReader; import com.google.api.client.util.PemReader.Section; -import com.google.api.client.util.SecurityUtils; import com.google.api.core.InternalApi; import com.google.auth.http.AuthHttpConstants; import com.google.auth.http.HttpTransportFactory; @@ -82,6 +81,11 @@ @InternalApi public class OAuth2Utils { + enum Pkcs8Algorithm { + RSA, + EC + } + static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; public static final String TOKEN_TYPE_ACCESS_TOKEN = @@ -269,6 +273,24 @@ static Map validateMap(Map map, String key, Stri * key creation. */ public static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8) throws IOException { + return privateKeyFromPkcs8(privateKeyPkcs8, Pkcs8Algorithm.RSA); + } + + /** + * Reads a private key from a PKCS#8 encoded string. + * + *

If the key is labeled with "-----BEGIN PRIVATE KEY-----", it is parsed as PKCS#8 as per RFC + * 7468 Section 10. + * + * @see RFC 7468 Section 10 + * @param privateKeyPkcs8 base64 encoded private key string + * @param algorithm expected algorithm of the private key + * @return the private key. + * @throws IOException if the private key data is invalid or if an unexpected exception occurs + * during key creation. + */ + public static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8, Pkcs8Algorithm algorithm) + throws IOException { Reader reader = new StringReader(privateKeyPkcs8); Section section = PemReader.readFirstSectionAndClose(reader, "PRIVATE KEY"); if (section == null) { @@ -278,7 +300,7 @@ public static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8) throws IOEx PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); Exception unexpectedException; try { - KeyFactory keyFactory = SecurityUtils.getRsaKeyFactory(); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm.toString()); return keyFactory.generatePrivate(keySpec); } catch (NoSuchAlgorithmException | InvalidKeySpecException exception) { unexpectedException = exception; diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java new file mode 100644 index 000000000000..93343c84ebab --- /dev/null +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/PropertyProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright 2026 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 com.google.api.core.InternalApi; + +/** + * Interface for a system property provider. + * + *

For internal use only. + */ +@InternalApi +public interface PropertyProvider { + String getProperty(String property, String def); +} diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java index a2970772135a..a58285831598 100644 --- a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java @@ -1,9 +1,45 @@ +/* + * Copyright 2026 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 com.google.api.core.InternalApi; import java.io.Serializable; -/** Represents the default system environment provider. */ -class SystemEnvironmentProvider implements EnvironmentProvider, Serializable { +/** + * Represents the default system environment provider. + * + *

For internal use only. + */ +@InternalApi +public class SystemEnvironmentProvider implements EnvironmentProvider, Serializable { static final SystemEnvironmentProvider INSTANCE = new SystemEnvironmentProvider(); private static final long serialVersionUID = -4698164985883575244L; diff --git a/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java new file mode 100644 index 000000000000..6c2042329be8 --- /dev/null +++ b/google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/SystemPropertyProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 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 com.google.api.core.InternalApi; +import java.io.Serializable; + +/** + * Represents the default system property provider. + * + *

For internal use only. + */ +@InternalApi +public class SystemPropertyProvider implements PropertyProvider, Serializable { + public static final SystemPropertyProvider INSTANCE = new SystemPropertyProvider(); + private static final long serialVersionUID = 1L; + + private SystemPropertyProvider() {} + + @Override + public String getProperty(String property, String def) { + return System.getProperty(property, def); + } + + public static SystemPropertyProvider getInstance() { + return INSTANCE; + } +} diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/TestUtils.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/TestUtils.java index d794ba18486d..91b648992848 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/TestUtils.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/TestUtils.java @@ -46,9 +46,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -90,17 +90,20 @@ private static boolean hasBearerToken(Map> metadata, String public static InputStream jsonToInputStream(GenericJson json) throws IOException { json.setFactory(JSON_FACTORY); String text = json.toPrettyString(); - return new ByteArrayInputStream(text.getBytes("UTF-8")); + return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); } public static InputStream stringToInputStream(String text) { - try { - return new ByteArrayInputStream(text.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Unexpected encoding exception", e); - } + return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); } + /** + * Parses a URI query string into a map of key-value pairs. + * + * @param query The URI query string (e.g., "key1=val1&key2=val2"). + * @return A map of decoded keys to decoded values. + * @throws IOException If the query string is malformed. + */ public static Map parseQuery(String query) throws IOException { Map map = new HashMap<>(); Iterable entries = Splitter.on('&').split(query); @@ -116,6 +119,23 @@ public static Map parseQuery(String query) throws IOException { return map; } + /** + * Parses a JSON string into a map of key-value pairs. + * + * @param content The JSON string representation of a flat object. + * @return A map of keys to string representations of their values. + * @throws IOException If the JSON is malformed. + */ + public static Map parseJson(String content) throws IOException { + GenericJson json = JSON_FACTORY.fromString(content, GenericJson.class); + Map map = new HashMap<>(); + for (Map.Entry entry : json.entrySet()) { + Object value = entry.getValue(); + map.put(entry.getKey(), value == null ? null : value.toString()); + } + return map; + } + public static String errorJson(String message) throws IOException { GenericJson errorResponse = new GenericJson(); errorResponse.setFactory(JSON_FACTORY); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java index 10d2141fd7b3..666be521e9ed 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java @@ -79,7 +79,7 @@ void initialize_populatesOAuth2Credentials() throws IOException { HttpHeaders requestHeaders = request.getHeaders(); String authorizationHeader = requestHeaders.getAuthorization(); - assertEquals(authorizationHeader, expectedAuthorization); + assertEquals(expectedAuthorization, authorizationHeader); } @Test diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/MtlsUtilsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/MtlsUtilsTest.java new file mode 100644 index 000000000000..f3fdf05a4c32 --- /dev/null +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/MtlsUtilsTest.java @@ -0,0 +1,246 @@ +/* + * Copyright 2026, Google Inc. All rights reserved. + * + * 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 Inc. 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.mtls; + +import static org.junit.jupiter.api.Assertions.*; + +import com.google.auth.oauth2.EnvironmentProvider; +import com.google.auth.oauth2.PropertyProvider; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class MtlsUtilsTest { + + @TempDir Path tempDir; + + @Test + void getCertificatePath_succeeds() throws IOException { + Path configFile = tempDir.resolve("config.json"); + Files.write( + configFile, + "{\"cert_configs\":{\"workload\":{\"cert_path\":\"cert.pem\",\"key_path\":\"key.pem\"}}}" + .getBytes()); + + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + return def; + } + }; + + String certPath = + MtlsUtils.getCertificatePath(envProvider, propProvider, configFile.toString()); + + assertEquals("cert.pem", certPath); + } + + @Test + void getCertificatePath_missingCertPath_throws() throws IOException { + Path configFile = tempDir.resolve("config.json"); + Files.write( + configFile, "{\"cert_configs\":{\"workload\":{\"key_path\":\"key.pem\"}}}".getBytes()); + + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + return def; + } + }; + + assertThrows( + IllegalArgumentException.class, + () -> MtlsUtils.getCertificatePath(envProvider, propProvider, configFile.toString())); + } + + @Test + void getWorkloadCertificateConfiguration_overridePath() throws IOException { + Path configFile = tempDir.resolve("custom_config.json"); + Files.write( + configFile, + "{\"cert_configs\":{\"workload\":{\"cert_path\":\"cert.pem\",\"key_path\":\"key.pem\"}}}" + .getBytes()); + + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + return def; + } + }; + + WorkloadCertificateConfiguration config = + MtlsUtils.getWorkloadCertificateConfiguration( + envProvider, propProvider, configFile.toString()); + + assertNotNull(config); + assertEquals("cert.pem", config.getCertPath()); + assertEquals("key.pem", config.getPrivateKeyPath()); + } + + @Test + void getWorkloadCertificateConfiguration_envVar() throws IOException { + Path configFile = tempDir.resolve("env_config.json"); + Files.write( + configFile, + "{\"cert_configs\":{\"workload\":{\"cert_path\":\"cert.pem\",\"key_path\":\"key.pem\"}}}" + .getBytes()); + + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return "GOOGLE_API_CERTIFICATE_CONFIG".equals(name) ? configFile.toString() : null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + return def; + } + }; + + WorkloadCertificateConfiguration config = + MtlsUtils.getWorkloadCertificateConfiguration(envProvider, propProvider, null); + + assertNotNull(config); + assertEquals("cert.pem", config.getCertPath()); + } + + @Test + void getWellKnownCertificateConfigFile_windows() throws IOException { + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return "APPDATA".equals(name) ? tempDir.toString() : null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + return "os.name".equals(name) ? "Windows 10" : def; + } + }; + + CertificateSourceUnavailableException exception = + assertThrows( + CertificateSourceUnavailableException.class, + () -> MtlsUtils.getWorkloadCertificateConfiguration(envProvider, propProvider, null)); + + String expectedPath = + new File(tempDir.toFile(), "gcloud/certificate_config.json").getAbsolutePath(); + assertTrue(exception.getMessage().contains(expectedPath)); + } + + @Test + void getWellKnownCertificateConfigFile_linux() throws IOException { + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + if ("os.name".equals(name)) return "Linux"; + if ("user.home".equals(name)) return tempDir.toString(); + return def; + } + }; + + CertificateSourceUnavailableException exception = + assertThrows( + CertificateSourceUnavailableException.class, + () -> MtlsUtils.getWorkloadCertificateConfiguration(envProvider, propProvider, null)); + + String expectedPath = + new File(tempDir.toFile(), ".config/gcloud/certificate_config.json").getAbsolutePath(); + assertTrue(exception.getMessage().contains(expectedPath)); + } + + @Test + void getWellKnownCertificateConfigFile_windows_missingAppData_throws() { + EnvironmentProvider envProvider = + new EnvironmentProvider() { + @Override + public String getEnv(String name) { + return null; + } + }; + PropertyProvider propProvider = + new PropertyProvider() { + @Override + public String getProperty(String name, String def) { + return "os.name".equals(name) ? "Windows 10" : def; + } + }; + + CertificateSourceUnavailableException exception = + assertThrows( + CertificateSourceUnavailableException.class, + () -> MtlsUtils.getWorkloadCertificateConfiguration(envProvider, propProvider, null)); + + assertEquals("APPDATA environment variable is not set on Windows.", exception.getMessage()); + } +} diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/SecureConnectProviderTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/SecureConnectProviderTest.java index 7e962014ae25..fab805fa9520 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/SecureConnectProviderTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/SecureConnectProviderTest.java @@ -38,7 +38,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.GeneralSecurityException; import java.util.List; import org.junit.jupiter.api.Test; @@ -70,7 +69,7 @@ public InputStream getErrorStream() { } @Override - public int waitFor() throws InterruptedException { + public int waitFor() { return 0; } @@ -83,7 +82,9 @@ public int exitValue() { } @Override - public void destroy() {} + public void destroy() { + // Nothing was initialized and nothing needs to be destroyed + } } static class TestProcessProvider implements SecureConnectProvider.ProcessProvider { @@ -102,19 +103,22 @@ public Process createProcess(InputStream metadata) throws IOException { @Test void testGetKeyStoreNonZeroExitCode() { - InputStream metadata = + try (InputStream metadata = this.getClass() .getClassLoader() - .getResourceAsStream("com/google/api/gax/rpc/mtls/mtlsCertAndKey.pem"); - IOException actual = - assertThrows( - IOException.class, - () -> SecureConnectProvider.getKeyStore(metadata, new TestProcessProvider(1))); - assertTrue( - actual - .getMessage() - .contains("SecureConnect: Cert provider command failed with exit code: 1"), - "expected to fail with nonzero exit code"); + .getResourceAsStream("com/google/api/gax/rpc/mtls/mtlsCertAndKey.pem")) { + IOException actual = + assertThrows( + IOException.class, + () -> SecureConnectProvider.getKeyStore(metadata, new TestProcessProvider(1))); + assertTrue( + actual + .getMessage() + .contains("SecureConnect: Cert provider command failed with exit code: 1"), + "expected to fail with nonzero exit code"); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Test @@ -147,8 +151,7 @@ void testRunCertificateProviderCommandTimeout() { } @Test - void testGetKeyStore_FileNotFoundException() - throws IOException, GeneralSecurityException, InterruptedException { + void testGetKeyStore_FileNotFoundException() { SecureConnectProvider provider = new SecureConnectProvider(new TestProcessProvider(0), "/invalid/metadata/path.json"); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java index bd17436c16ee..8d74f7978ce9 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java @@ -58,71 +58,77 @@ void workloadCertificateConfig_fromStream_Succeeds() throws IOException { void workloadCertificateConfig_fromStreamMissingCertPath_Fails() throws IOException { String certPath = ""; String privateKeyPath = "key.crt"; - InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath); - - IllegalArgumentException exception = - assertThrows( - IllegalArgumentException.class, - () -> - WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); - assertTrue( - exception - .getMessage() - .contains( - "The cert_path field must be provided in the workload certificate configuration.")); + try (InputStream configStream = + writeWorkloadCertificateConfigStream(certPath, privateKeyPath)) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream( + configStream)); + assertTrue( + exception + .getMessage() + .contains( + "The cert_path field must be provided in the workload certificate configuration.")); + } } @Test void workloadCertificateConfig_fromStreamMissingPrivateKeyPath_Fails() throws IOException { String certPath = "cert.crt"; String privateKeyPath = ""; - InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath); - - IllegalArgumentException exception = - assertThrows( - IllegalArgumentException.class, - () -> - WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); - assertTrue( - exception - .getMessage() - .contains( - "The key_path field must be provided in the workload certificate configuration.")); + try (InputStream configStream = + writeWorkloadCertificateConfigStream(certPath, privateKeyPath)) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream( + configStream)); + assertTrue( + exception + .getMessage() + .contains( + "The key_path field must be provided in the workload certificate configuration.")); + } } @Test void workloadCertificateConfig_fromStreamMissingWorkload_Fails() throws IOException { GenericJson json = new GenericJson(); json.put("cert_configs", new GenericJson()); - InputStream configStream = TestUtils.jsonToInputStream(json); - - CertificateSourceUnavailableException exception = - assertThrows( - CertificateSourceUnavailableException.class, - () -> - WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); - assertTrue( - exception - .getMessage() - .contains( - "A workload certificate configuration must be provided in the cert_configs object.")); + try (InputStream configStream = TestUtils.jsonToInputStream(json)) { + CertificateSourceUnavailableException exception = + assertThrows( + CertificateSourceUnavailableException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream( + configStream)); + assertTrue( + exception + .getMessage() + .contains( + "A workload certificate configuration must be provided in the cert_configs object.")); + } } @Test void workloadCertificateConfig_fromStreamMissingCertConfig_Fails() throws IOException { GenericJson json = new GenericJson(); - InputStream configStream = TestUtils.jsonToInputStream(json); - - IllegalArgumentException exception = - assertThrows( - IllegalArgumentException.class, - () -> - WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); - assertTrue( - exception - .getMessage() - .contains( - "The cert_configs object must be provided in the certificate configuration file.")); + try (InputStream configStream = TestUtils.jsonToInputStream(json)) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream( + configStream)); + assertTrue( + exception + .getMessage() + .contains( + "The cert_configs object must be provided in the certificate configuration file.")); + } } static InputStream writeWorkloadCertificateConfigStream(String certPath, String privateKeyPath) diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java index 7f3927654f80..5ddd1a169d29 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -36,73 +36,44 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.ByteArrayInputStream; +import com.google.auth.oauth2.SystemPropertyProvider; +import com.google.auth.oauth2.TestEnvironmentProvider; +import com.google.auth.oauth2.TestPropertyProvider; import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; -import java.util.HashMap; -import java.util.Map; import org.junit.jupiter.api.Test; class X509ProviderTest { - private static final String TEST_CERT = - "-----BEGIN CERTIFICATE-----\n" - + "MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE\n" - + "AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x\n" - + "MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3\n" - + "MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB\n" - + "BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ\n" - + "GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ\n" - + "Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB\n" - + "AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM\n" - + "MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4\n" - + "fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4\n" - + "uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1\n" - + "kWwa9n19NFiV0z3m6isj\n" - + "-----END CERTIFICATE-----\n"; - - private static final String TEST_PRIVATE_KEY = - "-----BEGIN PRIVATE KEY-----\n" - + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/\n" - + "XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ\n" - + "oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu\n" - + "uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy\n" - + "sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK\n" - + "/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY\n" - + "lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1\n" - + "fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq\n" - + "RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB\n" - + "Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno\n" - + "fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L\n" - + "XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw\n" - + "ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE\n" - + "VUV6b25kqrcu\n" - + "-----END PRIVATE KEY-----"; + private static final String TEST_CERT_PATH = "testresources/mtls/test_cert.pem"; + private static final String TEST_CONFIG_PATH = "testresources/mtls/certificate_config.json"; @Test void x509Provider_fileDoesntExist_throws() { String certConfigPath = "badfile.txt"; - X509Provider testProvider = new TestX509Provider(certConfigPath); - String expectedErrorMessage = "File does not exist."; - + X509Provider testProvider = new X509Provider(certConfigPath); + String expectedErrorMessage = + "Certificate configuration file does not exist or is not a file: " + + new File(certConfigPath).getAbsolutePath(); CertificateSourceUnavailableException exception = assertThrows(CertificateSourceUnavailableException.class, testProvider::getKeyStore); - assertTrue(exception.getMessage().contains(expectedErrorMessage)); + assertEquals(expectedErrorMessage, exception.getMessage()); } @Test - void x509Provider_emptyFile_throws() { - String certConfigPath = "certConfig.txt"; - InputStream certConfigStream = new ByteArrayInputStream("".getBytes()); - TestX509Provider testProvider = new TestX509Provider(certConfigPath); - testProvider.addFile(certConfigPath, certConfigStream); + void x509Provider_emptyFile_throws() throws IOException { + Path emptyConfig = Files.createTempFile("emptyConfig", ".txt"); + emptyConfig.toFile().deleteOnExit(); + + X509Provider testProvider = new X509Provider(emptyConfig.toString()); String expectedErrorMessage = "no JSON input found"; IllegalArgumentException exception = @@ -112,21 +83,13 @@ void x509Provider_emptyFile_throws() { @Test void x509Provider_succeeds() throws IOException, KeyStoreException, CertificateException { - String certConfigPath = "certConfig.txt"; - String certPath = "cert.crt"; - String keyPath = "key.crt"; - InputStream certConfigStream = - WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream( - certPath, keyPath); - - TestX509Provider testProvider = new TestX509Provider(certConfigPath); - testProvider.addFile(certConfigPath, certConfigStream); - testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes())); - testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes())); + X509Provider testProvider = new X509Provider(TEST_CONFIG_PATH); CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate expectedCert = - cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes())); + Certificate expectedCert; + try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) { + expectedCert = cf.generateCertificate(fis); + } // Assert that the store has the expected certificate and only the expected certificate. KeyStore store = testProvider.getKeyStore(); @@ -137,22 +100,17 @@ void x509Provider_succeeds() throws IOException, KeyStoreException, CertificateE @Test void x509Provider_succeeds_withEnvVariable() throws IOException, KeyStoreException, CertificateException { - String certConfigPath = "certConfig.txt"; - String certPath = "cert.crt"; - String keyPath = "key.crt"; - InputStream certConfigStream = - WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream( - certPath, keyPath); - - TestX509Provider testProvider = new TestX509Provider(); - testProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", certConfigPath); - testProvider.addFile(certConfigPath, certConfigStream); - testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes())); - testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes())); + TestEnvironmentProvider envProvider = new TestEnvironmentProvider(); + envProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", TEST_CONFIG_PATH); + + X509Provider testProvider = + new X509Provider(envProvider, SystemPropertyProvider.getInstance(), null); CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate expectedCert = - cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes())); + Certificate expectedCert; + try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) { + expectedCert = cf.generateCertificate(fis); + } // Assert that the store has the expected certificate and only the expected certificate. KeyStore store = testProvider.getKeyStore(); @@ -163,22 +121,17 @@ void x509Provider_succeeds_withEnvVariable() @Test void x509Provider_succeeds_withWellKnownPath() throws IOException, KeyStoreException, CertificateException { - String certConfigPath = "certConfig.txt"; - String certPath = "cert.crt"; - String keyPath = "key.crt"; - InputStream certConfigStream = - WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream( - certPath, keyPath); - - TestX509Provider testProvider = new TestX509Provider(); - testProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", certConfigPath); - testProvider.addFile(certConfigPath, certConfigStream); - testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes())); - testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes())); + TestEnvironmentProvider envProvider = new TestEnvironmentProvider(); + envProvider.setEnv("CLOUDSDK_CONFIG", "testresources/mtls/"); + + X509Provider testProvider = + new X509Provider(envProvider, SystemPropertyProvider.getInstance(), null); CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate expectedCert = - cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes())); + Certificate expectedCert; + try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) { + expectedCert = cf.generateCertificate(fis); + } // Assert that the store has the expected certificate and only the expected certificate. KeyStore store = testProvider.getKeyStore(); @@ -186,53 +139,73 @@ void x509Provider_succeeds_withWellKnownPath() assertNotNull(store.getCertificateAlias(expectedCert)); } - static class TestX509Provider extends X509Provider { - private final Map files; - private final Map variables; - private final Map properties; + @Test + void x509Provider_succeeds_withWindowsPath() + throws IOException, KeyStoreException, CertificateException { + Path windowsTempDir = Files.createTempDirectory("windowsTempDir"); + windowsTempDir.toFile().deleteOnExit(); + Path gcloudDir = windowsTempDir.resolve("gcloud"); + Files.createDirectory(gcloudDir); + Path configPath = gcloudDir.resolve("certificate_config.json"); - TestX509Provider() { - this(null); - } + // Copy the valid config to this new temp location + Files.copy(new File(TEST_CONFIG_PATH).toPath(), configPath); - TestX509Provider(String filePathOverride) { - super(filePathOverride); - this.files = new HashMap<>(); - this.variables = new HashMap<>(); - this.properties = new HashMap<>(); - } + TestEnvironmentProvider envProvider = new TestEnvironmentProvider(); + envProvider.setEnv("APPDATA", windowsTempDir.toString()); - void addFile(String file, InputStream stream) { - files.put(file, stream); - } + TestPropertyProvider propProvider = new TestPropertyProvider(); + propProvider.setProperty("os.name", "Windows 10"); - @Override - String getEnv(String name) { - return variables.get(name); - } + X509Provider testProvider = new X509Provider(envProvider, propProvider, null); - void setEnv(String name, String value) { - variables.put(name, value); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate expectedCert; + try (FileInputStream fis = new FileInputStream(new File(TEST_CERT_PATH))) { + expectedCert = cf.generateCertificate(fis); } - @Override - String getProperty(String property, String def) { - String value = properties.get(property); - return value == null ? def : value; - } + KeyStore store = testProvider.getKeyStore(); + assertEquals(1, store.size()); + assertNotNull(store.getCertificateAlias(expectedCert)); + } - @Override - boolean isFile(File file) { - return files.containsKey(file.getPath()); - } + @Test + void x509Provider_certFileDoesntExist_throws() throws IOException { + Path tempConfig = Files.createTempFile("config", ".json"); + tempConfig.toFile().deleteOnExit(); + Path nonExistentCert = tempConfig.getParent().resolve("non_existent_cert.pem"); - @Override - InputStream createInputStream(File file) throws FileNotFoundException { - InputStream stream = files.get(file.getPath()); - if (stream == null) { - throw new FileNotFoundException(file.getPath()); - } - return stream; - } + Files.write( + tempConfig, + ("{\"cert_configs\":{\"workload\":{\"cert_path\":\"" + + nonExistentCert.toString() + + "\",\"key_path\":\"key.pem\"}}}") + .getBytes()); + + X509Provider testProvider = new X509Provider(tempConfig.toString()); + + assertThrows(IOException.class, testProvider::getKeyStore); + } + + @Test + void x509Provider_malformedCert_throws() throws IOException { + Path tempConfig = Files.createTempFile("config", ".json"); + tempConfig.toFile().deleteOnExit(); + Path malformedCert = Files.createTempFile("badcert", ".pem"); + malformedCert.toFile().deleteOnExit(); + + Files.write(malformedCert, "This is not a valid certificate".getBytes()); + + Files.write( + tempConfig, + ("{\"cert_configs\":{\"workload\":{\"cert_path\":\"" + + malformedCert.toString() + + "\",\"key_path\":\"key.pem\"}}}") + .getBytes()); + + X509Provider testProvider = new X509Provider(tempConfig.toString()); + + assertThrows(Exception.class, testProvider::getKeyStore); } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java index d93e2c98f53e..f9ebaa2bb94c 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java @@ -32,14 +32,14 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import org.junit.jupiter.api.Test; @@ -92,7 +92,7 @@ void builder() { .setTokenValue(TOKEN) .setScopes("dummy") .build(); - assertEquals(Arrays.asList("dummy"), accessToken.getScopes()); + assertEquals(Collections.singletonList("dummy"), accessToken.getScopes()); // empty scope accessToken = @@ -105,7 +105,7 @@ void builder() { } @Test - void equals_true() throws IOException { + void equals_true() { AccessToken accessToken = AccessToken.newBuilder() .setExpirationTime(EXPIRATION_DATE) @@ -121,12 +121,12 @@ void equals_true() throws IOException { .setScopes(SCOPES) .build(); - assertTrue(accessToken.equals(otherAccessToken)); - assertTrue(otherAccessToken.equals(accessToken)); + assertEquals(accessToken, otherAccessToken); + assertEquals(otherAccessToken, accessToken); } @Test - void equals_false_scopes() throws IOException { + void equals_false_scopes() { AccessToken accessToken = AccessToken.newBuilder() .setExpirationTime(EXPIRATION_DATE) @@ -138,15 +138,15 @@ void equals_false_scopes() throws IOException { AccessToken.newBuilder() .setExpirationTime(EXPIRATION_DATE) .setTokenValue(TOKEN) - .setScopes(Arrays.asList("scope1")) + .setScopes(Collections.singletonList("scope1")) .build(); - assertFalse(accessToken.equals(otherAccessToken)); - assertFalse(otherAccessToken.equals(accessToken)); + assertNotEquals(accessToken, otherAccessToken); + assertNotEquals(otherAccessToken, accessToken); } @Test - void equals_false_token() throws IOException { + void equals_false_token() { AccessToken accessToken = AccessToken.newBuilder() .setExpirationTime(EXPIRATION_DATE) @@ -161,12 +161,12 @@ void equals_false_token() throws IOException { .setScopes(SCOPES) .build(); - assertFalse(accessToken.equals(otherAccessToken)); - assertFalse(otherAccessToken.equals(accessToken)); + assertNotEquals(accessToken, otherAccessToken); + assertNotEquals(otherAccessToken, accessToken); } @Test - void equals_false_expirationDate() throws IOException { + void equals_false_expirationDate() { AccessToken accessToken = AccessToken.newBuilder() .setExpirationTime(EXPIRATION_DATE) @@ -181,8 +181,8 @@ void equals_false_expirationDate() throws IOException { .setScopes(SCOPES) .build(); - assertFalse(accessToken.equals(otherAccessToken)); - assertFalse(otherAccessToken.equals(accessToken)); + assertNotEquals(accessToken, otherAccessToken); + assertNotEquals(otherAccessToken, accessToken); } @Test @@ -201,7 +201,7 @@ void toString_containsFields() { } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { AccessToken accessToken = AccessToken.newBuilder() .setExpirationTime(EXPIRATION_DATE) diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AppEngineCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AppEngineCredentialsTest.java index 43139e2bed2b..eb885d184c17 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AppEngineCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AppEngineCredentialsTest.java @@ -37,8 +37,8 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableMap; import java.io.IOException; @@ -77,18 +77,15 @@ void constructor_usesAppIdentityService() throws IOException { @Test void constructor_noAppEngineRuntime_throwsHelpfulLoadError() { - try { - new TestAppEngineCredentialsNoSdk(); - fail("Credential expected to fail to load if credential class not present."); - } catch (IOException e) { - String message = e.getMessage(); - assertTrue(message.contains("Check that the App Engine SDK is deployed.")); - assertInstanceOf(ClassNotFoundException.class, e.getCause()); - assertTrue( - e.getCause() - .getMessage() - .contains(AppEngineCredentials.APP_IDENTITY_SERVICE_FACTORY_CLASS)); - } + IOException exception = assertThrows(IOException.class, TestAppEngineCredentialsNoSdk::new); + String message = exception.getMessage(); + assertTrue(message.contains("Check that the App Engine SDK is deployed.")); + assertInstanceOf(ClassNotFoundException.class, exception.getCause()); + assertTrue( + exception + .getCause() + .getMessage() + .contains(AppEngineCredentials.APP_IDENTITY_SERVICE_FACTORY_CLASS)); } @Test @@ -115,12 +112,7 @@ void sign_sameAs() throws IOException { void createScoped_clonesWithScopes() throws IOException { TestAppEngineCredentials credentials = new TestAppEngineCredentials(null); assertTrue(credentials.createScopedRequired()); - try { - credentials.refreshAccessToken(); - fail("Should not be able to use credential without scopes."); - } catch (Exception expected) { - // Expected - } + assertThrows(IOException.class, credentials::refreshAccessToken); GoogleCredentials scopedCredentials = credentials.createScoped(SCOPES); assertNotSame(credentials, scopedCredentials); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index 4764d27ec38f..e401ae853771 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -32,12 +32,13 @@ package com.google.auth.oauth2; import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonParser; @@ -55,7 +56,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import org.junit.jupiter.api.Test; /** Tests for {@link AwsCredentials}. */ @@ -86,7 +86,6 @@ class AwsCredentialsTest extends BaseSerializationTest { } }; - private static final Map EMPTY_METADATA_HEADERS = Collections.emptyMap(); private static final Map EMPTY_STRING_HEADERS = Collections.emptyMap(); private static final AwsCredentialSource AWS_CREDENTIAL_SOURCE = @@ -110,13 +109,13 @@ class AwsCredentialsTest extends BaseSerializationTest { @Test void test_awsCredentialSource() { - String keys[] = {"region_url", "url", "imdsv2_session_token_url"}; + String[] keys = {"region_url", "url", "imdsv2_session_token_url"}; for (String key : keys) { Map credentialSourceWithInvalidUrl = buildAwsIpv6CredentialSourceMap(); credentialSourceWithInvalidUrl.put(key, "https://badhost.com/fake"); // Should succeed as no validation is done. - new AwsCredentialSource(credentialSourceWithInvalidUrl); + assertDoesNotThrow(() -> new AwsCredentialSource(credentialSourceWithInvalidUrl)); } } @@ -190,7 +189,7 @@ void refreshAccessToken_withServiceAccountImpersonationOptions() throws IOExcept .setServiceAccountImpersonationUrl( transportFactory.transport.getServiceAccountImpersonationUrl()) .setServiceAccountImpersonationOptions( - ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800)) + ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions()) .build(); AccessToken accessToken = awsCredential.refreshAccessToken(); @@ -199,17 +198,17 @@ void refreshAccessToken_withServiceAccountImpersonationOptions() throws IOExcept transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue()); // Validate that default lifetime was set correctly on the request. - GenericJson query = - OAuth2Utils.JSON_FACTORY - .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString()) - .parseAndClose(GenericJson.class); - - assertEquals("2800s", query.get("lifetime")); - - // Validate metrics header is set correctly on the sts request. - Map> headers = - transportFactory.transport.getRequests().get(6).getHeaders(); - ExternalAccountCredentialsTest.validateMetricsHeader(headers, "aws", true, true); + try (JsonParser jsonParser = + OAuth2Utils.JSON_FACTORY.createJsonParser( + transportFactory.transport.getLastRequest().getContentAsString())) { + GenericJson query = jsonParser.parseAndClose(GenericJson.class); + assertEquals("2800s", query.get("lifetime")); + + // Validate metrics header is set correctly on the sts request. + Map> headers = + transportFactory.transport.getRequests().get(6).getHeaders(); + ExternalAccountCredentialsTest.validateMetricsHeader(headers, "aws", true, true); + } } @Test @@ -485,12 +484,8 @@ void retrieveSubjectToken_noRegion_expectThrows() { .setCredentialSource(buildAwsCredentialSource(transportFactory)) .build(); - try { - awsCredential.retrieveSubjectToken(); - fail("Should not be able to use credential without exception."); - } catch (IOException exception) { - assertEquals("Failed to retrieve AWS region.", exception.getMessage()); - } + IOException exception = assertThrows(IOException.class, awsCredential::retrieveSubjectToken); + assertEquals("Failed to retrieve AWS region.", exception.getMessage()); List requests = transportFactory.transport.getRequests(); assertEquals(1, requests.size()); @@ -514,12 +509,8 @@ void retrieveSubjectToken_noRole_expectThrows() { .setCredentialSource(buildAwsCredentialSource(transportFactory)) .build(); - try { - awsCredential.retrieveSubjectToken(); - fail("Should not be able to use credential without exception."); - } catch (IOException exception) { - assertEquals("Failed to retrieve AWS IAM role.", exception.getMessage()); - } + IOException exception = assertThrows(IOException.class, awsCredential::retrieveSubjectToken); + assertEquals("Failed to retrieve AWS IAM role.", exception.getMessage()); List requests = transportFactory.transport.getRequests(); assertEquals(2, requests.size()); @@ -546,12 +537,8 @@ void retrieveSubjectToken_noCredentials_expectThrows() { .setCredentialSource(buildAwsCredentialSource(transportFactory)) .build(); - try { - awsCredential.retrieveSubjectToken(); - fail("Should not be able to use credential without exception."); - } catch (IOException exception) { - assertEquals("Failed to retrieve AWS credentials.", exception.getMessage()); - } + IOException exception = assertThrows(IOException.class, awsCredential::retrieveSubjectToken); + assertEquals("Failed to retrieve AWS credentials.", exception.getMessage()); List requests = transportFactory.transport.getRequests(); assertEquals(3, requests.size()); @@ -581,15 +568,11 @@ void retrieveSubjectToken_noRegionUrlProvided() { .setCredentialSource(new AwsCredentialSource(credentialSource)) .build(); - try { - awsCredential.retrieveSubjectToken(); - fail("Should not be able to use credential without exception."); - } catch (IOException exception) { - assertEquals( - "Unable to determine the AWS region. The credential source does not " - + "contain the region URL.", - exception.getMessage()); - } + IOException exception = assertThrows(IOException.class, awsCredential::retrieveSubjectToken); + assertEquals( + "Unable to determine the AWS region. The credential source does not " + + "contain the region URL.", + exception.getMessage()); // No requests because the credential source does not contain region URL. List requests = transportFactory.transport.getRequests(); @@ -679,7 +662,7 @@ void retrieveSubjectToken_withProgrammaticRefreshSessionToken() throws IOExcepti } @Test - void retrieveSubjectToken_passesContext() throws IOException { + void retrieveSubjectToken_passesContext() { MockExternalAccountCredentialsTransportFactory transportFactory = new MockExternalAccountCredentialsTransportFactory(); @@ -705,11 +688,11 @@ void retrieveSubjectToken_passesContext() throws IOException { .setSubjectTokenType("subjectTokenType") .build(); - awsCredential.retrieveSubjectToken(); + assertDoesNotThrow(awsCredential::retrieveSubjectToken); } @Test - void retrieveSubjectToken_withProgrammaticRefreshThrowsError() throws IOException { + void retrieveSubjectToken_withProgrammaticRefreshThrowsError() { MockExternalAccountCredentialsTransportFactory transportFactory = new MockExternalAccountCredentialsTransportFactory(); @@ -727,12 +710,8 @@ void retrieveSubjectToken_withProgrammaticRefreshThrowsError() throws IOExceptio .setSubjectTokenType("subjectTokenType") .build(); - try { - String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8"); - fail("retrieveSubjectToken should not succeed"); - } catch (IOException e) { - assertEquals("test", e.getMessage()); - } + IOException exception = assertThrows(IOException.class, awsCredential::retrieveSubjectToken); + assertEquals("test", exception.getMessage()); } @Test @@ -853,14 +832,13 @@ void getAwsSecurityCredentials_fromMetadataServer_noUrlProvided() { .setCredentialSource(new AwsCredentialSource(credentialSource)) .build(); - try { - awsCredential.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext); - fail("Should not be able to use credential without exception."); - } catch (IOException exception) { - assertEquals( - "Unable to determine the AWS IAM role name. The credential source does not contain the url field.", - exception.getMessage()); - } + IOException exception = + assertThrows( + IOException.class, + () -> awsCredential.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext)); + assertEquals( + "Unable to determine the AWS IAM role name. The credential source does not contain the url field.", + exception.getMessage()); // No requests because url field is not present in credential source. List requests = transportFactory.transport.getRequests(); @@ -946,7 +924,7 @@ void getAwsRegion_metadataServer() throws IOException { } @Test - void createdScoped_clonedCredentialWithAddedScopes() throws IOException { + void createdScoped_clonedCredentialWithAddedScopes() { AwsCredentials credentials = AwsCredentials.newBuilder(AWS_CREDENTIAL) .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) @@ -982,12 +960,10 @@ void credentialSource_invalidAwsEnvironmentId() { credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL); credentialSource.put("environment_id", "azure1"); - try { - new AwsCredentialSource(credentialSource); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals("Invalid AWS environment ID.", e.getMessage()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> new AwsCredentialSource(credentialSource)); + assertEquals("Invalid AWS environment ID.", exception.getMessage()); } @Test @@ -997,31 +973,27 @@ void credentialSource_invalidAwsEnvironmentVersion() { credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL); credentialSource.put("environment_id", "aws" + environmentVersion); - try { - new AwsCredentialSource(credentialSource); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals( - String.format( - "AWS version %s is not supported in the current build.", environmentVersion), - e.getMessage()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> new AwsCredentialSource(credentialSource)); + assertEquals( + String.format("AWS version %s is not supported in the current build.", environmentVersion), + exception.getMessage()); } @Test void credentialSource_missingRegionalCredVerificationUrl() { - try { - new AwsCredentialSource(new HashMap()); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals( - "A regional_cred_verification_url representing the GetCallerIdentity action URL must be specified.", - e.getMessage()); - } + HashMap credentialSourceMap = new HashMap<>(); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> new AwsCredentialSource(credentialSourceMap)); + assertEquals( + "A regional_cred_verification_url representing the GetCallerIdentity action URL must be specified.", + exception.getMessage()); } @Test - void builder_allFields() throws IOException { + void builder_allFields() { List scopes = Arrays.asList("scope1", "scope2"); AwsCredentials credentials = @@ -1057,7 +1029,7 @@ void builder_allFields() throws IOException { } @Test - void builder_missingUniverseDomain_defaults() throws IOException { + void builder_missingUniverseDomain_defaults() { List scopes = Arrays.asList("scope1", "scope2"); AwsCredentials credentials = @@ -1094,7 +1066,7 @@ void builder_missingUniverseDomain_defaults() throws IOException { } @Test - void newBuilder_allFields() throws IOException { + void newBuilder_allFields() { List scopes = Arrays.asList("scope1", "scope2"); AwsCredentials credentials = @@ -1132,7 +1104,7 @@ void newBuilder_allFields() throws IOException { } @Test - void newBuilder_noUniverseDomain_defaults() throws IOException { + void newBuilder_noUniverseDomain_defaults() { List scopes = Arrays.asList("scope1", "scope2"); AwsCredentials credentials = @@ -1169,7 +1141,7 @@ void newBuilder_noUniverseDomain_defaults() throws IOException { } @Test - void builder_defaultRegionalCredentialVerificationUrlOverride() throws IOException { + void builder_defaultRegionalCredentialVerificationUrlOverride() { List scopes = Arrays.asList("scope1", "scope2"); AwsSecurityCredentialsSupplier supplier = @@ -1197,63 +1169,55 @@ void builder_defaultRegionalCredentialVerificationUrlOverride() throws IOExcepti } @Test - void builder_supplierAndCredSourceThrows() throws IOException { + void builder_supplierAndCredSourceThrows() { List scopes = Arrays.asList("scope1", "scope2"); AwsSecurityCredentialsSupplier supplier = new TestAwsSecurityCredentialsSupplier("region", null, null, null); - try { - AwsCredentials credentials = - AwsCredentials.newBuilder() - .setAwsSecurityCredentialsSupplier(supplier) - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience("audience") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("tokenInfoUrl") - .setCredentialSource(AWS_CREDENTIAL_SOURCE) - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setQuotaProjectId("quotaProjectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setScopes(scopes) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "AwsCredentials cannot have both an awsSecurityCredentialsSupplier and a credentialSource.", - exception.getMessage()); - } + AwsCredentials.Builder builder = + AwsCredentials.newBuilder() + .setAwsSecurityCredentialsSupplier(supplier) + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("tokenInfoUrl") + .setCredentialSource(AWS_CREDENTIAL_SOURCE) + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setQuotaProjectId("quotaProjectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setScopes(scopes); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "AwsCredentials cannot have both an awsSecurityCredentialsSupplier and a credentialSource.", + exception.getMessage()); } @Test - void builder_noSupplieOrCredSourceThrows() throws IOException { + void builder_noSupplieOrCredSourceThrows() { List scopes = Arrays.asList("scope1", "scope2"); - Supplier testSupplier = () -> null; - - try { - AwsCredentials credentials = - AwsCredentials.newBuilder() - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience("audience") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("tokenInfoUrl") - .setTokenInfoUrl("tokenInfoUrl") - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setQuotaProjectId("quotaProjectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setScopes(scopes) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "An awsSecurityCredentialsSupplier or a credentialSource must be provided.", - exception.getMessage()); - } + AwsCredentials.Builder builder = + AwsCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("tokenInfoUrl") + .setTokenInfoUrl("tokenInfoUrl") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setQuotaProjectId("quotaProjectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setScopes(scopes); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "An awsSecurityCredentialsSupplier or a credentialSource must be provided.", + exception.getMessage()); } @Test @@ -1281,7 +1245,7 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(testCredentials, deserializedCredentials); assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(testCredentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } private static void ValidateRequest( @@ -1353,12 +1317,12 @@ static InputStream writeAwsCredentialsStream(String stsUrl, String regionUrl, St return TestUtils.jsonToInputStream(json); } - class TestAwsSecurityCredentialsSupplier implements AwsSecurityCredentialsSupplier { + static class TestAwsSecurityCredentialsSupplier implements AwsSecurityCredentialsSupplier { - private String region; - private AwsSecurityCredentials credentials; - private IOException credentialException; - private ExternalAccountSupplierContext expectedContext; + private final String region; + private final AwsSecurityCredentials credentials; + private final IOException credentialException; + private final ExternalAccountSupplierContext expectedContext; TestAwsSecurityCredentialsSupplier( String region, diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsRequestSignerTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsRequestSignerTest.java index a1125edef760..750390aeaec2 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsRequestSignerTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/AwsRequestSignerTest.java @@ -49,8 +49,9 @@ /** * Tests for {@link AwsRequestSigner}. * - *

Examples of sigv4 signed requests: - * https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html + *

Examples + * of sigv4 signed requests */ class AwsRequestSignerTest { diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CertificateIdentityPoolSubjectTokenSupplierTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CertificateIdentityPoolSubjectTokenSupplierTest.java index 9ff8f7f5f9f6..dfb81281db53 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CertificateIdentityPoolSubjectTokenSupplierTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CertificateIdentityPoolSubjectTokenSupplierTest.java @@ -32,6 +32,7 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -254,8 +255,9 @@ void getSubjectToken_trustChainWrongOrder_throwsIllegalArgumentException() { Throwable cause = exception.getCause(); assertNotNull(cause, "Exception cause should not be null"); - assertTrue( - cause instanceof IllegalArgumentException, + assertInstanceOf( + IllegalArgumentException.class, + cause, "Exception cause should be an IllegalArgumentException"); assertEquals(expectedRootErrorMessage, cause.getMessage()); } @@ -301,7 +303,7 @@ void getSubjectToken_trustChainFileNotFound_throwsIOException() { assertThrows(IOException.class, () -> supplier.getSubjectToken(mockContext)); // Check that the cause is NoSuchFileException from readTrustChain. - assertTrue(exception.getCause() instanceof NoSuchFileException); + assertInstanceOf(NoSuchFileException.class, exception.getCause()); // Check the outer exception message added in getSubjectToken. assertEquals("Trust chain file not found: " + nonExistentPath, exception.getMessage()); @@ -328,7 +330,7 @@ void getSubjectToken_trustChainInvalidFormat_throwsIOException() throws Exceptio exception.getMessage()); // Check that the cause is CertificateException from readTrustChain - assertTrue(exception.getCause() instanceof CertificateException); + assertInstanceOf(CertificateException.class, exception.getCause()); // Verify the cause's message specifically points to the trust chain parsing failure // and includes the path of the invalid trust chain file. @@ -357,8 +359,8 @@ void getSubjectToken_leafCertFileNotFound_throwsIOException() { // Check that the cause is the original NoSuchFileException. assertNotNull(exception.getCause(), "Exception should have a cause"); - assertTrue( - exception.getCause() instanceof NoSuchFileException, "Cause should be NoSuchFileException"); + assertInstanceOf( + NoSuchFileException.class, exception.getCause(), "Cause should be NoSuchFileException"); // Check the message of the cause (which is the path) in a platform-agnostic way. Path expectedCausePath = Paths.get(nonExistentPath); @@ -385,7 +387,7 @@ void getSubjectToken_leafCertInvalidFormat_throwsIOException() throws Exception // Check that the cause is CertificateException from parseCertificate (via // loadLeafCertificate) - assertTrue(exception.getCause() instanceof CertificateException); + assertInstanceOf(CertificateException.class, exception.getCause()); assertEquals("Failed to parse X.509 certificate data.", exception.getCause().getMessage()); // Check the outer exception message diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java index c002cf7e4fe5..eaceca0eaf3a 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java @@ -31,6 +31,7 @@ package com.google.auth.oauth2; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -57,9 +58,8 @@ void constructor() { @Test void constructor_nullClientId_throws() { - assertThrows( - NullPointerException.class, - () -> ClientId.newBuilder().setClientSecret(CLIENT_SECRET).build()); + ClientId.Builder builder = ClientId.newBuilder().setClientSecret(CLIENT_SECRET); + assertThrows(NullPointerException.class, builder::build); } @Test @@ -100,21 +100,21 @@ void fromJson_installedNoSecret() throws IOException { } @Test - void fromJson_invalidType_throws() throws IOException { + void fromJson_invalidType_throws() { GenericJson json = writeClientIdJson("invalid", CLIENT_ID, null); assertThrows(IOException.class, () -> ClientId.fromJson(json)); } @Test - void fromJson_noClientId_throws() throws IOException { + void fromJson_noClientId_throws() { GenericJson json = writeClientIdJson("web", null, null); assertThrows(IOException.class, () -> ClientId.fromJson(json)); } @Test - void fromJson_zeroLengthClientId_throws() throws IOException { + void fromJson_zeroLengthClientId_throws() { GenericJson json = writeClientIdJson("web", "", null); assertThrows(IOException.class, () -> ClientId.fromJson(json)); @@ -129,7 +129,7 @@ void fromResource() throws IOException { } @Test - void fromResource_badResource() throws IOException { + void fromResource_badResource() { assertThrows( NullPointerException.class, () -> ClientId.fromResource(ClientIdTest.class, "invalid.json")); @@ -168,9 +168,9 @@ void fromStream_invalidJson_doesNotThrow() throws IOException { + CLIENT_SECRET + "\"" + "}"; // No closing brace - InputStream stream = TestUtils.stringToInputStream(invalidJson); - - ClientId.fromStream(stream); + try (InputStream stream = TestUtils.stringToInputStream(invalidJson)) { + assertDoesNotThrow(() -> ClientId.fromStream(stream)); + } } private GenericJson writeClientIdJson(String type, String clientId, String clientSecret) { diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java index 2f0dde620679..21abe335f3f5 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java @@ -32,9 +32,8 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.api.client.util.Clock; import java.io.BufferedReader; @@ -50,63 +49,57 @@ class CloudShellCredentialsTest extends BaseSerializationTest { @Test void refreshAccessToken() throws IOException { - final ServerSocket authSocket = new ServerSocket(0); - try { + try (ServerSocket authSocket = new ServerSocket(0)) { Runnable serverTask = - new Runnable() { - @Override - public void run() { - try { - Socket clientSocket = authSocket.accept(); - BufferedReader input = - new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); - String lines = input.readLine(); - lines += '\n' + input.readLine(); - assertEquals(lines, CloudShellCredentials.GET_AUTH_TOKEN_REQUEST); - - PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); - out.println("32\n[\"email\", \"project-id\", \"token\"]"); - } catch (Exception reThrown) { - throw new RuntimeException(reThrown); - } + () -> { + try { + Socket clientSocket = authSocket.accept(); + BufferedReader input = + new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + String lines = input.readLine(); + lines += '\n' + input.readLine(); + assertEquals(CloudShellCredentials.GET_AUTH_TOKEN_REQUEST, lines); + + PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); + out.println("32\n[\"email\", \"project-id\", \"token\"]"); + } catch (Exception reThrown) { + throw new RuntimeException(reThrown); } }; Thread serverThread = new Thread(serverTask); serverThread.start(); - GoogleCredentials creds = + GoogleCredentials credentials = CloudShellCredentials.newBuilder().setAuthPort(authSocket.getLocalPort()).build(); - assertEquals("token", creds.refreshAccessToken().getTokenValue()); - } finally { - authSocket.close(); + assertEquals("token", credentials.refreshAccessToken().getTokenValue()); } } @Test - void equals_true() throws IOException { + void equals_true() { GoogleCredentials credentials = CloudShellCredentials.newBuilder().setAuthPort(42).build(); GoogleCredentials otherCredentials = CloudShellCredentials.newBuilder().setAuthPort(42).build(); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test - void equals_false_authPort() throws IOException { + void equals_false_authPort() { GoogleCredentials credentials = CloudShellCredentials.newBuilder().setAuthPort(42).build(); GoogleCredentials otherCredentials = CloudShellCredentials.newBuilder().setAuthPort(43).build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void toString_containsFields() throws IOException { + void toString_containsFields() { String expectedToString = String.format("CloudShellCredentials{authPort=%d}", 42); GoogleCredentials credentials = CloudShellCredentials.newBuilder().setAuthPort(42).build(); assertEquals(expectedToString, credentials.toString()); } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { GoogleCredentials credentials = CloudShellCredentials.newBuilder().setAuthPort(42).build(); GoogleCredentials otherCredentials = CloudShellCredentials.newBuilder().setAuthPort(42).build(); assertEquals(credentials.hashCode(), otherCredentials.hashCode()); @@ -119,7 +112,7 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } @Test diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java index 2f9b4b7e2836..82240171d9af 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java @@ -36,12 +36,13 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpStatusCodes; import com.google.api.client.http.HttpTransport; @@ -151,7 +152,7 @@ void buildTokenUrlWithScopes_empty_scopes() { @Test void buildTokenUrlWithScopes_single_scope() { ComputeEngineCredentials credentials = - ComputeEngineCredentials.newBuilder().setScopes(Arrays.asList("foo")).build(); + ComputeEngineCredentials.newBuilder().setScopes(Collections.singletonList("foo")).build(); String tokenUrlWithScopes = credentials.createTokenUrlWithScopes(); Collection scopes = credentials.getScopes(); @@ -295,11 +296,11 @@ void buildTokenUrlHardDirectPathBound_alts_transport() { } @Test - void buildScoped_scopesPresent() throws IOException { + void buildScoped_scopesPresent() { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setScopes(null).build(); ComputeEngineCredentials scopedCredentials = - (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo")); + (ComputeEngineCredentials) credentials.createScoped(Collections.singletonList("foo")); Collection scopes = scopedCredentials.getScopes(); assertEquals(1, scopes.size()); @@ -307,11 +308,11 @@ void buildScoped_scopesPresent() throws IOException { } @Test - void buildScoped_correctMargins() throws IOException { + void buildScoped_correctMargins() { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setScopes(null).build(); ComputeEngineCredentials scopedCredentials = - (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo")); + (ComputeEngineCredentials) credentials.createScoped(Collections.singletonList("foo")); assertEquals( ComputeEngineCredentials.COMPUTE_EXPIRATION_MARGIN, @@ -328,7 +329,7 @@ void buildScoped_explicitUniverse() throws IOException { .setUniverseDomain("some-universe") .build(); ComputeEngineCredentials scopedCredentials = - (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo")); + (ComputeEngineCredentials) credentials.createScoped(Collections.singletonList("foo")); assertEquals("some-universe", scopedCredentials.getUniverseDomain()); assertTrue(scopedCredentials.isExplicitUniverseDomain()); @@ -337,7 +338,7 @@ void buildScoped_explicitUniverse() throws IOException { @Test void createScoped_defaultScopes() { GoogleCredentials credentials = - ComputeEngineCredentials.create().createScoped(null, Arrays.asList("foo")); + ComputeEngineCredentials.create().createScoped(null, Collections.singletonList("foo")); Collection scopes = ((ComputeEngineCredentials) credentials).getScopes(); assertEquals(1, scopes.size()); @@ -345,14 +346,14 @@ void createScoped_defaultScopes() { } @Test - void buildScoped_quotaProjectId() throws IOException { + void buildScoped_quotaProjectId() { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder() .setScopes(null) .setQuotaProjectId("some-project-id") .build(); ComputeEngineCredentials scopedCredentials = - (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo")); + (ComputeEngineCredentials) credentials.createScoped(Collections.singletonList("foo")); assertEquals("some-project-id", scopedCredentials.getQuotaProjectId()); } @@ -365,7 +366,7 @@ void buildDefaultScoped_explicitUniverse() throws IOException { .setUniverseDomain("some-universe") .build(); ComputeEngineCredentials scopedCredentials = - (ComputeEngineCredentials) credentials.createScoped(null, Arrays.asList("foo")); + (ComputeEngineCredentials) credentials.createScoped(null, Collections.singletonList("foo")); assertEquals("some-universe", scopedCredentials.getUniverseDomain()); assertTrue(scopedCredentials.isExplicitUniverseDomain()); @@ -374,7 +375,7 @@ void buildDefaultScoped_explicitUniverse() throws IOException { @Test void create_scoped_correctMargins() { GoogleCredentials credentials = - ComputeEngineCredentials.create().createScoped(null, Arrays.asList("foo")); + ComputeEngineCredentials.create().createScoped(null, Collections.singletonList("foo")); assertEquals( ComputeEngineCredentials.COMPUTE_EXPIRATION_MARGIN, credentials.getExpirationMargin()); @@ -425,15 +426,12 @@ void getRequestMetadata_missingServiceAccount_throws() { transportFactory.transport.setServiceAccountEmail("SA_CLIENT_EMAIL"); ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); - try { - credentials.getRequestMetadata(CALL_URI); - fail("Expected error refreshing token."); - } catch (IOException expected) { - String message = expected.getMessage(); - assertTrue(message.contains(Integer.toString(HttpStatusCodes.STATUS_CODE_NOT_FOUND))); - // Message should mention scopes are missing on the VM. - assertTrue(message.contains("scope")); - } + IOException exception = + assertThrows(IOException.class, () -> credentials.getRequestMetadata(CALL_URI)); + String message = exception.getMessage(); + assertTrue(message.contains(Integer.toString(HttpStatusCodes.STATUS_CODE_NOT_FOUND))); + // Message should mention scopes are missing on the VM. + assertTrue(message.contains("scope")); } @Test @@ -443,14 +441,11 @@ void getRequestMetadata_serverError_throws() { transportFactory.transport.setServiceAccountEmail("SA_CLIENT_EMAIL"); ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); - try { - credentials.getRequestMetadata(CALL_URI); - fail("Expected error refreshing token."); - } catch (IOException expected) { - String message = expected.getMessage(); - assertTrue(message.contains(Integer.toString(HttpStatusCodes.STATUS_CODE_SERVER_ERROR))); - assertTrue(message.contains("Unexpected")); - } + IOException exception = + assertThrows(IOException.class, () -> credentials.getRequestMetadata(CALL_URI)); + String message = exception.getMessage(); + assertTrue(message.contains(Integer.toString(HttpStatusCodes.STATUS_CODE_SERVER_ERROR))); + assertTrue(message.contains("Unexpected")); } @Test @@ -464,19 +459,19 @@ void equals_true() throws IOException { ComputeEngineCredentials otherCredentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, otherCredentials.getUniverseDomain()); - assertFalse(explicitUniverseCredentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(explicitUniverseCredentials)); + assertNotEquals(explicitUniverseCredentials, otherCredentials); + assertNotEquals(otherCredentials, explicitUniverseCredentials); ComputeEngineCredentials otherExplicitUniverseCredentials = ComputeEngineCredentials.newBuilder() .setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE) .setHttpTransportFactory(transportFactory) .build(); - assertTrue(explicitUniverseCredentials.equals(otherExplicitUniverseCredentials)); - assertTrue(otherExplicitUniverseCredentials.equals(explicitUniverseCredentials)); + assertEquals(explicitUniverseCredentials, otherExplicitUniverseCredentials); + assertEquals(otherExplicitUniverseCredentials, explicitUniverseCredentials); } @Test - void equals_false_transportFactory() throws IOException { + void equals_false_transportFactory() { MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); MockMetadataServerTransportFactory serverTransportFactory = new MockMetadataServerTransportFactory(); @@ -486,12 +481,12 @@ void equals_false_transportFactory() throws IOException { .build(); ComputeEngineCredentials otherCredentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(httpTransportFactory).build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void toString_explicit_containsFields() throws IOException { + void toString_explicit_containsFields() { MockMetadataServerTransportFactory serverTransportFactory = new MockMetadataServerTransportFactory(); String expectedToString = @@ -513,7 +508,7 @@ void toString_explicit_containsFields() throws IOException { } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { MockMetadataServerTransportFactory serverTransportFactory = new MockMetadataServerTransportFactory(); ComputeEngineCredentials credentials = @@ -552,17 +547,17 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); credentials = ComputeEngineCredentials.newBuilder().build(); deserializedCredentials = serializeAndDeserialize(credentials); assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } @Test - void getAccount_sameAs() throws IOException { + void getAccount_sameAs() { MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); String defaultAccountEmail = "mail@mail.com"; @@ -603,14 +598,10 @@ public LowLevelHttpResponse execute() throws IOException { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); - try { - credentials.getAccount(); - fail("Fetching default service account should have failed"); - } catch (RuntimeException e) { - assertEquals("Failed to get service account", e.getMessage()); - assertNotNull(e.getCause()); - assertTrue(e.getCause().getMessage().contains("404")); - } + RuntimeException exception = assertThrows(RuntimeException.class, credentials::getAccount); + assertEquals("Failed to get service account", exception.getMessage()); + assertNotNull(exception.getCause()); + assertTrue(exception.getCause().getMessage().contains("404")); } @Test @@ -638,14 +629,10 @@ public LowLevelHttpResponse execute() throws IOException { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); - try { - credentials.getAccount(); - fail("Fetching default service account should have failed"); - } catch (RuntimeException e) { - assertEquals("Failed to get service account", e.getMessage()); - assertNotNull(e.getCause()); - assertTrue(e.getCause().getMessage().contains("Empty content")); - } + RuntimeException exception = assertThrows(RuntimeException.class, credentials::getAccount); + assertEquals("Failed to get service account", exception.getMessage()); + assertNotNull(exception.getCause()); + assertTrue(exception.getCause().getMessage().contains("Empty content")); } @Test @@ -793,9 +780,9 @@ public LowLevelHttpResponse execute() throws IOException { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); - IOException exception = assertThrows(IOException.class, () -> credentials.refreshAccessToken()); + IOException exception = assertThrows(IOException.class, credentials::refreshAccessToken); assertTrue(exception.getCause().getMessage().contains("503")); - assertTrue(exception instanceof GoogleAuthException); + assertInstanceOf(GoogleAuthException.class, exception); assertTrue(((GoogleAuthException) exception).isRetryable()); } @@ -803,7 +790,7 @@ public LowLevelHttpResponse execute() throws IOException { void refresh_non503_ioexception_throws() { MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); final Queue responseSequence = new ArrayDeque<>(); - IntStream.rangeClosed(400, 600).forEach(i -> responseSequence.add(i)); + IntStream.rangeClosed(400, 600).forEach(responseSequence::add); while (!responseSequence.isEmpty()) { if (responseSequence.peek() == 503) { @@ -829,8 +816,7 @@ public LowLevelHttpResponse execute() throws IOException { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); - IOException exception = - assertThrows(IOException.class, () -> credentials.refreshAccessToken()); + IOException exception = assertThrows(IOException.class, credentials::refreshAccessToken); assertFalse(exception instanceof GoogleAuthException); } } @@ -859,7 +845,7 @@ public LowLevelHttpResponse execute() throws IOException { String universeDomain = credentials.getUniverseDomain(); assertEquals("some-universe.xyz", universeDomain); - assertEquals(false, credentials.isExplicitUniverseDomain()); + assertFalse(credentials.isExplicitUniverseDomain()); } @Test @@ -886,7 +872,7 @@ public LowLevelHttpResponse execute() throws IOException { String universeDomain = credentials.getUniverseDomain(); assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, universeDomain); - assertEquals(false, credentials.isExplicitUniverseDomain()); + assertFalse(credentials.isExplicitUniverseDomain()); } @Test @@ -913,7 +899,7 @@ public LowLevelHttpResponse execute() throws IOException { String universeDomain = credentials.getUniverseDomain(); assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, universeDomain); - assertEquals(false, credentials.isExplicitUniverseDomain()); + assertFalse(credentials.isExplicitUniverseDomain()); } @Test @@ -929,7 +915,7 @@ void getUniverseDomain_explicitSet_NoMdsCall() throws IOException { String universeDomain = credentials.getUniverseDomain(); assertEquals("explicit.universe", universeDomain); - assertEquals(true, credentials.isExplicitUniverseDomain()); + assertTrue(credentials.isExplicitUniverseDomain()); assertEquals(0, transportFactory.transport.getRequestCount()); } @@ -946,14 +932,13 @@ void getUniverseDomain_explicitGduSet_NoMdsCall() throws IOException { String universeDomain = credentials.getUniverseDomain(); assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, universeDomain); - assertEquals(true, credentials.isExplicitUniverseDomain()); + assertTrue(credentials.isExplicitUniverseDomain()); assertEquals(0, transportFactory.transport.getRequestCount()); } @Test - void getUniverseDomain_fromMetadata_non404error_throws() throws IOException { + void getUniverseDomain_fromMetadata_non404error_throws() { MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); - MockMetadataServerTransport transport = transportFactory.transport; ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); @@ -963,13 +948,10 @@ void getUniverseDomain_fromMetadata_non404error_throws() throws IOException { if (status == 404) { continue; } - try { - transportFactory.transport.setStatusCode(status); - credentials.getUniverseDomain(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - assertTrue(ex.isRetryable()); - } + transportFactory.transport.setStatusCode(status); + GoogleAuthException ex = + assertThrows(GoogleAuthException.class, credentials::getUniverseDomain); + assertTrue(ex.isRetryable()); } } @@ -985,7 +967,7 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce if (isSignRequestUrl(url)) { return new MockLowLevelHttpRequest(url) { @Override - public LowLevelHttpResponse execute() throws IOException { + public LowLevelHttpResponse execute() { return new MockLowLevelHttpResponse() .setStatusCode(HttpStatusCodes.STATUS_CODE_OK); } @@ -1061,7 +1043,7 @@ void idTokenWithAudience_full() throws IOException { IdTokenCredentials.newBuilder() .setIdTokenProvider(credentials) .setTargetAudience(targetAudience) - .setOptions(Arrays.asList(IdTokenProvider.Option.FORMAT_FULL)) + .setOptions(Collections.singletonList(IdTokenProvider.Option.FORMAT_FULL)) .build(); tokenCredential.refresh(); Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload(); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CredentialAccessBoundaryTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CredentialAccessBoundaryTest.java index 0dee7011e4ee..04abd3d9d23c 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CredentialAccessBoundaryTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/CredentialAccessBoundaryTest.java @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.auth.oauth2.CredentialAccessBoundary.AccessBoundaryRule; import com.google.auth.oauth2.CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition; @@ -90,22 +90,17 @@ void credentialAccessBoundary() { @Test void credentialAccessBoundary_nullRules_throws() { - try { - CredentialAccessBoundary.newBuilder().build(); - fail("Should fail."); - } catch (NullPointerException e) { - // Expected. - } + CredentialAccessBoundary.Builder builder = CredentialAccessBoundary.newBuilder(); + assertThrows(NullPointerException.class, builder::build); } @Test void credentialAccessBoundary_withoutRules_throws() { - try { - CredentialAccessBoundary.newBuilder().setRules(new ArrayList()).build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("At least one access boundary rule must be provided.", e.getMessage()); - } + CredentialAccessBoundary.Builder builder = + CredentialAccessBoundary.newBuilder().setRules(new ArrayList<>()); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("At least one access boundary rule must be provided.", exception.getMessage()); } @Test @@ -121,12 +116,10 @@ void credentialAccessBoundary_ruleCountExceeded_throws() { builder.addRule(rule); } - try { - builder.build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("The provided list has more than 10 access boundary rules.", e.getMessage()); - } + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "The provided list has more than 10 access boundary rules.", exception.getMessage()); } @Test @@ -204,74 +197,58 @@ void accessBoundaryRule_requiredFields() { @Test void accessBoundaryRule_withEmptyAvailableResource_throws() { - try { - AccessBoundaryRule.newBuilder() - .setAvailableResource("") - .addAvailablePermission("permission") - .build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("The provided availableResource is empty.", e.getMessage()); - } + AccessBoundaryRule.Builder builder = + AccessBoundaryRule.newBuilder() + .setAvailableResource("") + .addAvailablePermission("permission"); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("The provided availableResource is empty.", exception.getMessage()); } @Test void accessBoundaryRule_withoutAvailableResource_throws() { - try { - AccessBoundaryRule.newBuilder().addAvailablePermission("permission").build(); - fail("Should fail."); - } catch (NullPointerException e) { - // Expected. - } + AccessBoundaryRule.Builder builder = + AccessBoundaryRule.newBuilder().addAvailablePermission("permission"); + assertThrows(NullPointerException.class, builder::build); } @Test void accessBoundaryRule_withoutAvailablePermissions_throws() { - try { - AccessBoundaryRule.newBuilder().setAvailableResource("resource").build(); - fail("Should fail."); - } catch (NullPointerException e) { - // Expected. - } + AccessBoundaryRule.Builder builder = + AccessBoundaryRule.newBuilder().setAvailableResource("resource"); + assertThrows(NullPointerException.class, builder::build); } @Test void accessBoundaryRule_withEmptyAvailablePermissions_throws() { - try { - AccessBoundaryRule.newBuilder() - .setAvailableResource("resource") - .setAvailablePermissions(new ArrayList()) - .build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("The list of provided availablePermissions is empty.", e.getMessage()); - } + AccessBoundaryRule.Builder builder = + AccessBoundaryRule.newBuilder() + .setAvailableResource("resource") + .setAvailablePermissions(new ArrayList<>()); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("The list of provided availablePermissions is empty.", exception.getMessage()); } @Test void accessBoundaryRule_withNullAvailablePermissions_throws() { - try { - AccessBoundaryRule.newBuilder() - .setAvailableResource("resource") - .addAvailablePermission(null) - .build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("One of the provided available permissions is null.", e.getMessage()); - } + AccessBoundaryRule.Builder builder = + AccessBoundaryRule.newBuilder() + .setAvailableResource("resource") + .addAvailablePermission(null); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("One of the provided available permissions is null.", exception.getMessage()); } @Test void accessBoundaryRule_withEmptyAvailablePermission_throws() { - try { - AccessBoundaryRule.newBuilder() - .setAvailableResource("resource") - .addAvailablePermission("") - .build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("One of the provided available permissions is empty.", e.getMessage()); - } + AccessBoundaryRule.Builder builder = + AccessBoundaryRule.newBuilder().setAvailableResource("resource").addAvailablePermission(""); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("One of the provided available permissions is empty.", exception.getMessage()); } @Test @@ -300,21 +277,15 @@ void availabilityCondition_expressionOnly() { @Test void availabilityCondition_nullExpression_throws() { - try { - AvailabilityCondition.newBuilder().setExpression(null).build(); - fail("Should fail."); - } catch (NullPointerException e) { - // Expected. - } + AvailabilityCondition.Builder builder = AvailabilityCondition.newBuilder().setExpression(null); + assertThrows(NullPointerException.class, builder::build); } @Test void availabilityCondition_emptyExpression_throws() { - try { - AvailabilityCondition.newBuilder().setExpression("").build(); - fail("Should fail."); - } catch (IllegalArgumentException e) { - assertEquals("The provided expression is empty.", e.getMessage()); - } + AvailabilityCondition.Builder builder = AvailabilityCondition.newBuilder().setExpression(""); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals("The provided expression is empty.", exception.getMessage()); } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java index 1852629ee6cb..d0447871b01a 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java @@ -33,11 +33,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.LowLevelHttpRequest; @@ -83,7 +85,7 @@ class DefaultCredentialsProviderTest { private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; private static final String SA_PRIVATE_KEY_PKCS8 = ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8; - private static final String GDCH_SA_FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION; + private static final String GDCH_SA_PROJECT_ID = "gdch-service-account-project-id"; private static final String GDCH_SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; private static final String GDCH_SA_PRIVATE_KEY_PKC8 = GdchCredentialsTest.PRIVATE_KEY_PKCS8; @@ -94,7 +96,7 @@ class DefaultCredentialsProviderTest { private static final String GDCH_SA_CA_CERT_FILE_NAME = "cert.pem"; private static final String GDCH_SA_CA_CERT_PATH = GdchCredentialsTest.class.getClassLoader().getResource(GDCH_SA_CA_CERT_FILE_NAME).getPath(); - private static final URI GDCH_SA_API_AUDIENCE = URI.create("https://gdch-api-audience"); + private static final String GDCH_SA_API_AUDIENCE = "https://gdch-api-audience"; private static final Collection SCOPES = Collections.singletonList("dummy.scope"); private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); private static final String QUOTA_PROJECT = "sample-quota-project-id"; @@ -107,28 +109,22 @@ void getDefaultCredentials_noCredentials_throws() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException e) { - String message = e.getMessage(); - assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS)); - } + IOException e = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = e.getMessage(); + assertEquals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS, message); } @Test void getDefaultCredentials_noCredentialsSandbox_throwsNonSecurity() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.setFileSandbox(true); - - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException e) { - String message = e.getMessage(); - assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS)); - } + testProvider.setFileSandbox(); + + IOException e = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = e.getMessage(); + assertEquals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS, message); } @Test @@ -138,18 +134,15 @@ void getDefaultCredentials_envValidSandbox_throwsNonSecurity() throws Exception UserCredentialsTest.writeUserStream( USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.setFileSandbox(true); + testProvider.setFileSandbox(); String userPath = tempFilePath("user.json"); testProvider.addFile(userPath, userStream); testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, userPath); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException e) { - String message = e.getMessage(); - assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS)); - } + IOException e = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = e.getMessage(); + assertEquals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS, message); } @Test @@ -158,70 +151,47 @@ void getDefaultCredentials_noCredentials_singleGceTestRequest() { new MockRequestCountingTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException expected) { - String message = expected.getMessage(); - assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS)); - } + IOException expected = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = expected.getMessage(); + assertEquals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS, message); assertEquals( - transportFactory.transport.getRequestCount(), - ComputeEngineCredentials.MAX_COMPUTE_PING_TRIES); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException expected) { - // Expected - } + ComputeEngineCredentials.MAX_COMPUTE_PING_TRIES, + transportFactory.transport.getRequestCount()); + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); assertEquals( - transportFactory.transport.getRequestCount(), - ComputeEngineCredentials.MAX_COMPUTE_PING_TRIES); + ComputeEngineCredentials.MAX_COMPUTE_PING_TRIES, + transportFactory.transport.getRequestCount()); } @Test - void getDefaultCredentials_noCredentials_linuxNotGce() throws IOException { - TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.setProperty("os.name", "Linux"); - String productFilePath = SMBIOS_PATH_LINUX; - InputStream productStream = new ByteArrayInputStream("test".getBytes()); - testProvider.addFile(productFilePath, productStream); - - assertFalse(ComputeEngineCredentials.checkStaticGceDetection(testProvider)); + void getDefaultCredentials_noCredentials_linuxNotGce() { + checkStaticGceDetection("linux", "test", false); } @Test - void getDefaultCredentials_static_linux() throws IOException { - TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.setProperty("os.name", "Linux"); - String productFilePath = SMBIOS_PATH_LINUX; - File productFile = new File(productFilePath); - InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes()); - testProvider.addFile(productFile.getAbsolutePath(), productStream); - - assertTrue(ComputeEngineCredentials.checkStaticGceDetection(testProvider)); + void getDefaultCredentials_static_linux() { + checkStaticGceDetection("linux", "Googlekdjsfhg", true); } @Test - void getDefaultCredentials_static_windows_configuredAsLinux_notGce() throws IOException { - TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.setProperty("os.name", "windows"); - String productFilePath = SMBIOS_PATH_LINUX; - InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes()); - testProvider.addFile(productFilePath, productStream); - - assertFalse(ComputeEngineCredentials.checkStaticGceDetection(testProvider)); + void getDefaultCredentials_static_windows_configuredAsLinux_notGce() { + checkStaticGceDetection("windows", "Googlekdjsfhg", false); } @Test - void getDefaultCredentials_static_unsupportedPlatform_notGce() throws IOException { + void getDefaultCredentials_static_unsupportedPlatform_notGce() { + checkStaticGceDetection("macos", "Googlekdjsfhg", false); + } + + private void checkStaticGceDetection(String osName, String productContent, boolean expected) { TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.setProperty("os.name", "macos"); + testProvider.setProperty("os.name", osName); String productFilePath = SMBIOS_PATH_LINUX; - InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes()); + InputStream productStream = new ByteArrayInputStream(productContent.getBytes()); testProvider.addFile(productFilePath, productStream); - assertFalse(ComputeEngineCredentials.checkStaticGceDetection(testProvider)); + assertEquals(expected, ComputeEngineCredentials.checkStaticGceDetection(testProvider)); } @Test @@ -253,49 +223,40 @@ void getDefaultCredentials_caches() throws IOException { void getDefaultCredentials_appEngineClassWithoutRuntime_NotFoundError() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.addType( - DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockOffAppEngineSystemProperty.class); + testProvider.addType(MockOffAppEngineSystemProperty.class); testProvider.setProperty("isOnGAEStandard7", "true"); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected when not on App Engine."); - } catch (IOException e) { - String message = e.getMessage(); - assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS)); - } + IOException e = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = e.getMessage(); + assertEquals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS, message); } @Test void getDefaultCredentials_appEngineRuntimeWithoutClass_throwsHelpfulLoadError() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.addType( - DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockAppEngineSystemProperty.class); + testProvider.addType(MockAppEngineSystemProperty.class); testProvider.setProperty("isOnGAEStandard7", "true"); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("Credential expected to fail to load if credential class not present."); - } catch (IOException e) { - String message = e.getMessage(); - assertFalse(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS)); - assertTrue(message.contains("Check that the App Engine SDK is deployed.")); - } + IOException e = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = e.getMessage(); + assertNotEquals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS, message); + assertTrue(message.contains("Check that the App Engine SDK is deployed.")); } @Test void getDefaultCredentials_appEngineSkipWorks_retrievesCloudShellCredential() throws IOException { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - testProvider.addType( - DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockOffAppEngineSystemProperty.class); + testProvider.addType(MockOffAppEngineSystemProperty.class); testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "9090"); testProvider.setEnv(DefaultCredentialsProvider.SKIP_APP_ENGINE_ENV_VAR, "true"); testProvider.setProperty("isOnGAEStanadard7", "true"); GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof CloudShellCredentials); + assertInstanceOf(CloudShellCredentials.class, credentials); } @Test @@ -319,8 +280,8 @@ void getDefaultCredentials_cloudshell() throws IOException { GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); - assertTrue(defaultCredentials instanceof CloudShellCredentials); - assertEquals(((CloudShellCredentials) defaultCredentials).getAuthPort(), 4); + assertInstanceOf(CloudShellCredentials.class, defaultCredentials); + assertEquals(4, ((CloudShellCredentials) defaultCredentials).getAuthPort()); } @Test @@ -331,8 +292,8 @@ void getDefaultCredentials_cloudshell_withComputCredentialsPresent() throws IOEx GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); - assertTrue(defaultCredentials instanceof CloudShellCredentials); - assertEquals(((CloudShellCredentials) defaultCredentials).getAuthPort(), 4); + assertInstanceOf(CloudShellCredentials.class, defaultCredentials); + assertEquals(4, ((CloudShellCredentials) defaultCredentials).getAuthPort()); } @Test @@ -342,14 +303,11 @@ void getDefaultCredentials_envMissingFile_throws() { TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, invalidPath); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("Non existent credential should throw exception"); - } catch (IOException e) { - String message = e.getMessage(); - assertTrue(message.contains(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR)); - assertTrue(message.contains(invalidPath)); - } + IOException e = + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + String message = e.getMessage(); + assertTrue(message.contains(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR)); + assertTrue(message.contains(invalidPath)); } @Test @@ -382,7 +340,7 @@ void getDefaultCredentials_envUser_providesToken() throws IOException { testProvider.addFile(userPath, userStream); testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, userPath); - testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); + testUserProvidesToken(testProvider, USER_CLIENT_ID); } @Test @@ -390,7 +348,7 @@ void getDefaultCredentials_GdchServiceAccount() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, @@ -405,7 +363,7 @@ void getDefaultCredentials_GdchServiceAccount() throws IOException { GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(defaultCredentials); - assertTrue(defaultCredentials instanceof GdchCredentials); + assertInstanceOf(GdchCredentials.class, defaultCredentials); assertEquals(GDCH_SA_PROJECT_ID, defaultCredentials.getProjectId()); assertEquals( GDCH_SA_SERVICE_IDENTITY_NAME, @@ -418,7 +376,7 @@ void getDefaultCredentials_GdchServiceAccount() throws IOException { defaultCredentials = ((GdchCredentials) defaultCredentials).createWithGdchAudience(GDCH_SA_API_AUDIENCE); assertNotNull(defaultCredentials); - assertTrue(defaultCredentials instanceof GdchCredentials); + assertInstanceOf(GdchCredentials.class, defaultCredentials); assertEquals(GDCH_SA_PROJECT_ID, defaultCredentials.getProjectId()); assertEquals( GDCH_SA_SERVICE_IDENTITY_NAME, @@ -451,7 +409,7 @@ void getDefaultCredentials_quota_project() throws IOException { credentials = credentials.toBuilder().setQuotaProjectId(QUOTA_PROJECT_EXPLICIT).build(); assertEquals(QUOTA_PROJECT_EXPLICIT, credentials.getQuotaProjectId()); - testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); + testUserProvidesToken(testProvider, USER_CLIENT_ID); } @Test @@ -463,7 +421,7 @@ void getDefaultCredentials_compute_quotaProject() throws IOException { GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); - assertTrue(defaultCredentials instanceof ComputeEngineCredentials); + assertInstanceOf(ComputeEngineCredentials.class, defaultCredentials); assertEquals(QUOTA_PROJECT_FROM_ENVIRONMENT, defaultCredentials.getQuotaProjectId()); // verify metrics header @@ -482,44 +440,33 @@ void getDefaultCredentials_cloudshell_quotaProject() throws IOException { GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); - assertTrue(defaultCredentials instanceof CloudShellCredentials); + assertInstanceOf(CloudShellCredentials.class, defaultCredentials); assertEquals(QUOTA_PROJECT_FROM_ENVIRONMENT, defaultCredentials.getQuotaProjectId()); } @Test - void getDefaultCredentials_envNoGceCheck_noGceRequest() throws IOException { + void getDefaultCredentials_envNoGceCheck_noGceRequest() { MockRequestCountingTransportFactory transportFactory = new MockRequestCountingTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR, "true"); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException expected) { - // Expected - } - assertEquals(transportFactory.transport.getRequestCount(), 0); + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + assertEquals(0, transportFactory.transport.getRequestCount()); } @Test - void getDefaultCredentials_linuxSetup_envNoGceCheck_noGce() throws IOException { + void getDefaultCredentials_linuxSetup_envNoGceCheck_noGce() { MockRequestCountingTransportFactory transportFactory = new MockRequestCountingTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR, "true"); testProvider.setProperty("os.name", "Linux"); - String productFilePath = SMBIOS_PATH_LINUX; - File productFile = new File(productFilePath); + File productFile = new File(SMBIOS_PATH_LINUX); InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes()); testProvider.addFile(productFile.getAbsolutePath(), productStream); - try { - testProvider.getDefaultCredentials(transportFactory); - fail("No credential expected."); - } catch (IOException expected) { - // Expected - } - assertEquals(transportFactory.transport.getRequestCount(), 0); + assertThrows(IOException.class, () -> testProvider.getDefaultCredentials(transportFactory)); + assertEquals(0, transportFactory.transport.getRequestCount()); } @Test @@ -527,7 +474,7 @@ void getDefaultCredentials_envGceMetadataHost_setsMetadataServerUrl() { String testUrl = "192.0.2.0"; TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.GCE_METADATA_HOST_ENV_VAR, testUrl); - assertEquals(ComputeEngineCredentials.getMetadataServerUrl(testProvider), "http://" + testUrl); + assertEquals("http://" + testUrl, ComputeEngineCredentials.getMetadataServerUrl(testProvider)); } @Test @@ -536,8 +483,8 @@ void getDefaultCredentials_envGceMetadataHost_setsTokenServerUrl() { TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.GCE_METADATA_HOST_ENV_VAR, testUrl); assertEquals( - ComputeEngineCredentials.getTokenServerEncodedUrl(testProvider), - "http://" + testUrl + "/computeMetadata/v1/instance/service-accounts/default/token"); + "http://" + testUrl + "/computeMetadata/v1/instance/service-accounts/default/token", + ComputeEngineCredentials.getTokenServerEncodedUrl(testProvider)); } @Test @@ -552,7 +499,7 @@ void getDefaultCredentials_wellKnownFileEnv_providesToken() throws IOException { testProvider.setEnv("CLOUDSDK_CONFIG", cloudConfigDir.getAbsolutePath()); testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream); - testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); + testUserProvidesToken(testProvider, USER_CLIENT_ID); } @Test @@ -570,7 +517,7 @@ void getDefaultCredentials_wellKnownFileNonWindows_providesToken() throws IOExce testProvider.setProperty("user.home", homeDir.getAbsolutePath()); testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream); - testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); + testUserProvidesToken(testProvider, USER_CLIENT_ID); } @Test @@ -587,7 +534,7 @@ void getDefaultCredentials_wellKnownFileWindows_providesToken() throws IOExcepti testProvider.setEnv("APPDATA", homeDir.getAbsolutePath()); testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream); - testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); + testUserProvidesToken(testProvider, USER_CLIENT_ID); } @Test @@ -629,12 +576,12 @@ private String tempFilePath(String filename) { return Paths.get(System.getProperty("java.io.tmpdir"), filename).toString(); } - private class LogHandler extends Handler { + private static class LogHandler extends Handler { LogRecord lastRecord; @Override - public void publish(LogRecord record) { - lastRecord = record; + public void publish(LogRecord logRecord) { + this.lastRecord = logRecord; } public LogRecord getRecord() { @@ -642,10 +589,14 @@ public LogRecord getRecord() { } @Override - public void close() {} + public void close() { + // Test implementation. Nothing to close + } @Override - public void flush() {} + public void flush() { + // Test implementation. Nothing to flush + } } @Test @@ -653,8 +604,7 @@ void getDefaultCredentials_wellKnownFile_logsGcloudWarning() throws IOException LogRecord message = getCredentialsAndReturnLogMessage(false, true); assertNotNull(message); assertEquals(Level.WARNING, message.getLevel()); - assertTrue( - message.getMessage().equals(DefaultCredentialsProvider.CLOUDSDK_CREDENTIALS_WARNING)); + assertEquals(DefaultCredentialsProvider.CLOUDSDK_CREDENTIALS_WARNING, message.getMessage()); } @Test @@ -690,13 +640,12 @@ private LogRecord getCredentialsAndReturnLogMessage(boolean suppressWarning, boo testProvider.setProperty("os.name", "linux"); testProvider.setProperty("user.home", homeDir.getAbsolutePath()); if (isGce) { - String productFilePath = SMBIOS_PATH_LINUX; - File productFile = new File(productFilePath); + File productFile = new File(SMBIOS_PATH_LINUX); InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes()); testProvider.addFile(productFile.getAbsolutePath(), productStream); } testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream); - testUserProvidesToken(testProvider, GCLOUDSDK_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); + testUserProvidesToken(testProvider, GCLOUDSDK_CLIENT_ID); return handler.getRecord(); } @@ -704,15 +653,13 @@ private static File getTempDirectory() { return new File(System.getProperty("java.io.tmpdir")); } - private void testUserProvidesToken( - TestDefaultCredentialsProvider testProvider, - String clientId, - String clientSecret, - String refreshToken) + private void testUserProvidesToken(TestDefaultCredentialsProvider testProvider, String clientId) throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - transportFactory.transport.addClient(clientId, clientSecret); - transportFactory.transport.addRefreshToken(refreshToken, ACCESS_TOKEN); + transportFactory.transport.addClient( + clientId, DefaultCredentialsProviderTest.USER_CLIENT_SECRET); + transportFactory.transport.addRefreshToken( + DefaultCredentialsProviderTest.REFRESH_TOKEN, ACCESS_TOKEN); testUserProvidesToken(testProvider, transportFactory, ACCESS_TOKEN); } @@ -755,7 +702,7 @@ void getDefaultCredentials_envVarSet_serviceAccountCredentials_correctCredential GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof ServiceAccountCredentials); + assertInstanceOf(ServiceAccountCredentials.class, credentials); Map credentialInfo = credentials.getCredentialInfo(); assertEquals( String.format( @@ -780,7 +727,7 @@ void getDefaultCredentials_envVarSet_userCredential_correctCredentialInfo() thro GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof UserCredentials); + assertInstanceOf(UserCredentials.class, credentials); Map credentialInfo = credentials.getCredentialInfo(); assertEquals( String.format( @@ -808,7 +755,7 @@ void getDefaultCredentials_wellKnownFile_userCredential_correctCredentialInfo() GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof UserCredentials); + assertInstanceOf(UserCredentials.class, credentials); Map credentialInfo = credentials.getCredentialInfo(); assertEquals( String.format("Well Known File at %s", wellKnownFile.getCanonicalPath()), @@ -827,7 +774,7 @@ void getDefaultCredentials_computeEngineCredentials_defaultMDSUrl_correctCredent GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof ComputeEngineCredentials); + assertInstanceOf(ComputeEngineCredentials.class, credentials); Map credentialInfo = credentials.getCredentialInfo(); assertEquals( String.format( @@ -849,7 +796,7 @@ void getDefaultCredentials_computeEngineCredentials_customMDSUrl_correctCredenti GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof ComputeEngineCredentials); + assertInstanceOf(ComputeEngineCredentials.class, credentials); Map credentialInfo = credentials.getCredentialInfo(); assertEquals( String.format("Metadata Server URL set to http://%s", gceMetadataHost), @@ -868,7 +815,7 @@ private static class MockAppEngineSystemProperty { @SuppressWarnings("unused") public static final MockEnvironment environment = - new MockEnvironment(MockEnvironmentEnum.Production); + new MockEnvironment(MockEnvironmentEnum.PRODUCTION); } private static class MockOffAppEngineSystemProperty { @@ -878,13 +825,13 @@ private static class MockOffAppEngineSystemProperty { } private enum MockEnvironmentEnum { - Production, - Development; + PRODUCTION, + DEVELOPMENT; } public static class MockEnvironment { - private MockEnvironmentEnum innerValue; + private final MockEnvironmentEnum innerValue; MockEnvironment(MockEnvironmentEnum value) { this.innerValue = value; @@ -934,8 +881,8 @@ void addFile(String file, InputStream stream) { files.put(file, stream); } - void addType(String className, Class type) { - types.put(className, type); + void addType(Class type) { + types.put(DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, type); } @Override @@ -991,8 +938,8 @@ InputStream readStream(File file) throws FileNotFoundException { return stream; } - void setFileSandbox(boolean fileSandbox) { - this.fileSandbox = fileSandbox; + void setFileSandbox() { + this.fileSandbox = true; } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultPKCEProviderTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultPKCEProviderTest.java index 114e27298b3f..34e9be380841 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultPKCEProviderTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DefaultPKCEProviderTest.java @@ -54,11 +54,11 @@ void testPkceExpected() throws NoSuchAlgorithmException { String expectedCodeChallengeMethod = "S256"; assertEquals(pkce.getCodeChallenge(), expectedCodeChallenge); - assertEquals(pkce.getCodeChallengeMethod(), expectedCodeChallengeMethod); + assertEquals(expectedCodeChallengeMethod, pkce.getCodeChallengeMethod()); } @Test - void testNoBase64Padding() throws NoSuchAlgorithmException { + void testNoBase64Padding() { PKCEProvider pkce = new DefaultPKCEProvider(); assertFalse(pkce.getCodeChallenge().endsWith("=")); assertFalse(pkce.getCodeChallenge().contains("=")); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java index 31c4e776e3f8..34a52b4154fb 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java @@ -35,7 +35,7 @@ import static com.google.auth.oauth2.OAuth2Utils.TOKEN_EXCHANGE_URL_FORMAT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.api.client.http.HttpTransport; import com.google.auth.TestUtils; @@ -188,38 +188,25 @@ void refreshAccessToken_cantRefreshSourceCredentials_throws() throws IOException .setHttpTransportFactory(transportFactory) .build(); - try { - downscopedCredentials.refreshAccessToken(); - fail("Should fail as the source credential should not be able to be refreshed."); - } catch (IOException e) { - assertEquals("Unable to refresh the provided source credential.", e.getMessage()); - } + IOException e = assertThrows(IOException.class, downscopedCredentials::refreshAccessToken); + assertEquals("Unable to refresh the provided source credential.", e.getMessage()); } @Test void builder_noSourceCredential_throws() { - try { - DownscopedCredentials.newBuilder() - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setCredentialAccessBoundary(CREDENTIAL_ACCESS_BOUNDARY) - .build(); - fail("Should fail as the source credential is null."); - } catch (NullPointerException e) { - // Expected. - } + DownscopedCredentials.Builder builder = + DownscopedCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY); + assertThrows(NullPointerException.class, builder::build); } @Test void builder_noCredentialAccessBoundary_throws() throws IOException { - try { - DownscopedCredentials.newBuilder() - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setSourceCredential(getServiceAccountSourceCredentials(/* canRefresh= */ true)) - .build(); - fail("Should fail as no access boundary was provided."); - } catch (NullPointerException e) { - // Expected. - } + DownscopedCredentials.Builder builder = + DownscopedCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setSourceCredential(getServiceAccountSourceCredentials(/* canRefresh= */ true)); + assertThrows(NullPointerException.class, builder::build); } @Test @@ -263,37 +250,31 @@ void builder_universeDomainMismatch_throws() throws IOException { GoogleCredentials sourceCredentials = getServiceAccountSourceCredentials(/* canRefresh= */ true); - try { - DownscopedCredentials.newBuilder() - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setSourceCredential(sourceCredentials) - .setCredentialAccessBoundary(CREDENTIAL_ACCESS_BOUNDARY) - .setUniverseDomain("differentUniverseDomain") - .build(); - fail("Should fail with universe domain mismatch."); - } catch (IllegalArgumentException e) { - assertEquals( - "The downscoped credential's universe domain must be the same as the source credential.", - e.getMessage()); - } + DownscopedCredentials.Builder builder = + DownscopedCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setSourceCredential(sourceCredentials) + .setCredentialAccessBoundary(CREDENTIAL_ACCESS_BOUNDARY) + .setUniverseDomain("differentUniverseDomain"); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "The downscoped credential's universe domain must be the same as the source credential.", + e.getMessage()); } @Test - void builder_sourceUniverseDomainUnavailable_throws() throws IOException { + void builder_sourceUniverseDomainUnavailable_throws() { GoogleCredentials sourceCredentials = new MockSourceCredentialWithoutUniverseDomain(); - try { - DownscopedCredentials.newBuilder() - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setSourceCredential(sourceCredentials) - .setCredentialAccessBoundary(CREDENTIAL_ACCESS_BOUNDARY) - .build(); - fail("Should fail to retrieve source credential universe domain."); - } catch (IllegalStateException e) { - assertEquals( - "Error occurred when attempting to retrieve source credential universe domain.", - e.getMessage()); - } + DownscopedCredentials.Builder builder = + DownscopedCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setSourceCredential(sourceCredentials) + .setCredentialAccessBoundary(CREDENTIAL_ACCESS_BOUNDARY); + IllegalStateException e = assertThrows(IllegalStateException.class, builder::build); + assertEquals( + "Error occurred when attempting to retrieve source credential universe domain.", + e.getMessage()); } private static GoogleCredentials getServiceAccountSourceCredentials(boolean canRefresh) diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java index da7ea8cc6e5b..d9923e968a16 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java @@ -34,8 +34,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.json.GenericJson; import java.io.IOException; @@ -128,129 +128,107 @@ void constructor_validErrorResponse() throws IOException { } @Test - void constructor_errorResponseMissingCode_throws() throws IOException { + void constructor_errorResponseMissingCode_throws() { GenericJson jsonResponse = buildErrorResponse(); Object[] values = new Object[] {null, ""}; for (Object value : values) { jsonResponse.put("code", value); - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain " - + "`error` and `message` fields when unsuccessful.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain " + + "`error` and `message` fields when unsuccessful.", + exception.getMessage()); } } @Test - void constructor_errorResponseMissingMessage_throws() throws IOException { + void constructor_errorResponseMissingMessage_throws() { GenericJson jsonResponse = buildErrorResponse(); Object[] values = new Object[] {null, ""}; for (Object value : values) { jsonResponse.put("message", value); - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain " - + "`error` and `message` fields when unsuccessful.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain " + + "`error` and `message` fields when unsuccessful.", + exception.getMessage()); } } @Test - void constructor_successResponseMissingVersionField_throws() throws IOException { + void constructor_successResponseMissingVersionField_throws() { GenericJson jsonResponse = buildOidcResponse(); jsonResponse.remove("version"); - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " - + "`version` field.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`version` field.", + exception.getMessage()); } @Test - void constructor_successResponseMissingSuccessField_throws() throws Exception { + void constructor_successResponseMissingSuccessField_throws() { GenericJson jsonResponse = buildOidcResponse(); jsonResponse.remove("success"); - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " - + "`success` field.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`success` field.", + exception.getMessage()); } @Test - void constructor_successResponseMissingTokenTypeField_throws() throws IOException { + void constructor_successResponseMissingTokenTypeField_throws() { GenericJson jsonResponse = buildOidcResponse(); jsonResponse.remove("token_type"); - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " - + "`token_type` field.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`token_type` field.", + exception.getMessage()); } @Test - void constructor_samlResponseMissingSubjectToken_throws() throws IOException { + void constructor_samlResponseMissingSubjectToken_throws() { GenericJson jsonResponse = buildSamlResponse(); Object[] values = new Object[] {null, ""}; for (Object value : values) { jsonResponse.put("saml_response", value); - - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not " - + "contain a valid token.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not " + + "contain a valid token.", + exception.getMessage()); } } @Test - void constructor_oidcResponseMissingSubjectToken_throws() throws IOException { + void constructor_oidcResponseMissingSubjectToken_throws() { GenericJson jsonResponse = buildOidcResponse(); Object[] values = new Object[] {null, ""}; for (Object value : values) { jsonResponse.put("id_token", value); - try { - new ExecutableResponse(jsonResponse); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not " - + "contain a valid token.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows(PluggableAuthException.class, () -> new ExecutableResponse(jsonResponse)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not " + + "contain a valid token.", + exception.getMessage()); } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 4913e5aec53e..78bb6811953e 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -39,7 +39,6 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; @@ -51,12 +50,12 @@ import com.google.auth.oauth2.GoogleCredentials.GoogleCredentialsInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.io.BaseEncoding; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -84,8 +83,8 @@ class ExternalAccountAuthorizedUserCredentialsTest extends BaseSerializationTest private static final String BASIC_AUTH = String.format( "Basic %s", - BaseEncoding.base64() - .encode( + Base64.getEncoder() + .encodeToString( String.format("%s:%s", CLIENT_ID, CLIENT_SECRET) .getBytes(StandardCharsets.UTF_8))); @@ -246,70 +245,59 @@ void builder_accessTokenWithMissingRefreshFields() { @Test void builder_accessAndRefreshTokenNull_throws() { - try { - ExternalAccountAuthorizedUserCredentials.newBuilder().build(); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + ExternalAccountAuthorizedUserCredentials.Builder builder = + ExternalAccountAuthorizedUserCredentials.newBuilder(); + IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test void builder_missingTokenUrl_throws() { - try { - ExternalAccountAuthorizedUserCredentials.newBuilder() - .setRefreshToken(REFRESH_TOKEN) - .setClientId(CLIENT_ID) - .setClientSecret(CLIENT_SECRET) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + ExternalAccountAuthorizedUserCredentials.Builder builder = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setRefreshToken(REFRESH_TOKEN) + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET); + IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test void builder_missingClientId_throws() { - try { - ExternalAccountAuthorizedUserCredentials.newBuilder() - .setRefreshToken(REFRESH_TOKEN) - .setTokenUrl(TOKEN_URL) - .setClientSecret(CLIENT_SECRET) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + ExternalAccountAuthorizedUserCredentials.Builder builder = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setRefreshToken(REFRESH_TOKEN) + .setTokenUrl(TOKEN_URL) + .setClientSecret(CLIENT_SECRET); + IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test void builder_missingClientSecret_throws() { - try { - ExternalAccountAuthorizedUserCredentials.newBuilder() - .setRefreshToken(REFRESH_TOKEN) - .setTokenUrl(TOKEN_URL) - .setClientId(CLIENT_ID) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + ExternalAccountAuthorizedUserCredentials.Builder builder = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setRefreshToken(REFRESH_TOKEN) + .setTokenUrl(TOKEN_URL) + .setClientId(CLIENT_ID); + IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test @@ -421,84 +409,89 @@ void fromJson_minimumRequiredFieldsForRefresh() throws IOException { } @Test - void fromJson_accessTokenOnly_notSupported() throws IOException { + void fromJson_accessTokenOnly_notSupported() { GenericJson json = new GenericJson(); json.put("access_token", ACCESS_TOKEN); - try { - ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> + ExternalAccountAuthorizedUserCredentials.fromJson( + json, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test - void fromJson_missingRefreshToken_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("refresh_token"); - ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + void fromJson_missingRefreshToken_throws() { + GenericJson json = buildJsonCredentials(); + json.remove("refresh_token"); + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> + ExternalAccountAuthorizedUserCredentials.fromJson( + json, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test - void fromJson_missingTokenUrl_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("token_url"); - ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + void fromJson_missingTokenUrl_throws() { + GenericJson json = buildJsonCredentials(); + json.remove("token_url"); + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> + ExternalAccountAuthorizedUserCredentials.fromJson( + json, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test - void fromJson_missingClientId_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("client_id"); - ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + void fromJson_missingClientId_throws() { + GenericJson json = buildJsonCredentials(); + json.remove("client_id"); + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> + ExternalAccountAuthorizedUserCredentials.fromJson( + json, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test - void fromJson_missingClientSecret_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("client_secret"); - ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { - assertEquals( - "ExternalAccountAuthorizedUserCredentials must be initialized with " - + "an access token or fields to enable refresh: " - + "('refresh_token', 'token_url', 'client_id', 'client_secret').", - exception.getMessage()); - } + void fromJson_missingClientSecret_throws() { + GenericJson json = buildJsonCredentials(); + json.remove("client_secret"); + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> + ExternalAccountAuthorizedUserCredentials.fromJson( + json, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + assertEquals( + "ExternalAccountAuthorizedUserCredentials must be initialized with " + + "an access token or fields to enable refresh: " + + "('refresh_token', 'token_url', 'client_id', 'client_secret').", + exception.getMessage()); } @Test @@ -567,10 +560,11 @@ void fromStream_accessTokenOnly_notSupported() throws IOException { json.put("access_token", ACCESS_TOKEN); json.put( "type", GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType()); - try { - ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { + try (InputStream credentialsStream = TestUtils.jsonToInputStream(json)) { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> ExternalAccountAuthorizedUserCredentials.fromStream(credentialsStream)); assertEquals( "ExternalAccountAuthorizedUserCredentials must be initialized with " + "an access token or fields to enable refresh: " @@ -581,12 +575,13 @@ void fromStream_accessTokenOnly_notSupported() throws IOException { @Test void fromStream_missingRefreshToken_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("refresh_token"); - ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { + GenericJson json = buildJsonCredentials(); + json.remove("refresh_token"); + try (InputStream credentialsStream = TestUtils.jsonToInputStream(json)) { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> ExternalAccountAuthorizedUserCredentials.fromStream(credentialsStream)); assertEquals( "ExternalAccountAuthorizedUserCredentials must be initialized with " + "an access token or fields to enable refresh: " @@ -597,12 +592,13 @@ void fromStream_missingRefreshToken_throws() throws IOException { @Test void fromStream_missingTokenUrl_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("token_url"); - ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { + GenericJson json = buildJsonCredentials(); + json.remove("token_url"); + try (InputStream credentialsStream = TestUtils.jsonToInputStream(json)) { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> ExternalAccountAuthorizedUserCredentials.fromStream(credentialsStream)); assertEquals( "ExternalAccountAuthorizedUserCredentials must be initialized with " + "an access token or fields to enable refresh: " @@ -613,12 +609,13 @@ void fromStream_missingTokenUrl_throws() throws IOException { @Test void fromStream_missingClientId_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("client_id"); - ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { + GenericJson json = buildJsonCredentials(); + json.remove("client_id"); + try (InputStream credentialsStream = TestUtils.jsonToInputStream(json)) { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> ExternalAccountAuthorizedUserCredentials.fromStream(credentialsStream)); assertEquals( "ExternalAccountAuthorizedUserCredentials must be initialized with " + "an access token or fields to enable refresh: " @@ -629,12 +626,13 @@ void fromStream_missingClientId_throws() throws IOException { @Test void fromStream_missingClientSecret_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.remove("client_secret"); - ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should not be able to continue without exception."); - } catch (IllegalStateException exception) { + GenericJson json = buildJsonCredentials(); + json.remove("client_secret"); + try (InputStream credentialsStream = TestUtils.jsonToInputStream(json)) { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> ExternalAccountAuthorizedUserCredentials.fromStream(credentialsStream)); assertEquals( "ExternalAccountAuthorizedUserCredentials must be initialized with " + "an access token or fields to enable refresh: " @@ -664,12 +662,13 @@ void fromStream_missingUniverseDomain_defaults() throws IOException { @Test void fromStream_invalidInputStream_throws() throws IOException { - try { - GenericJson json = buildJsonCredentials(); - json.put("audience", new HashMap<>()); - ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should not be able to continue without exception."); - } catch (CredentialFormatException e) { + GenericJson json = buildJsonCredentials(); + json.put("audience", new HashMap<>()); + try (InputStream credentialsStream = TestUtils.jsonToInputStream(json)) { + CredentialFormatException e = + assertThrows( + CredentialFormatException.class, + () -> ExternalAccountAuthorizedUserCredentials.fromStream(credentialsStream)); assertEquals("Invalid input stream provided.", e.getMessage()); } } @@ -720,8 +719,8 @@ void getRequestMetadata_withQuotaProjectId() throws IOException { assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); assertEquals( - metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY), - Collections.singletonList(QUOTA_PROJECT)); + Collections.singletonList(QUOTA_PROJECT), + metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); validateAuthHeader(transportFactory.transport.getRequest()); } @@ -775,13 +774,9 @@ void refreshAccessToken_genericAuthError_throws() throws IOException { ExternalAccountAuthorizedUserCredentials credentials = ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory); - try { - credentials.refreshAccessToken(); - fail(""); - } catch (OAuthException e) { - assertEquals("invalid_request", e.getErrorCode()); - assertEquals("Invalid request.", e.getErrorDescription()); - } + OAuthException e = assertThrows(OAuthException.class, credentials::refreshAccessToken); + assertEquals("invalid_request", e.getErrorCode()); + assertEquals("Invalid request.", e.getErrorDescription()); } @Test @@ -791,11 +786,11 @@ void refreshAccessToken_genericIOError_throws() throws IOException { ExternalAccountAuthorizedUserCredentials credentials = ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory); - assertThrows(IOException.class, () -> credentials.refreshAccessToken()); + assertThrows(IOException.class, credentials::refreshAccessToken); } @Test - void refreshAccessToken_missingRefreshFields_throws() throws IOException { + void refreshAccessToken_missingRefreshFields_throws() { ExternalAccountAuthorizedUserCredentials credentials = ExternalAccountAuthorizedUserCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -805,7 +800,7 @@ void refreshAccessToken_missingRefreshFields_throws() throws IOException { .setHttpTransportFactory(transportFactory) .build(); - assertThrows(IllegalStateException.class, () -> credentials.refreshAccessToken()); + assertThrows(IllegalStateException.class, credentials::refreshAccessToken); } @Test @@ -1238,7 +1233,7 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } static GenericJson buildJsonCredentials() { diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index d45979cfb985..1338c0d68fe9 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -32,15 +32,18 @@ package com.google.auth.oauth2; import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonParser; import com.google.api.client.util.Clock; import com.google.auth.TestUtils; import com.google.auth.http.HttpTransportFactory; @@ -97,7 +100,7 @@ void fromStream_identityPoolCredentials() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); - assertTrue(credential instanceof IdentityPoolCredentials); + assertInstanceOf(IdentityPoolCredentials.class, credential); } @Test @@ -107,7 +110,7 @@ void fromStream_awsCredentials() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); - assertTrue(credential instanceof AwsCredentials); + assertInstanceOf(AwsCredentials.class, credential); } @Test @@ -117,32 +120,29 @@ void fromStream_pluggableAuthCredentials() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); } @Test - void fromStream_invalidStream_throws() throws IOException { + void fromStream_invalidStream_throws() { GenericJson json = buildJsonAwsCredential(); json.put("audience", new HashMap<>()); - try { - ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("Should fail."); - } catch (CredentialFormatException e) { - assertEquals("An invalid input stream was provided.", e.getMessage()); - } + CredentialFormatException e = + assertThrows( + CredentialFormatException.class, + () -> ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json))); + assertEquals("An invalid input stream was provided.", e.getMessage()); } @Test - void fromStream_nullTransport_throws() throws IOException { - try { - ExternalAccountCredentials.fromStream( - new ByteArrayInputStream("foo".getBytes()), /* transportFactory= */ null); - fail("NullPointerException should be thrown."); - } catch (NullPointerException e) { - // Expected. - } + void fromStream_nullTransport_throws() { + ByteArrayInputStream credentialsStream = new ByteArrayInputStream("foo".getBytes()); + assertThrows( + NullPointerException.class, + () -> + ExternalAccountCredentials.fromStream(credentialsStream, /* transportFactory= */ null)); } @Test @@ -164,36 +164,32 @@ void fromStream_nullOptionalField() throws IOException { } @Test - void fromStream_nullStream_throws() throws IOException { - try { - ExternalAccountCredentials.fromStream( - /* credentialsStream= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("NullPointerException should be thrown."); - } catch (NullPointerException e) { - // Expected. - } + void fromStream_nullStream_throws() { + assertThrows( + NullPointerException.class, + () -> + ExternalAccountCredentials.fromStream( + /* credentialsStream= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); } @Test - void fromStream_invalidWorkloadAudience_throws() throws IOException { - try { - GenericJson json = buildJsonIdentityPoolWorkforceCredential(); - json.put("audience", "invalidAudience"); - ExternalAccountCredentials credential = - ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); - fail("CredentialFormatException should be thrown."); - } catch (CredentialFormatException e) { - assertEquals("An invalid input stream was provided.", e.getMessage()); - } + void fromStream_invalidWorkloadAudience_throws() { + GenericJson json = buildJsonIdentityPoolWorkforceCredential(); + json.put("audience", "invalidAudience"); + CredentialFormatException e = + assertThrows( + CredentialFormatException.class, + () -> ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json))); + assertEquals("An invalid input stream was provided.", e.getMessage()); } @Test - void fromJson_identityPoolCredentialsWorkload() throws IOException { + void fromJson_identityPoolCredentialsWorkload() { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonIdentityPoolCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof IdentityPoolCredentials); + assertInstanceOf(IdentityPoolCredentials.class, credential); assertEquals( "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", credential.getAudience()); @@ -205,12 +201,12 @@ void fromJson_identityPoolCredentialsWorkload() throws IOException { } @Test - void fromJson_identityPoolCredentialsWorkforce() throws IOException { + void fromJson_identityPoolCredentialsWorkforce() { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonIdentityPoolWorkforceCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof IdentityPoolCredentials); + assertInstanceOf(IdentityPoolCredentials.class, credential); assertEquals( "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider", credential.getAudience()); @@ -223,16 +219,16 @@ void fromJson_identityPoolCredentialsWorkforce() throws IOException { } @Test - void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions() throws IOException { + void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions() { GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); identityPoolCredentialJson.set( - "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); + "service_account_impersonation", buildServiceAccountImpersonationOptions()); ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( identityPoolCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof IdentityPoolCredentials); + assertInstanceOf(IdentityPoolCredentials.class, credential); assertEquals( "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", credential.getAudience()); @@ -245,7 +241,7 @@ void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions() th } @Test - void fromJson_identityPoolCredentialsWithUniverseDomain() throws IOException { + void fromJson_identityPoolCredentialsWithUniverseDomain() { GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); identityPoolCredentialJson.set("universe_domain", "universeDomain"); @@ -253,7 +249,7 @@ void fromJson_identityPoolCredentialsWithUniverseDomain() throws IOException { ExternalAccountCredentials.fromJson( identityPoolCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof IdentityPoolCredentials); + assertInstanceOf(IdentityPoolCredentials.class, credential); assertNotNull(credential.getCredentialSource()); assertEquals( "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", @@ -265,12 +261,12 @@ void fromJson_identityPoolCredentialsWithUniverseDomain() throws IOException { } @Test - void fromJson_awsCredentials() throws IOException { + void fromJson_awsCredentials() { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonAwsCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof AwsCredentials); + assertInstanceOf(AwsCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -280,15 +276,15 @@ void fromJson_awsCredentials() throws IOException { } @Test - void fromJson_awsCredentialsWithServiceAccountImpersonationOptions() throws IOException { + void fromJson_awsCredentialsWithServiceAccountImpersonationOptions() { GenericJson awsCredentialJson = buildJsonAwsCredential(); awsCredentialJson.set( - "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); + "service_account_impersonation", buildServiceAccountImpersonationOptions()); ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson(awsCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof AwsCredentials); + assertInstanceOf(AwsCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -299,14 +295,14 @@ void fromJson_awsCredentialsWithServiceAccountImpersonationOptions() throws IOEx } @Test - void fromJson_awsCredentialsWithUniverseDomain() throws IOException { + void fromJson_awsCredentialsWithUniverseDomain() { GenericJson awsCredentialJson = buildJsonAwsCredential(); awsCredentialJson.set("universe_domain", "universeDomain"); ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson(awsCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof AwsCredentials); + assertInstanceOf(AwsCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -316,12 +312,12 @@ void fromJson_awsCredentialsWithUniverseDomain() throws IOException { } @Test - void fromJson_pluggableAuthCredentials() throws IOException { + void fromJson_pluggableAuthCredentials() { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonPluggableAuthCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -337,12 +333,12 @@ void fromJson_pluggableAuthCredentials() throws IOException { } @Test - void fromJson_pluggableAuthCredentialsWorkforce() throws IOException { + void fromJson_pluggableAuthCredentialsWorkforce() { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonPluggableAuthWorkforceCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); assertEquals( "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider", credential.getAudience()); @@ -363,7 +359,7 @@ void fromJson_pluggableAuthCredentialsWorkforce() throws IOException { @Test @SuppressWarnings("unchecked") - void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() throws IOException { + void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() { GenericJson json = buildJsonPluggableAuthCredential(); Map credentialSourceMap = (Map) json.get("credential_source"); // Add optional params to the executable config (timeout, output file path). @@ -375,7 +371,7 @@ void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() throws IOExcept ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -391,17 +387,16 @@ void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() throws IOExcept } @Test - void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions() - throws IOException { + void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions() { GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); pluggableAuthCredentialJson.set( - "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); + "service_account_impersonation", buildServiceAccountImpersonationOptions()); ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( pluggableAuthCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -419,7 +414,7 @@ void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions() @Test @SuppressWarnings("unchecked") - void fromJson_pluggableAuthCredentials_withUniverseDomain() throws IOException { + void fromJson_pluggableAuthCredentials_withUniverseDomain() { GenericJson json = buildJsonPluggableAuthCredential(); json.set("universe_domain", "universeDomain"); @@ -433,7 +428,7 @@ void fromJson_pluggableAuthCredentials_withUniverseDomain() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -449,7 +444,7 @@ void fromJson_pluggableAuthCredentials_withUniverseDomain() throws IOException { } @Test - void fromJson_pluggableAuthCredentialsWithUniverseDomain() throws IOException { + void fromJson_pluggableAuthCredentialsWithUniverseDomain() { GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); pluggableAuthCredentialJson.set("universe_domain", "universeDomain"); @@ -457,7 +452,7 @@ void fromJson_pluggableAuthCredentialsWithUniverseDomain() throws IOException { ExternalAccountCredentials.fromJson( pluggableAuthCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - assertTrue(credential instanceof PluggableAuthCredentials); + assertInstanceOf(PluggableAuthCredentials.class, credential); assertEquals("audience", credential.getAudience()); assertEquals("subjectTokenType", credential.getSubjectTokenType()); assertEquals(STS_URL, credential.getTokenUrl()); @@ -473,31 +468,26 @@ void fromJson_pluggableAuthCredentialsWithUniverseDomain() throws IOException { } @Test - void fromJson_nullJson_throws() throws IOException { - try { - ExternalAccountCredentials.fromJson(/* json= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Exception should be thrown."); - } catch (NullPointerException e) { - // Expected. - } + void fromJson_nullJson_throws() { + assertThrows( + NullPointerException.class, + () -> + ExternalAccountCredentials.fromJson( + /* json= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); } @Test - void fromJson_nullTransport_throws() throws IOException { - try { - ExternalAccountCredentials.fromJson( - new HashMap(), /* transportFactory= */ null); - fail("Exception should be thrown."); - } catch (NullPointerException e) { - // Expected. - } + void fromJson_nullTransport_throws() { + HashMap json = new HashMap<>(); + assertThrows(NullPointerException.class, () -> ExternalAccountCredentials.fromJson(json, null)); } @Test - void fromJson_invalidWorkforceAudiences_throws() throws IOException { + void fromJson_invalidWorkforceAudiences_throws() { List invalidAudiences = Arrays.asList( - "//iam.googleapis.com/locations/global/workloadIdentityPools/pool/providers/provider", + "", + "//iam.googleapis.com/projects/x23/locations/global/workloadIdentityPools/pool/providers/provider", "//iam.googleapis.com/locations/global/workforcepools/pool/providers/provider", "//iam.googleapis.com/locations/global/workforcePools/providers/provider", "//iam.googleapis.com/locations/global/workforcePools/providers", @@ -507,23 +497,25 @@ void fromJson_invalidWorkforceAudiences_throws() throws IOException { "//iam.googleapis.com/locations/global/workforce/providers"); for (String audience : invalidAudiences) { - try { - GenericJson json = buildJsonIdentityPoolCredential(); - json.put("audience", audience); - json.put("workforce_pool_user_project", "userProject"); - - ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals( - "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", - e.getMessage()); - } + GenericJson json = buildJsonIdentityPoolCredential(); + json.put("audience", audience); + json.put("workforce_pool_user_project", "userProject"); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> + ExternalAccountCredentials.fromJson( + json, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + assertEquals( + "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", + e.getMessage()); } } @Test - void constructor_builder() throws IOException { + void constructor_builder() { HashMap credentialSource = new HashMap<>(); credentialSource.put("file", "file"); @@ -617,37 +609,37 @@ void constructor_builder_subjectTokenTypeEnum() { @Test void constructor_builder_invalidTokenUrl() { - try { - ExternalAccountCredentials.Builder builder = - TestExternalAccountCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience("audience") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl("tokenUrl") - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)); - new TestExternalAccountCredentials(builder); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals("The provided token URL is invalid.", exception.getMessage()); - } + ExternalAccountCredentials.Builder builder = + TestExternalAccountCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl("tokenUrl") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> new TestExternalAccountCredentials(builder)); + assertEquals("The provided token URL is invalid.", exception.getMessage()); } @Test void constructor_builder_invalidServiceAccountImpersonationUrl() { - try { - ExternalAccountCredentials.Builder builder = - TestExternalAccountCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience("audience") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl("tokenUrl") - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .setServiceAccountImpersonationUrl("serviceAccountImpersonationUrl"); - new TestExternalAccountCredentials(builder); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals("The provided token URL is invalid.", exception.getMessage()); - } + ExternalAccountCredentials.Builder builder = + TestExternalAccountCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl("tokenUrl") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setServiceAccountImpersonationUrl("serviceAccountImpersonationUrl"); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> new TestExternalAccountCredentials(builder)); + assertEquals("The provided token URL is invalid.", exception.getMessage()); } @Test @@ -667,21 +659,22 @@ void constructor_builderWithInvalidWorkforceAudiences_throws() { HashMap credentialSource = new HashMap<>(); credentialSource.put("file", "file"); for (String audience : invalidAudiences) { - try { - TestExternalAccountCredentials.newBuilder() - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience(audience) - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setCredentialSource(new TestCredentialSource(credentialSource)) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", - exception.getMessage()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> + TestExternalAccountCredentials.newBuilder() + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience(audience) + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setCredentialSource(new TestCredentialSource(credentialSource)) + .build()); + assertEquals( + "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", + exception.getMessage()); } } @@ -690,14 +683,16 @@ void constructor_builderWithEmptyWorkforceUserProjectAndWorkforceAudience() { HashMap credentialSource = new HashMap<>(); credentialSource.put("file", "file"); // No exception should be thrown. - TestExternalAccountCredentials.newBuilder() - .setWorkforcePoolUserProject("") - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience("//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setCredentialSource(new TestCredentialSource(credentialSource)) - .build(); + ExternalAccountCredentials.Builder builder = + TestExternalAccountCredentials.newBuilder() + .setWorkforcePoolUserProject("") + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience( + "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setCredentialSource(new TestCredentialSource(credentialSource)); + assertDoesNotThrow(builder::build); } @Test @@ -705,31 +700,32 @@ void constructor_builder_invalidTokenLifetime_throws() { Map invalidOptionsMap = new HashMap(); invalidOptionsMap.put("token_lifetime_seconds", "thisIsAString"); - try { - IdentityPoolCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience( - "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("https://tokeninfo.com") - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .setScopes(Arrays.asList("scope1", "scope2")) - .setQuotaProjectId("projectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setUniverseDomain("universeDomain") - .setServiceAccountImpersonationOptions(invalidOptionsMap) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "Value of \"token_lifetime_seconds\" field could not be parsed into an integer.", - exception.getMessage()); - assertEquals(NumberFormatException.class, exception.getCause().getClass()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("https://tokeninfo.com") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setScopes(Arrays.asList("scope1", "scope2")) + .setQuotaProjectId("projectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") + .setServiceAccountImpersonationOptions(invalidOptionsMap) + .build()); + assertEquals( + "Value of \"token_lifetime_seconds\" field could not be parsed into an integer.", + exception.getMessage()); + assertEquals(NumberFormatException.class, exception.getCause().getClass()); } @Test @@ -789,7 +785,7 @@ void constructor_builder_bigDecimalTokenLifetime() { @Test void constructor_builder_integerTokenLifetime() { Map optionsMap = new HashMap(); - optionsMap.put("token_lifetime_seconds", Integer.valueOf(2800)); + optionsMap.put("token_lifetime_seconds", 2800); ExternalAccountCredentials credentials = IdentityPoolCredentials.newBuilder() @@ -818,59 +814,61 @@ void constructor_builder_lowTokenLifetime_throws() { Map optionsMap = new HashMap(); optionsMap.put("token_lifetime_seconds", 599); - try { - IdentityPoolCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience( - "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("https://tokeninfo.com") - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .setScopes(Arrays.asList("scope1", "scope2")) - .setQuotaProjectId("projectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setUniverseDomain("universeDomain") - .setServiceAccountImpersonationOptions(optionsMap) - .build(); - } catch (IllegalArgumentException e) { - assertEquals( - "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", - e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("https://tokeninfo.com") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setScopes(Arrays.asList("scope1", "scope2")) + .setQuotaProjectId("projectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") + .setServiceAccountImpersonationOptions(optionsMap) + .build()); + assertEquals( + "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", + e.getMessage()); } @Test void constructor_builder_highTokenLifetime_throws() { - Map optionsMap = new HashMap(); + Map optionsMap = new HashMap<>(); optionsMap.put("token_lifetime_seconds", 43201); - try { - IdentityPoolCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience( - "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("https://tokeninfo.com") - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .setScopes(Arrays.asList("scope1", "scope2")) - .setQuotaProjectId("projectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setUniverseDomain("universeDomain") - .setServiceAccountImpersonationOptions(optionsMap) - .build(); - } catch (IllegalArgumentException e) { - assertEquals( - "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", - e.getMessage()); - } + IdentityPoolCredentials.Builder builder = + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("https://tokeninfo.com") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setScopes(Arrays.asList("scope1", "scope2")) + .setQuotaProjectId("projectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain"); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> builder.setServiceAccountImpersonationOptions(optionsMap)); + assertEquals( + "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", + e.getMessage()); } @Test @@ -1006,17 +1004,18 @@ void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonation() transportFactory.transport.getServiceAccountAccessToken(), returnedToken.getTokenValue()); // Validate that default lifetime was set correctly on the request. - GenericJson query = - OAuth2Utils.JSON_FACTORY - .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString()) - .parseAndClose(GenericJson.class); + try (JsonParser jsonParser = + OAuth2Utils.JSON_FACTORY.createJsonParser( + transportFactory.transport.getLastRequest().getContentAsString())) { + GenericJson query = jsonParser.parseAndClose(GenericJson.class); - assertEquals("3600s", query.get("lifetime")); + assertEquals("3600s", query.get("lifetime")); - // Validate metrics header is set correctly on the sts request. - Map> headers = - transportFactory.transport.getRequests().get(1).getHeaders(); - validateMetricsHeader(headers, "url", true, false); + // Validate metrics header is set correctly on the sts request. + Map> headers = + transportFactory.transport.getRequests().get(1).getHeaders(); + validateMetricsHeader(headers, "url", true, false); + } } @Test @@ -1030,7 +1029,7 @@ void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonationOpt transportFactory.transport.getStsUrl(), transportFactory.transport.getMetadataUrl(), transportFactory.transport.getServiceAccountImpersonationUrl(), - buildServiceAccountImpersonationOptions(2800)), + buildServiceAccountImpersonationOptions()), transportFactory); StsTokenExchangeRequest stsTokenExchangeRequest = @@ -1043,16 +1042,16 @@ void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonationOpt transportFactory.transport.getServiceAccountAccessToken(), returnedToken.getTokenValue()); // Validate that lifetime was set correctly on the request. - GenericJson query = - OAuth2Utils.JSON_FACTORY - .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString()) - .parseAndClose(GenericJson.class); - - // Validate metrics header is set correctly on the sts request. - Map> headers = - transportFactory.transport.getRequests().get(1).getHeaders(); - validateMetricsHeader(headers, "url", true, true); - assertEquals("2800s", query.get("lifetime")); + try (JsonParser jsonParser = + OAuth2Utils.JSON_FACTORY.createJsonParser( + transportFactory.transport.getLastRequest().getContentAsString())) { + GenericJson query = jsonParser.parseAndClose(GenericJson.class); + // Validate metrics header is set correctly on the sts request. + Map> headers = + transportFactory.transport.getRequests().get(1).getHeaders(); + validateMetricsHeader(headers, "url", true, true); + assertEquals("2800s", query.get("lifetime")); + } } @Test @@ -1069,19 +1068,18 @@ void exchangeExternalCredentialForAccessToken_throws() throws IOException { StsTokenExchangeRequest stsTokenExchangeRequest = StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); - try { - credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); - fail("Exception should be thrown."); - } catch (OAuthException e) { - assertEquals(errorCode, e.getErrorCode()); - assertEquals(errorDescription, e.getErrorDescription()); - assertEquals(errorUri, e.getErrorUri()); - } + OAuthException e = + assertThrows( + OAuthException.class, + (org.junit.jupiter.api.function.Executable) + () -> credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest)); + assertEquals(errorCode, e.getErrorCode()); + assertEquals(errorDescription, e.getErrorDescription()); + assertEquals(errorUri, e.getErrorUri()); } @Test - void exchangeExternalCredentialForAccessToken_invalidImpersonatedCredentialsThrows() - throws IOException { + void exchangeExternalCredentialForAccessToken_invalidImpersonatedCredentialsThrows() { GenericJson json = buildJsonIdentityPoolCredential(); json.put("service_account_impersonation_url", "https://iamcredentials.googleapis.com"); ExternalAccountCredentials credential = @@ -1090,14 +1088,13 @@ void exchangeExternalCredentialForAccessToken_invalidImpersonatedCredentialsThro StsTokenExchangeRequest stsTokenExchangeRequest = StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); - try { - credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals( - "Unable to determine target principal from service account impersonation URL.", - e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest)); + assertEquals( + "Unable to determine target principal from service account impersonation URL.", + e.getMessage()); } @Test @@ -1147,7 +1144,7 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals( testCredentials.getServiceAccountImpersonationOptions().getLifetime(), deserializedCredentials.getServiceAccountImpersonationOptions().getLifetime()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); assertEquals( MockExternalAccountCredentialsTransportFactory.class, deserializedCredentials.toBuilder().getHttpTransportFactory().getClass()); @@ -1189,12 +1186,11 @@ void validateTokenUrl_invalidUrls() { "https://us- -1.sts.googleapis.com"); for (String url : invalidUrls) { - try { - ExternalAccountCredentials.validateTokenUrl(url); - fail("Should have failed since an invalid URL was passed."); - } catch (IllegalArgumentException e) { - assertEquals("The provided token URL is invalid.", e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> ExternalAccountCredentials.validateTokenUrl(url)); + assertEquals("The provided token URL is invalid.", e.getMessage()); } } @@ -1235,12 +1231,12 @@ void validateServiceAccountImpersonationUrls_invalidUrls() { "https://us- -1.iamcredentials.googleapis.com"); for (String url : invalidUrls) { - try { - ExternalAccountCredentials.validateServiceAccountImpersonationInfoUrl(url); - fail("Should have failed since an invalid URL was passed."); - } catch (IllegalArgumentException e) { - assertEquals("The provided service account impersonation URL is invalid.", e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + (org.junit.jupiter.api.function.Executable) + () -> ExternalAccountCredentials.validateServiceAccountImpersonationInfoUrl(url)); + assertEquals("The provided service account impersonation URL is invalid.", e.getMessage()); } } @@ -1318,10 +1314,9 @@ private GenericJson buildJsonPluggableAuthWorkforceCredential() { return json; } - static Map buildServiceAccountImpersonationOptions(Integer lifetime) { + static Map buildServiceAccountImpersonationOptions() { Map map = new HashMap(); - map.put("token_lifetime_seconds", lifetime); - + map.put("token_lifetime_seconds", 2800); return map; } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java index b555149167f1..ace74dd67e63 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java @@ -32,18 +32,21 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.json.GenericJson; +import com.google.api.client.json.Json; import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebSignature; import com.google.api.client.json.webtoken.JsonWebToken; import com.google.api.client.testing.http.FixedClock; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.auth.TestUtils; import com.google.auth.oauth2.GoogleCredentials.GoogleCredentialsInfo; @@ -52,27 +55,35 @@ import java.io.InputStream; import java.net.URI; import java.nio.file.Files; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.spec.ECGenParameterSpec; +import java.util.Base64; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; /** Test case for {@link GdchCredentials}. */ class GdchCredentialsTest extends BaseSerializationTest { - private static final String FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION; + private static final String FORMAT_VERSION = GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION; private static final String PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; static final String PRIVATE_KEY_PKCS8 = "-----BEGIN PRIVATE KEY-----\n" - + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i" - + "kv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0" - + "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw" - + "4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr" - + "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6" - + "D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP" - + "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAut" - + "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA" - + "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ" - + "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ" - + "==\n-----END PRIVATE KEY-----\n"; + + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyITXsUvRm1C3lnyz\n" + + "OaMY7TNXZois4NH0bkMwqTAnVbqhRANCAASk5+U9skHVTo+sEVd2/yKY7A2eYn8K\n" + + "Cygd3bQalfWs533aTu93XwVx0YNN310aFquv3/VIiFofm1JEBAhUiG8e\n" + + "-----END PRIVATE KEY-----"; + + // Generated using OpenSSL: + // openssl ecparam -name prime256v1 -genkey -noout -out ec-sec1.pem + static final String PRIVATE_KEY_SEC1 = + "-----BEGIN EC PRIVATE KEY-----\n" + + "MHcCAQEEIK+G0KyJyJPDK/tyYsF0RyFW+X1GMsWbrlWn8TbLAI0doAoGCCqGSM49\n" + + "AwEHoUQDQgAEmGMJNcYyb9IfS4ngfvSf+c0sxOdcRfPNnZajry4bLgs++2VpQn8e\n" + + "l27zuFvF8jrM2/XyG5y9heE8YSjfLMy0Rw==\n" + + "-----END EC PRIVATE KEY-----"; private static final String PROJECT_ID = "project-id"; private static final String SERVICE_IDENTITY_NAME = "service-identity-name"; private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; @@ -81,7 +92,7 @@ class GdchCredentialsTest extends BaseSerializationTest { private static final String CA_CERT_FILE_NAME = "cert.pem"; private static final String CA_CERT_PATH = GdchCredentialsTest.class.getClassLoader().getResource(CA_CERT_FILE_NAME).getPath(); - private static final URI API_AUDIENCE = URI.create("https://gdch-api-audience"); + private static final String API_AUDIENCE = "https://gdch-api-audience"; private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); @Test @@ -100,6 +111,23 @@ void fromJSON_getProjectId() throws IOException { assertEquals(PROJECT_ID, credentials.getProjectId()); } + @Test + void fromJSON_sec1Key() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_SEC1, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + assertNotNull(credentials); + assertEquals(PROJECT_ID, credentials.getProjectId()); + } + @Test void fromJSON_getServiceIdentityName() throws IOException { GenericJson json = @@ -149,7 +177,7 @@ void fromJSON_getTokenServerUri() throws IOException { } @Test - void fromJSON_nullFormatVersion() throws IOException { + void fromJSON_nullFormatVersion() { GenericJson json = writeGdchServiceAccountJson( null, @@ -160,22 +188,18 @@ void fromJSON_nullFormatVersion() throws IOException { CA_CERT_PATH, TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains( - String.format( - "Error reading GDCH service account credential from JSON, " - + "%s is misconfigured.", - "format_version"))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "format_version"))); } @Test - void fromJSON_nullProjectId() throws IOException { + void fromJSON_nullProjectId() { GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -186,22 +210,18 @@ void fromJSON_nullProjectId() throws IOException { CA_CERT_PATH, TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains( - String.format( - "Error reading GDCH service account credential from JSON, " - + "%s is misconfigured.", - "project"))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "project"))); } @Test - void fromJSON_nullPrivateKeyId() throws IOException { + void fromJSON_nullPrivateKeyId() { GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -212,22 +232,18 @@ void fromJSON_nullPrivateKeyId() throws IOException { CA_CERT_PATH, TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains( - String.format( - "Error reading GDCH service account credential from JSON, " - + "%s is misconfigured.", - "private_key_id"))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "private_key_id"))); } @Test - void fromJSON_nullPrivateKey() throws IOException { + void fromJSON_nullPrivateKey() { GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -238,22 +254,18 @@ void fromJSON_nullPrivateKey() throws IOException { CA_CERT_PATH, TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains( - String.format( - "Error reading GDCH service account credential from JSON, " - + "%s is misconfigured.", - "private_key"))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "private_key"))); } @Test - void fromJSON_nullServiceIdentityName() throws IOException { + void fromJSON_nullServiceIdentityName() { GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -264,18 +276,14 @@ void fromJSON_nullServiceIdentityName() throws IOException { CA_CERT_PATH, TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains( - String.format( - "Error reading GDCH service account credential from JSON, " - + "%s is misconfigured.", - "name"))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "name"))); } @Test @@ -294,7 +302,7 @@ void fromJSON_nullCaCertPath() throws IOException { } @Test - void fromJSON_nullTokenServerUri() throws IOException { + void fromJSON_nullTokenServerUri() { GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -305,22 +313,18 @@ void fromJSON_nullTokenServerUri() throws IOException { CA_CERT_PATH, null); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains( - String.format( - "Error reading GDCH service account credential from JSON, " - + "%s is misconfigured.", - "token_uri"))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "token_uri"))); } @Test - void fromJSON_invalidFormatVersion() throws IOException { + void fromJSON_invalidFormatVersion() { GenericJson json = writeGdchServiceAccountJson( "100", @@ -331,18 +335,14 @@ void fromJSON_invalidFormatVersion() throws IOException { CA_CERT_PATH, TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue( - ex.getMessage() - .contains(String.format("Only format version %s is supported", FORMAT_VERSION))); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue( + ex.getMessage() + .contains(String.format("Only format version %s is supported", FORMAT_VERSION))); } @Test - void fromJSON_invalidCaCertPath() throws IOException { + void fromJSON_invalidCaCertPath() { GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -353,12 +353,8 @@ void fromJSON_invalidCaCertPath() throws IOException { "/path/to/missing/file", TOKEN_SERVER_URI); - try { - GdchCredentials credentials = GdchCredentials.fromJson(json); - fail("Should not be able to create GDCH credential without exception."); - } catch (IOException ex) { - assertTrue(ex.getMessage().contains("Error reading certificate file from CA cert path")); - } + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromJson(json)); + assertTrue(ex.getMessage().contains("Error reading certificate file from CA cert path")); } @Test @@ -414,6 +410,73 @@ void fromJSON_hasAccessToken() throws IOException { TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } + @Test + void fromStream_correct() throws IOException { + InputStream stream = + writeGdchServiceAccountStream( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromStream(stream); + + assertEquals(PROJECT_ID, credentials.getProjectId()); + assertEquals(SERVICE_IDENTITY_NAME, credentials.getServiceIdentityName()); + } + + @Test + void fromStream_invalidType() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + json.put("type", "invalid_type"); + InputStream stream = TestUtils.jsonToInputStream(json); + + IOException ex = assertThrows(IOException.class, () -> GdchCredentials.fromStream(stream)); + assertTrue(ex.getMessage().contains("not recognized")); + } + + @Test + void fromStream_withTransportFactory() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + InputStream stream = + writeGdchServiceAccountStream( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromStream(stream, transportFactory); + + assertEquals(transportFactory, credentials.getTransportFactory()); + } + + @Test + void fromPem_correct() throws IOException { + GdchCredentials.Builder builder = + GdchCredentials.newBuilder() + .setProjectId(PROJECT_ID) + .setPrivateKeyId(PRIVATE_KEY_ID) + .setServiceIdentityName(SERVICE_IDENTITY_NAME) + .setTokenServerUri(TOKEN_SERVER_URI) + .setHttpTransportFactory(new MockTokenServerTransportFactory()); + + GdchCredentials credentials = GdchCredentials.fromPem(PRIVATE_KEY_PKCS8, builder); + assertNotNull(credentials.getPrivateKey()); + assertEquals(PROJECT_ID, credentials.getProjectId()); + } + @Test void createWithGdchAudience_correct() throws IOException { GenericJson json = @@ -439,7 +502,7 @@ void createWithGdchAudience_correct() throws IOException { assertEquals(SERVICE_IDENTITY_NAME, gdchWithAudience.getServiceIdentityName()); assertEquals(TOKEN_SERVER_URI, gdchWithAudience.getTokenServerUri()); assertEquals(CA_CERT_PATH, credentials.getCaCertPath()); - assertEquals(API_AUDIENCE, gdchWithAudience.getApiAudience()); + assertEquals(API_AUDIENCE, gdchWithAudience.getGdchAudience()); } @Test @@ -455,12 +518,55 @@ void createWithGdchAudience_nullApiAudience() throws IOException { TOKEN_SERVER_URI); GdchCredentials credentials = GdchCredentials.fromJson(json); - try { - GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(null); - fail("Should not be able to create GDCH credential without exception."); - } catch (NullPointerException ex) { - assertTrue(ex.getMessage().contains("Audience are not configured for GDCH service account")); - } + IllegalArgumentException ex = + assertThrows( + IllegalArgumentException.class, + () -> credentials.createWithGdchAudience((String) null)); + assertTrue( + ex.getMessage().contains("Audience cannot be null or empty for GDCH service account")); + } + + @Test + void createWithGdchAudience_emptyApiAudience() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> credentials.createWithGdchAudience("")); + assertTrue( + ex.getMessage().contains("Audience cannot be null or empty for GDCH service account")); + } + + @Test + void getGdchAudience_vs_getApiAudience() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + String validUri = "https://valid-audience.com"; + GdchCredentials validCredentials = credentials.createWithGdchAudience(validUri); + assertEquals(validUri, validCredentials.getGdchAudience()); + assertEquals(URI.create(validUri), validCredentials.getApiAudience()); + + String invalidUri = "invalid uri ^"; + GdchCredentials invalidCredentials = credentials.createWithGdchAudience(invalidUri); + assertEquals(invalidUri, invalidCredentials.getGdchAudience()); + assertNull(invalidCredentials.getApiAudience()); } @Test @@ -477,7 +583,7 @@ void createAssertion_correct() throws IOException { GdchCredentials credentials = GdchCredentials.fromJson(json); JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; long currentTimeMillis = Clock.SYSTEM.currentTimeMillis(); - String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis, API_AUDIENCE); + String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis); JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion); JsonWebToken.Payload payload = signature.getPayload(); @@ -548,30 +654,17 @@ void refreshAccessToken_nullApiAudience() throws IOException { transportFactory.transport.addGdchServiceAccount( GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), tokenString); transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI); - try { - AccessToken accessToken = credentials.refreshAccessToken(); - fail("Should not be able to refresh access token without exception."); - } catch (NullPointerException ex) { - assertTrue( - ex.getMessage() - .contains( - "Audience are not configured for GDCH service account. Specify the " - + "audience by calling createWithGDCHAudience")); - } + NullPointerException ex = + assertThrows(NullPointerException.class, () -> credentials.refreshAccessToken()); + assertTrue( + ex.getMessage() + .contains( + "Audience cannot be null or empty for GDCH service account credentials. " + + "Specify the audience by calling createWithGdchAudience")); } @Test - void getIssuerSubjectValue_correct() throws IOException { - GenericJson json = - writeGdchServiceAccountJson( - FORMAT_VERSION, - PROJECT_ID, - PRIVATE_KEY_ID, - PRIVATE_KEY_PKCS8, - SERVICE_IDENTITY_NAME, - CA_CERT_PATH, - TOKEN_SERVER_URI); - GdchCredentials credentials = GdchCredentials.fromJson(json); + void getIssuerSubjectValue_correct() { Object expectedIssSubValue = String.format("system:serviceaccount:%s:%s", PROJECT_ID, SERVICE_IDENTITY_NAME); assertEquals( @@ -590,7 +683,7 @@ void equals_same() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -600,14 +693,14 @@ void equals_same() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test @@ -621,7 +714,7 @@ void equals_false_projectId() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -631,14 +724,14 @@ void equals_false_projectId() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -652,7 +745,7 @@ void equals_false_keyId() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -662,14 +755,14 @@ void equals_false_keyId() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -683,7 +776,7 @@ void equals_false_serviceIdentityName() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -693,20 +786,22 @@ void equals_false_serviceIdentityName() throws IOException { "otherServiceIdentityName", CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test void equals_false_caCertPath() throws IOException { File tmpDirectory = Files.createTempDirectory("tmpDirectory").toFile(); + tmpDirectory.deleteOnExit(); File testCaCertFile = File.createTempFile("testCert", ".pem", tmpDirectory); + testCaCertFile.deleteOnExit(); GenericJson json = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -716,7 +811,7 @@ void equals_false_caCertPath() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -726,16 +821,14 @@ void equals_false_caCertPath() throws IOException { SERVICE_IDENTITY_NAME, testCaCertFile.getPath(), TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); - - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); - testCaCertFile.delete(); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -749,7 +842,7 @@ void equals_false_tokenServer() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -759,19 +852,19 @@ void equals_false_tokenServer() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, URI.create("https://foo1.com/bar")); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test void equals_false_apiAudience() throws IOException { - URI otherApiAudience = URI.create("https://foo1.com/bar"); + String otherApiAudience = URI.create("https://foo1.com/bar").toString(); GenericJson json = writeGdchServiceAccountJson( @@ -782,7 +875,7 @@ void equals_false_apiAudience() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -792,13 +885,12 @@ void equals_false_apiAudience() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = - ((GdchCredentials) otherCredentials).createWithGdchAudience(otherApiAudience); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(otherApiAudience); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -812,8 +904,8 @@ void toString_containsFields() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + GdchCredentials credentials = GdchCredentials.fromJson(json); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); String expectedToString = String.format( "GdchCredentials{projectId=%s, privateKeyId=%s, serviceIdentityName=%s, " @@ -839,7 +931,7 @@ void hashCode_equals() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GdchCredentials credentials = GdchCredentials.fromJson(json); GenericJson otherJson = writeGdchServiceAccountJson( FORMAT_VERSION, @@ -849,11 +941,11 @@ void hashCode_equals() throws IOException { SERVICE_IDENTITY_NAME, CA_CERT_PATH, TOKEN_SERVER_URI); - OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + GdchCredentials otherCredentials = GdchCredentials.fromJson(otherJson); assertEquals(credentials.hashCode(), otherCredentials.hashCode()); - credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); - otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + otherCredentials = otherCredentials.createWithGdchAudience(API_AUDIENCE); assertEquals(credentials.hashCode(), otherCredentials.hashCode()); } @@ -876,12 +968,264 @@ void serialize_correct() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); assertEquals( MockTokenServerTransportFactory.class, deserializedCredentials.toBuilder().getHttpTransportFactory().getClass()); } + @Test + void refreshAccessToken_invalidResponse_missingAccessToken() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + + transportFactory.transport.addGdchServiceAccount( + GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), null); + transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI); + + IOException ex = assertThrows(IOException.class, () -> gdchWithAudience.refreshAccessToken()); + assertEquals( + "Error parsing token refresh response. Expected value access_token not found.", + ex.getMessage()); + } + + @Test + void refreshAccessToken_invalidResponse_wrongTypeAccessToken() throws IOException { + refreshAccessToken_invalidResponse( + "{\"access_token\": 123, \"expires_in\": 3600}", + "Error parsing token refresh response. Expected string value access_token of wrong type."); + } + + @Test + void refreshAccessToken_invalidResponse_missingExpiresIn() throws IOException { + refreshAccessToken_invalidResponse( + "{\"access_token\": \"token\"}", + "Error parsing token refresh response. Expected value expires_in not found."); + } + + @Test + void refreshAccessToken_invalidResponse_wrongTypeExpiresIn() throws IOException { + refreshAccessToken_invalidResponse( + "{\"access_token\": \"token\", \"expires_in\": \"3600\"}", + "Error parsing token refresh response. Expected integer value expires_in of wrong type."); + } + + private void refreshAccessToken_invalidResponse( + String responseContent, String expectedErrorMessage) throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + + transportFactory.transport.addResponseSequence( + new MockLowLevelHttpResponse().setContentType(Json.MEDIA_TYPE).setContent(responseContent)); + + IOException ex = assertThrows(IOException.class, () -> gdchWithAudience.refreshAccessToken()); + assertEquals(expectedErrorMessage, ex.getMessage()); + } + + @Test + void refreshAccessToken_serverError() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + + transportFactory.transport.addResponseSequence( + new MockLowLevelHttpResponse().setStatusCode(400).setReasonPhrase("Bad Request")); + + GoogleAuthException ex = + assertThrows(GoogleAuthException.class, () -> gdchWithAudience.refreshAccessToken()); + assertTrue(ex.getMessage().contains("Error getting access token for GDCH service account")); + assertTrue(ex.getMessage().contains("400 Bad Request")); + } + + @Test + void refreshAccessToken_ioException() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + + transportFactory.transport.addResponseErrorSequence(new IOException("Connection reset")); + + GoogleAuthException ex = + assertThrows(GoogleAuthException.class, () -> gdchWithAudience.refreshAccessToken()); + assertTrue(ex.getMessage().contains("Error getting access token for GDCH service account")); + assertTrue(ex.getMessage().contains("Connection reset")); + } + + @Test + void transcodeDerToConcat_withGeneratedSignature() throws Exception { + // Generate a new key pair and a signature. + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair keyPair = keyGen.generateKeyPair(); + Signature signer = Signature.getInstance("SHA256withECDSA"); + signer.initSign(keyPair.getPrivate()); + signer.update(new byte[] {1, 2, 3, 4}); + byte[] derSignature = signer.sign(); + + // Transcode the signature and check length. + byte[] jwsSignature = GdchCredentials.transcodeDerToConcat(derSignature, 64); + assertEquals(64, jwsSignature.length); + } + + @Test + void transcodeDerToConcat_invalidDerFormat() { + byte[] invalidDer = new byte[] {0x31, 0x00}; // Not a SEQUENCE + GoogleAuthException e = + assertThrows( + GoogleAuthException.class, () -> GdchCredentials.transcodeDerToConcat(invalidDer, 64)); + assertEquals("Invalid DER signature format.", e.getMessage()); + } + + @Test + void transcodeDerToConcat_invalidLength() { + // SEQUENCE length doesn't match actual length + byte[] invalidDer = new byte[] {0x30, 0x05, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02}; + GoogleAuthException e = + assertThrows( + GoogleAuthException.class, () -> GdchCredentials.transcodeDerToConcat(invalidDer, 64)); + assertEquals("Invalid DER signature length.", e.getMessage()); + } + + @Test + void transcodeDerToConcat_invalidRInteger() { + // Uses BIT STRING (0x03) instead of INTEGER (0x02) for R + byte[] invalidDer = new byte[] {0x30, 0x06, 0x03, 0x01, 0x01, 0x02, 0x01, 0x02}; + GoogleAuthException e = + assertThrows( + GoogleAuthException.class, () -> GdchCredentials.transcodeDerToConcat(invalidDer, 64)); + assertEquals("Expected INTEGER for R.", e.getMessage()); + } + + @Test + void transcodeDerToConcat_invalidSInteger() { + // Uses BIT STRING (0x03) instead of INTEGER (0x02) for S + byte[] invalidDer = new byte[] {0x30, 0x06, 0x02, 0x01, 0x01, 0x03, 0x01, 0x01}; + GoogleAuthException e = + assertThrows( + GoogleAuthException.class, () -> GdchCredentials.transcodeDerToConcat(invalidDer, 64)); + assertEquals("Expected INTEGER for S.", e.getMessage()); + } + + @Test + void signUsingEsSha256_producesVerifiableSignature() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair keyPair = keyGen.generateKeyPair(); + + JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); + + JsonWebSignature.Header header = new JsonWebSignature.Header(); + header.setAlgorithm("ES256"); + header.setType("JWT"); + header.setKeyId("test-key-id"); + + JsonWebToken.Payload payload = new JsonWebToken.Payload(); + payload.setIssuer("test-issuer"); + payload.setAudience("test-audience"); + + String signedJws = + GdchCredentials.signUsingEsSha256(keyPair.getPrivate(), jsonFactory, header, payload); + + // Verify the signature. + JsonWebSignature jws = JsonWebSignature.parse(jsonFactory, signedJws); + assertTrue(jws.verifySignature(keyPair.getPublic())); + } + + @Test + void signUsingEsSha256_validStructure() throws Exception { + PrivateKey privateKey = + OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8, OAuth2Utils.Pkcs8Algorithm.EC); + JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); + + JsonWebSignature.Header header = new JsonWebSignature.Header(); + header.setAlgorithm("ES256"); + header.setType("JWT"); + header.setKeyId(PRIVATE_KEY_ID); + + JsonWebToken.Payload payload = new JsonWebToken.Payload(); + payload.setIssuer("test-issuer"); + payload.setAudience("test-audience"); + payload.setSubject("test-subject"); + payload.setIssuedAtTimeSeconds(1000L); + payload.setExpirationTimeSeconds(2000L); + + String signedJws = GdchCredentials.signUsingEsSha256(privateKey, jsonFactory, header, payload); + + // Verify JWS structure + String[] parts = signedJws.split("\\."); + assertEquals(3, parts.length); + + // Verify header + JsonWebSignature.Header decodedHeader = + jsonFactory.fromInputStream( + new java.io.ByteArrayInputStream(Base64.getUrlDecoder().decode(parts[0])), + JsonWebSignature.Header.class); + assertEquals("ES256", decodedHeader.getAlgorithm()); + assertEquals("JWT", decodedHeader.getType()); + assertEquals(PRIVATE_KEY_ID, decodedHeader.getKeyId()); + + // Verify payload + JsonWebToken.Payload decodedPayload = + jsonFactory.fromInputStream( + new java.io.ByteArrayInputStream(Base64.getUrlDecoder().decode(parts[1])), + JsonWebToken.Payload.class); + assertEquals("test-issuer", decodedPayload.getIssuer()); + assertEquals("test-audience", decodedPayload.getAudience()); + assertEquals("test-subject", decodedPayload.getSubject()); + + // Verify signature format (64 bytes for ES256) + byte[] signatureBytes = Base64.getUrlDecoder().decode(parts[2]); + assertEquals(64, signatureBytes.length); + } + + @Test + void builder_setGdchAudience_nullString() { + GdchCredentials.Builder builder = GdchCredentials.newBuilder(); + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> builder.setGdchAudience((String) null)); + assertTrue( + ex.getMessage() + .contains("Audience cannot be null or empty for GDCH service account credentials.")); + } + static GenericJson writeGdchServiceAccountJson( String formatVersion, String project, diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleAuthUtilsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleAuthUtilsTest.java index 7c883df0fe73..de7077ef6277 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleAuthUtilsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleAuthUtilsTest.java @@ -47,8 +47,9 @@ void getWellKnownCredentialsPath_correct() { // the base folder will be an empty string using. File homeDir = new File(""); File configDir = new File(homeDir, ".config"); - File cloudConfigDir = new File(configDir, provider.CLOUDSDK_CONFIG_DIRECTORY); - File wellKnownFile = new File(cloudConfigDir, provider.WELL_KNOWN_CREDENTIALS_FILE); + File cloudConfigDir = new File(configDir, DefaultCredentialsProvider.CLOUDSDK_CONFIG_DIRECTORY); + File wellKnownFile = + new File(cloudConfigDir, DefaultCredentialsProvider.WELL_KNOWN_CREDENTIALS_FILE); String obtainedPath = GoogleAuthUtils.getWellKnownCredentialsPath(provider); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index 503c87d54207..74aa9fae9ccd 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -33,12 +33,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpStatusCodes; import com.google.api.client.json.GenericJson; @@ -71,7 +71,7 @@ class GoogleCredentialsTest extends BaseSerializationTest { private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; private static final String SA_PRIVATE_KEY_PKCS8 = ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8; - private static final String GDCH_SA_FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION; + private static final String GDCH_SA_PROJECT_ID = "gdch-service-account-project-id"; private static final String GDCH_SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; private static final String GDCH_SA_PRIVATE_KEY_PKC8 = GdchCredentialsTest.PRIVATE_KEY_PKCS8; @@ -82,7 +82,7 @@ class GoogleCredentialsTest extends BaseSerializationTest { private static final String GDCH_SA_CA_CERT_FILE_NAME = "cert.pem"; private static final String GDCH_SA_CA_CERT_PATH = GdchCredentialsTest.class.getClassLoader().getResource(GDCH_SA_CA_CERT_FILE_NAME).getPath(); - private static final URI GDCH_API_AUDIENCE = URI.create("https://gdch-api-audience"); + private static final String GDCH_API_AUDIENCE = "https://gdch-api-audience"; private static final String USER_CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu"; private static final String USER_CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws"; private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY"; @@ -100,13 +100,8 @@ class GoogleCredentialsTest extends BaseSerializationTest { private static final String TPC_UNIVERSE = "foo.bar"; @Test - void getApplicationDefault_nullTransport_throws() throws IOException { - try { - GoogleCredentials.getApplicationDefault(null); - fail(); - } catch (NullPointerException expected) { - // Expected - } + void getApplicationDefault_nullTransport_throws() { + assertThrows(NullPointerException.class, () -> GoogleCredentials.getApplicationDefault(null)); } @Test @@ -114,29 +109,23 @@ void fromStream_unknownType_throws() throws IOException { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); GenericJson json = new GenericJson(); json.put("type", "unsupported_credential"); - InputStream stream = TestUtils.jsonToInputStream(json); - try { - GoogleCredentials.fromStream(stream, transportFactory); - fail("Should throw if type is unknown."); - } catch (IOException expected) { + try (InputStream stream = TestUtils.jsonToInputStream(json)) { + IOException exception = + assertThrows( + IOException.class, () -> GoogleCredentials.fromStream(stream, transportFactory)); String expectedError = "Error reading credentials from stream, 'type' value " + "'unsupported_credential' not recognized. Valid values are 'authorized_user', " + "'service_account', 'gdch_service_account', 'external_account', " + "'external_account_authorized_user', 'impersonated_service_account'."; - assertTrue(expected.getMessage().contains(expectedError)); + assertTrue(exception.getMessage().contains(expectedError)); } } @Test - void fromStream_nullTransport_throws() throws IOException { + void fromStream_nullTransport_throws() { InputStream stream = new ByteArrayInputStream("foo".getBytes()); - try { - GoogleCredentials.fromStream(stream, null); - fail("Should throw if HttpTransportFactory is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows(NullPointerException.class, () -> GoogleCredentials.fromStream(stream, null)); } @Test @@ -146,19 +135,17 @@ void fromStream_noType_throws() throws IOException { ServiceAccountCredentialsTest.writeServiceAccountJson( "project_id", QUOTA_PROJECT, "universe"); json.remove("type"); - InputStream stream = TestUtils.jsonToInputStream(json); - try { - GoogleCredentials.fromStream(stream, transportFactory); - fail("Should throw if type is unknown."); - } catch (IOException expected) { + try (InputStream stream = TestUtils.jsonToInputStream(json)) { + IOException exception = + assertThrows( + IOException.class, () -> GoogleCredentials.fromStream(stream, transportFactory)); String expectedError = "Error reading credentials from stream, 'type' field not specified."; - assertEquals(expectedError, expected.getMessage()); + assertEquals(expectedError, exception.getMessage()); } } @Test void fromStream_nullStream_throws() { - MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); assertThrows(NullPointerException.class, () -> GoogleCredentials.parseJsonInputStream(null)); } @@ -175,7 +162,7 @@ void fromStream_serviceAccount_noUniverse_providesToken() throws IOException { assertNotNull(credentials); assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); - assertEquals(false, credentials.isExplicitUniverseDomain()); + assertFalse(credentials.isExplicitUniverseDomain()); credentials = credentials.createScoped(SCOPES); Map> metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); @@ -198,46 +185,62 @@ void fromStream_serviceAccount_Universe_noToken() throws IOException { assertNotNull(credentials); assertEquals(TPC_UNIVERSE, credentials.getUniverseDomain()); - assertEquals(true, credentials.isExplicitUniverseDomain()); + assertTrue(credentials.isExplicitUniverseDomain()); credentials = credentials.createScoped(SCOPES); - Map> metadata = credentials.getRequestMetadata(CALL_URI); + credentials.getRequestMetadata(CALL_URI); assertNotNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope()); } @Test void fromStream_serviceAccountNoClientId_throws() throws IOException { - InputStream serviceAccountStream = + try (InputStream serviceAccountStream = ServiceAccountCredentialsTest.writeServiceAccountStream( - null, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID); - - testFromStreamException(serviceAccountStream, "client_id"); + null, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("client_id")); + } } @Test void fromStream_serviceAccountNoClientEmail_throws() throws IOException { - InputStream serviceAccountStream = + try (InputStream serviceAccountStream = ServiceAccountCredentialsTest.writeServiceAccountStream( - SA_CLIENT_ID, null, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID); - - testFromStreamException(serviceAccountStream, "client_email"); + SA_CLIENT_ID, null, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("client_email")); + } } @Test void fromStream_serviceAccountNoPrivateKey_throws() throws IOException { - InputStream serviceAccountStream = + try (InputStream serviceAccountStream = ServiceAccountCredentialsTest.writeServiceAccountStream( - SA_CLIENT_ID, SA_CLIENT_EMAIL, null, SA_PRIVATE_KEY_ID); - - testFromStreamException(serviceAccountStream, "private_key"); + SA_CLIENT_ID, SA_CLIENT_EMAIL, null, SA_PRIVATE_KEY_ID)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("private_key")); + } } @Test void fromStream_serviceAccountNoPrivateKeyId_throws() throws IOException { - InputStream serviceAccountStream = + try (InputStream serviceAccountStream = ServiceAccountCredentialsTest.writeServiceAccountStream( - SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, null); - - testFromStreamException(serviceAccountStream, "private_key_id"); + SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, null)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("private_key_id")); + } } @Test @@ -245,7 +248,7 @@ void fromStream_gdchServiceAccount_correct() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, @@ -256,8 +259,8 @@ void fromStream_gdchServiceAccount_correct() throws IOException { GoogleCredentials.fromStream(gdchServiceAccountStream, transportFactory); assertNotNull(credentials); - assertTrue(credentials instanceof GdchCredentials); - assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) credentials).getProjectId()); + assertInstanceOf(GdchCredentials.class, credentials); + assertEquals(GDCH_SA_PROJECT_ID, credentials.getProjectId()); assertEquals( GDCH_SA_SERVICE_IDENTITY_NAME, ((GdchCredentials) credentials).getServiceIdentityName()); assertEquals(GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) credentials).getTokenServerUri()); @@ -267,8 +270,8 @@ void fromStream_gdchServiceAccount_correct() throws IOException { credentials = ((GdchCredentials) credentials).createWithGdchAudience(GDCH_API_AUDIENCE); assertNotNull(credentials); - assertTrue(credentials instanceof GdchCredentials); - assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) credentials).getProjectId()); + assertTrue(true); + assertEquals(GDCH_SA_PROJECT_ID, credentials.getProjectId()); assertEquals( GDCH_SA_SERVICE_IDENTITY_NAME, ((GdchCredentials) credentials).getServiceIdentityName()); assertEquals(GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) credentials).getTokenServerUri()); @@ -278,7 +281,7 @@ void fromStream_gdchServiceAccount_correct() throws IOException { @Test void fromStream_gdchServiceAccountNoFormatVersion_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( null, GDCH_SA_PROJECT_ID, @@ -286,89 +289,119 @@ void fromStream_gdchServiceAccountNoFormatVersion_throws() throws IOException { GDCH_SA_PRIVATE_KEY_PKC8, GDCH_SA_SERVICE_IDENTITY_NAME, GDCH_SA_CA_CERT_PATH, - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException(gdchServiceAccountStream, "format_version"); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("format_version")); + } } @Test void fromStream_gdchServiceAccountNoProjectId_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, null, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, GDCH_SA_SERVICE_IDENTITY_NAME, GDCH_SA_CA_CERT_PATH, - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException(gdchServiceAccountStream, "project"); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("project")); + } } @Test void fromStream_gdchServiceAccountNoPrivateKeyId_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, null, GDCH_SA_PRIVATE_KEY_PKC8, GDCH_SA_SERVICE_IDENTITY_NAME, GDCH_SA_CA_CERT_PATH, - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException(gdchServiceAccountStream, "private_key_id"); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("private_key_id")); + } } @Test void fromStream_gdchServiceAccountNoPrivateKey_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, null, GDCH_SA_SERVICE_IDENTITY_NAME, GDCH_SA_CA_CERT_PATH, - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException(gdchServiceAccountStream, "private_key"); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("private_key")); + } } @Test void fromStream_gdchServiceAccountNoServiceIdentityName_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, null, GDCH_SA_CA_CERT_PATH, - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException(gdchServiceAccountStream, "name"); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("name")); + } } @Test void fromStream_gdchServiceAccountNoTokenServerUri_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, GDCH_SA_SERVICE_IDENTITY_NAME, GDCH_SA_CA_CERT_PATH, - null); - - testFromStreamException(gdchServiceAccountStream, "token_uri"); + null)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("token_uri")); + } } @Test void fromStream_gdchServiceAccountInvalidFormatVersion_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( "100", GDCH_SA_PROJECT_ID, @@ -376,28 +409,41 @@ void fromStream_gdchServiceAccountInvalidFormatVersion_throws() throws IOExcepti GDCH_SA_PRIVATE_KEY_PKC8, GDCH_SA_SERVICE_IDENTITY_NAME, GDCH_SA_CA_CERT_PATH, - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException( - gdchServiceAccountStream, - String.format("Only format version %s is supported", GDCH_SA_FORMAT_VERSION)); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue( + exception + .getMessage() + .contains( + String.format( + "Only format version %s is supported", + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION))); + } } @Test void fromStream_gdchServiceAccountInvalidCaCertPath_throws() throws IOException { - InputStream gdchServiceAccountStream = + try (InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, GDCH_SA_SERVICE_IDENTITY_NAME, "/path/to/missing/file", - GDCH_SA_TOKEN_SERVER_URI); - - testFromStreamException( - gdchServiceAccountStream, - String.format("Error reading certificate file from CA cert path")); + GDCH_SA_TOKEN_SERVER_URI)) { + IOException exception = + assertThrows( + IOException.class, + () -> + GoogleCredentials.fromStream(gdchServiceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue( + exception.getMessage().contains("Error reading certificate file from CA cert path")); + } } @Test @@ -430,27 +476,40 @@ void fromStream_userCredentials_defaultUniverse() throws IOException { @Test void fromStream_userCredentials_NoClientId_throws() throws IOException { - InputStream userStream = - UserCredentialsTest.writeUserStream(null, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT); - - testFromStreamException(userStream, "client_id"); + try (InputStream userStream = + UserCredentialsTest.writeUserStream( + null, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(userStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("client_id")); + } } @Test void fromStream_userCredentials_NoClientSecret_throws() throws IOException { - InputStream userStream = - UserCredentialsTest.writeUserStream(USER_CLIENT_ID, null, REFRESH_TOKEN, QUOTA_PROJECT); - - testFromStreamException(userStream, "client_secret"); + try (InputStream userStream = + UserCredentialsTest.writeUserStream(USER_CLIENT_ID, null, REFRESH_TOKEN, QUOTA_PROJECT)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(userStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("client_secret")); + } } @Test void fromStream_userCredentials_NoRefreshToken_throws() throws IOException { - InputStream userStream = + try (InputStream userStream = UserCredentialsTest.writeUserStream( - USER_CLIENT_ID, USER_CLIENT_SECRET, null, QUOTA_PROJECT); - - testFromStreamException(userStream, "refresh_token"); + USER_CLIENT_ID, USER_CLIENT_SECRET, null, QUOTA_PROJECT)) { + IOException exception = + assertThrows( + IOException.class, + () -> GoogleCredentials.fromStream(userStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("refresh_token")); + } } @Test @@ -723,7 +782,7 @@ void create_withoutUniverse() throws IOException { GoogleCredentials credentials = GoogleCredentials.create(token); assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); - assertEquals(false, credentials.isExplicitUniverseDomain()); + assertFalse(credentials.isExplicitUniverseDomain()); } @Test @@ -732,7 +791,7 @@ void create_withUniverse() throws IOException { GoogleCredentials credentials = GoogleCredentials.create("some-universe", token); assertEquals("some-universe", credentials.getUniverseDomain()); - assertEquals(true, credentials.isExplicitUniverseDomain()); + assertTrue(credentials.isExplicitUniverseDomain()); } @Test @@ -748,7 +807,7 @@ void buildWithQuotaProject() { assertEquals("", withEmptyQuota.getQuotaProjectId()); GoogleCredentials sameCredentials = googleCredentials.createWithQuotaProject(null); - assertEquals(null, sameCredentials.getQuotaProjectId()); + assertNull(sameCredentials.getQuotaProjectId()); } @Test @@ -758,17 +817,17 @@ void buildWithUniverseDomain() throws IOException { GoogleCredentials updated = original.toBuilder().setUniverseDomain("universe2").build(); assertEquals("universe1", original.getUniverseDomain()); - assertEquals(true, original.isExplicitUniverseDomain()); + assertTrue(original.isExplicitUniverseDomain()); assertEquals("universe2", updated.getUniverseDomain()); - assertEquals(true, updated.isExplicitUniverseDomain()); + assertTrue(updated.isExplicitUniverseDomain()); GoogleCredentials withEmpty = original.toBuilder().setUniverseDomain("").build(); assertEquals(GOOGLE_DEFAULT_UNIVERSE, withEmpty.getUniverseDomain()); - assertEquals(false, withEmpty.isExplicitUniverseDomain()); + assertFalse(withEmpty.isExplicitUniverseDomain()); GoogleCredentials withNull = original.toBuilder().setUniverseDomain(null).build(); assertEquals(GOOGLE_DEFAULT_UNIVERSE, withNull.getUniverseDomain()); - assertEquals(false, withNull.isExplicitUniverseDomain()); + assertFalse(withNull.isExplicitUniverseDomain()); } @Test @@ -778,22 +837,22 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(testCredentials, deserializedCredentials); assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(testCredentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } @Test - void toString_containsFields() throws IOException { + void toString_containsFields() { String expectedToString = String.format( "GoogleCredentials{quotaProjectId=%s, universeDomain=%s, isExplicitUniverseDomain=%s}", - "some-project", "googleapis.com", false, "[some scope]"); + "some-project", "googleapis.com", false); GoogleCredentials credentials = GoogleCredentials.newBuilder().setQuotaProjectId("some-project").build(); assertEquals(expectedToString, credentials.toString()); } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { GoogleCredentials credentials = GoogleCredentials.newBuilder().setUniverseDomain("some-domain").build(); GoogleCredentials otherCredentials = @@ -802,24 +861,13 @@ void hashCode_equals() throws IOException { } @Test - void equals_true() throws IOException { + void equals_true() { GoogleCredentials credentials = GoogleCredentials.newBuilder().setUniverseDomain("some-domain").build(); GoogleCredentials otherCredentials = GoogleCredentials.newBuilder().setUniverseDomain("some-domain").build(); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); - } - - private static void testFromStreamException(InputStream stream, String expectedMessageContent) { - try { - GoogleCredentials.fromStream(stream, DUMMY_TRANSPORT_FACTORY); - fail( - String.format( - "Should throw exception with message containing '%s'", expectedMessageContent)); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedMessageContent)); - } + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test @@ -854,7 +902,7 @@ void getCredentialInfo_userCredentials() throws IOException { void getCredentialInfo_gdchCredentials() throws IOException { InputStream gdchServiceAccountStream = GdchCredentialsTest.writeGdchServiceAccountStream( - GDCH_SA_FORMAT_VERSION, + GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION, GDCH_SA_PROJECT_ID, GDCH_SA_PRIVATE_KEY_ID, GDCH_SA_PRIVATE_KEY_PKC8, diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITDownscopingTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITDownscopingTest.java index 2a04d771030d..5422760c4396 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITDownscopingTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITDownscopingTest.java @@ -32,8 +32,8 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; @@ -122,12 +122,11 @@ public AccessToken refreshAccessToken() throws IOException { // Attempt to retrieve the object that the downscoped token does not have access to. This should // fail. - try { - retrieveObjectFromGcs(credentials, GCS_OBJECT_NAME_WITHOUT_PERMISSION); - fail("Call to GCS should have failed."); - } catch (HttpResponseException e) { - assertEquals(403, e.getStatusCode()); - } + HttpResponseException e = + assertThrows( + HttpResponseException.class, + () -> retrieveObjectFromGcs(credentials, GCS_OBJECT_NAME_WITHOUT_PERMISSION)); + assertEquals(403, e.getStatusCode()); } private void retrieveObjectFromGcs(Credentials credentials, String objectName) diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITWorkloadIdentityFederationTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITWorkloadIdentityFederationTest.java index d1574a06a2fc..0f1cdd3092f6 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITWorkloadIdentityFederationTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ITWorkloadIdentityFederationTest.java @@ -312,13 +312,13 @@ private GenericJson buildIdentityPoolCredentialConfig() throws IOException { private GenericJson buildPluggableCredentialConfig() throws IOException { String idToken = generateGoogleIdToken(OIDC_AUDIENCE); - Instant expiration_time = Instant.now().plusSeconds(60 * 60); + Instant expirationTime = Instant.now().plusSeconds(60 * 60); GenericJson executableJson = new GenericJson(); executableJson.setFactory(OAuth2Utils.JSON_FACTORY); executableJson.put("success", true); executableJson.put("version", 1); - executableJson.put("expiration_time", expiration_time.toEpochMilli()); + executableJson.put("expirationTime", expirationTime.toEpochMilli()); executableJson.put("token_type", "urn:ietf:params:oauth:token-type:jwt"); executableJson.put("id_token", idToken); @@ -403,8 +403,8 @@ private void callGcs(GoogleCredentials credentials) throws IOException { } /** - * Generates a Google ID token using the iamcredentials generateIdToken API. - * https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oidc + * Generates a Google ID token using the iamcredentials generateIdToken API. ... */ private String generateGoogleIdToken(String audience) throws IOException { GoogleCredentials googleCredentials = @@ -451,10 +451,10 @@ private String getXmlValueByTagName(String rawXml, String tagName) { return null; } - private class ITAwsSecurityCredentialsProvider implements AwsSecurityCredentialsSupplier { + private static class ITAwsSecurityCredentialsProvider implements AwsSecurityCredentialsSupplier { - private String region; - private AwsSecurityCredentials credentials; + private final String region; + private final AwsSecurityCredentials credentials; ITAwsSecurityCredentialsProvider(String region, AwsSecurityCredentials credentials) { this.region = region; diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdTokenTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdTokenTest.java index 52fa2c00885e..6ab42fa49e2f 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdTokenTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdTokenTest.java @@ -32,8 +32,7 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.io.IOException; import java.util.Date; @@ -61,16 +60,16 @@ void constructor() throws IOException { void equals_true() throws IOException { IdToken accessToken = IdToken.create(TOKEN_1); IdToken otherAccessToken = IdToken.create(TOKEN_1); - assertTrue(accessToken.equals(otherAccessToken)); - assertTrue(otherAccessToken.equals(accessToken)); + assertEquals(accessToken, otherAccessToken); + assertEquals(otherAccessToken, accessToken); } @Test void equals_false_token() throws IOException { IdToken accessToken = IdToken.create(TOKEN_1); IdToken otherAccessToken = IdToken.create(TOKEN_2); - assertFalse(accessToken.equals(otherAccessToken)); - assertFalse(otherAccessToken.equals(accessToken)); + assertNotEquals(accessToken, otherAccessToken); + assertNotEquals(otherAccessToken, accessToken); } @Test diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 6997c79b0ef2..674d523e5090 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -34,14 +34,12 @@ import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE; import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; @@ -78,7 +76,7 @@ class IdentityPoolCredentialsTest extends BaseSerializationTest { (ExternalAccountSupplierContext context) -> "testSubjectToken"; @Test - void createdScoped_clonedCredentialWithAddedScopes() throws IOException { + void createdScoped_clonedCredentialWithAddedScopes() { IdentityPoolCredentials credentials = IdentityPoolCredentials.newBuilder(createBaseFileSourcedCredentials()) .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) @@ -188,12 +186,11 @@ void retrieveSubjectToken_fileSourcedWithNullFormat_throws() throws IOException credentialSourceMap.put("file", file.getAbsolutePath()); credentialSourceMap.put("format", formatMap); - try { - new IdentityPoolCredentialSource(credentialSourceMap); - fail("Exception should be thrown due to null format."); - } catch (IllegalArgumentException e) { - assertEquals("Invalid credential source format type: null.", e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> new IdentityPoolCredentialSource(credentialSourceMap)); + assertEquals("Invalid credential source format type: null.", e.getMessage()); } @Test @@ -209,14 +206,10 @@ void retrieveSubjectToken_noFile_throws() { .setCredentialSource(credentialSource) .build(); - try { - credentials.retrieveSubjectToken(); - fail("Exception should be thrown."); - } catch (IOException e) { - assertEquals( - String.format("Invalid credential location. The file at %s does not exist.", path), - e.getMessage()); - } + IOException e = assertThrows(IOException.class, credentials::retrieveSubjectToken); + assertEquals( + String.format("Invalid credential location. The file at %s does not exist.", path), + e.getMessage()); } @Test @@ -276,15 +269,11 @@ void retrieveSubjectToken_urlSourcedCredential_throws() { buildUrlBasedCredentialSource(transportFactory.transport.getMetadataUrl())) .build(); - try { - credential.retrieveSubjectToken(); - fail("Exception should be thrown."); - } catch (IOException e) { - assertEquals( - String.format( - "Error getting subject token from metadata server: %s", response.getMessage()), - e.getMessage()); - } + IOException e = assertThrows(IOException.class, credential::retrieveSubjectToken); + assertEquals( + String.format( + "Error getting subject token from metadata server: %s", response.getMessage()), + e.getMessage()); } @Test @@ -303,7 +292,7 @@ void retrieveSubjectToken_provider() throws IOException { } @Test - void retrieveSubjectToken_providerThrowsError() throws IOException { + void retrieveSubjectToken_providerThrowsError() { IOException testException = new IOException("test"); IdentityPoolSubjectTokenSupplier errorProvider = @@ -316,12 +305,8 @@ void retrieveSubjectToken_providerThrowsError() throws IOException { .setSubjectTokenSupplier(errorProvider) .build(); - try { - String subjectToken = credentials.retrieveSubjectToken(); - fail("retrieveSubjectToken should fail."); - } catch (IOException e) { - assertEquals("test", e.getMessage()); - } + IOException e = assertThrows(IOException.class, credentials::retrieveSubjectToken); + assertEquals("test", e.getMessage()); } @Test @@ -458,7 +443,7 @@ void refreshAccessToken_withServiceAccountImpersonationOptions() throws IOExcept .setCredentialSource( buildUrlBasedCredentialSource(transportFactory.transport.getMetadataUrl())) .setServiceAccountImpersonationOptions( - ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800)) + ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions()) .build(); AccessToken accessToken = credential.refreshAccessToken(); @@ -590,7 +575,7 @@ void refreshAccessToken_workforceWithServiceAccountImpersonationOptions() throws buildUrlBasedCredentialSource(transportFactory.transport.getMetadataUrl())) .setWorkforcePoolUserProject("userProject") .setServiceAccountImpersonationOptions( - ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800)) + ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions()) .build(); AccessToken accessToken = credential.refreshAccessToken(); @@ -646,7 +631,7 @@ void identityPoolCredentialSource_validFormats() { credentialSourceMapWithUrlJsonTextSource); for (Map source : sources) { // Should not throw. - new IdentityPoolCredentialSource(source); + assertDoesNotThrow(() -> new IdentityPoolCredentialSource(source)); } } @@ -689,20 +674,20 @@ void identityPoolCredentialSource_caseInsensitive() { credentialSourceMapWithUrlJsonTextSource); for (Map source : sources) { // Should not throw. - new IdentityPoolCredentialSource(source); + assertDoesNotThrow(() -> new IdentityPoolCredentialSource(source)); } } @Test void identityPoolCredentialSource_invalidSourceType() { - try { - new IdentityPoolCredentialSource(new HashMap<>()); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "Missing credential source file location, URL, or certificate. At least one must be specified.", - exception.getMessage()); - } + HashMap credentialSourceMap = new HashMap<>(); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> new IdentityPoolCredentialSource(credentialSourceMap)); + assertEquals( + "Missing credential source file location, URL, or certificate. At least one must be specified.", + e.getMessage()); } @Test @@ -714,12 +699,11 @@ void identityPoolCredentialSource_invalidFormatType() { format.put("type", "unsupportedType"); credentialSourceMap.put("format", format); - try { - new IdentityPoolCredentialSource(credentialSourceMap); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals("Invalid credential source format type: unsupportedType.", e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> new IdentityPoolCredentialSource(credentialSourceMap)); + assertEquals("Invalid credential source format type: unsupportedType.", e.getMessage()); } @Test @@ -731,12 +715,11 @@ void identityPoolCredentialSource_nullFormatType() { format.put("type", null); credentialSourceMap.put("format", format); - try { - new IdentityPoolCredentialSource(credentialSourceMap); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals("Invalid credential source format type: null.", e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> new IdentityPoolCredentialSource(credentialSourceMap)); + assertEquals("Invalid credential source format type: null.", e.getMessage()); } @Test @@ -748,18 +731,17 @@ void identityPoolCredentialSource_subjectTokenFieldNameUnset() { format.put("type", "json"); credentialSourceMap.put("format", format); - try { - new IdentityPoolCredentialSource(credentialSourceMap); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals( - "When specifying a JSON credential type, the subject_token_field_name must be set.", - e.getMessage()); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> new IdentityPoolCredentialSource(credentialSourceMap)); + assertEquals( + "When specifying a JSON credential type, the subject_token_field_name must be set.", + e.getMessage()); } @Test - void builder_allFields() throws IOException { + void builder_allFields() { List scopes = Arrays.asList("scope1", "scope2"); IdentityPoolCredentialSource credentialSource = createFileCredentialSource(); @@ -831,23 +813,20 @@ void builder_invalidWorkforceAudiences_throws() { "//iam.googleapis.com/locations/global/workforce/providers"); for (String audience : invalidAudiences) { - try { - IdentityPoolCredentials.newBuilder() - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience(audience) - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("tokenInfoUrl") - .setCredentialSource(createFileCredentialSource()) - .setQuotaProjectId("quotaProjectId") - .build(); - fail("Exception should be thrown."); - } catch (IllegalArgumentException e) { - assertEquals( - "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", - e.getMessage()); - } + IdentityPoolCredentials.Builder builder = + IdentityPoolCredentials.newBuilder() + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience(audience) + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("tokenInfoUrl") + .setCredentialSource(createFileCredentialSource()) + .setQuotaProjectId("quotaProjectId"); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", + e.getMessage()); } } @@ -872,44 +851,37 @@ void builder_emptyWorkforceUserProjectWithWorkforceAudience() { @Test void builder_supplierAndCredSourceThrows() { - try { - IdentityPoolCredentials credentials = - IdentityPoolCredentials.newBuilder() - .setSubjectTokenSupplier(testProvider) - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience("audience") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setCredentialSource(createFileCredentialSource()) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "IdentityPoolCredentials cannot have both a subjectTokenSupplier and a credentialSource.", - exception.getMessage()); - } + IdentityPoolCredentials.Builder builder = + IdentityPoolCredentials.newBuilder() + .setSubjectTokenSupplier(testProvider) + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setCredentialSource(createFileCredentialSource()); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "IdentityPoolCredentials cannot have both a subjectTokenSupplier and a credentialSource.", + exception.getMessage()); } @Test - void builder_noSupplierOrCredSourceThrows() throws IOException { - - try { - IdentityPoolCredentials credentials = - IdentityPoolCredentials.newBuilder() - .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) - .setAudience("audience") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .build(); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "A subjectTokenSupplier or a credentialSource must be provided.", exception.getMessage()); - } + void builder_noSupplierOrCredSourceThrows() { + IdentityPoolCredentials.Builder builder = + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, builder::build); + assertEquals( + "A subjectTokenSupplier or a credentialSource must be provided.", exception.getMessage()); } @Test - void builder_missingUniverseDomain_defaults() throws IOException { + void builder_missingUniverseDomain_defaults() { List scopes = Arrays.asList("scope1", "scope2"); IdentityPoolCredentialSource credentialSource = createFileCredentialSource(); @@ -944,7 +916,7 @@ void builder_missingUniverseDomain_defaults() throws IOException { } @Test - void newBuilder_allFields() throws IOException { + void newBuilder_allFields() { List scopes = Arrays.asList("scope1", "scope2"); IdentityPoolCredentials credentials = @@ -986,7 +958,7 @@ void newBuilder_allFields() throws IOException { } @Test - void newBuilder_noUniverseDomain_defaults() throws IOException { + void newBuilder_noUniverseDomain_defaults() { List scopes = Arrays.asList("scope1", "scope2"); IdentityPoolCredentials credentials = @@ -1045,14 +1017,42 @@ void serialize() throws IOException, ClassNotFoundException { } @Test - void build_withCertificateSourceAndCustomX509Provider_success() - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - // Create an empty KeyStore and a spy on a custom X509Provider. - KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(null, null); - TestX509Provider x509Provider = - spy(new TestX509Provider(keyStore, "/path/to/certificate.json")); + void build_withCertificateSource_succeeds() throws Exception { + // Set up credential source for certificate type. + Map certificateMap = new HashMap<>(); + certificateMap.put("use_default_certificate_config", false); + certificateMap.put("certificate_config_location", "testresources/mtls/certificate_config.json"); + Map credentialSourceMap = new HashMap<>(); + credentialSourceMap.put("certificate", certificateMap); + IdentityPoolCredentialSource credentialSource = + new IdentityPoolCredentialSource(credentialSourceMap); + MockExternalAccountCredentialsTransportFactory mockTransportFactory = + new MockExternalAccountCredentialsTransportFactory(); + // Build credentials. + IdentityPoolCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(mockTransportFactory) + .setAudience("test-audience") + .setSubjectTokenType("test-token-type") + .setCredentialSource(credentialSource) + .build(); + + // Verify successful creation and correct internal setup. + assertNotNull(credentials, "Credentials should be successfully created"); + assertTrue( + credentials.getIdentityPoolSubjectTokenSupplier() + instanceof CertificateIdentityPoolSubjectTokenSupplier, + "Subject token supplier should be for certificates"); + assertEquals( + IdentityPoolCredentials.CERTIFICATE_METRICS_HEADER_VALUE, + credentials.getCredentialSourceType(), + "Metrics header should indicate certificate source"); + } + + @Test + void build_withDefaultCertificateConfig_success() + throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { // Set up credential source for certificate type. Map certificateMap = new HashMap<>(); certificateMap.put("use_default_certificate_config", true); @@ -1063,11 +1063,18 @@ void build_withCertificateSourceAndCustomX509Provider_success() MockExternalAccountCredentialsTransportFactory mockTransportFactory = new MockExternalAccountCredentialsTransportFactory(); - // Build credentials with the custom provider. + // Use the pre-existing test configuration file to bypass well-known path resolution. + EnvironmentProvider mockEnvProvider = + name -> + "GOOGLE_API_CERTIFICATE_CONFIG".equals(name) + ? new File("testresources/mtls/certificate_config.json").getAbsolutePath() + : null; + + // Build credentials using the default provider (no setX509Provider). IdentityPoolCredentials credentials = IdentityPoolCredentials.newBuilder() - .setX509Provider(x509Provider) .setHttpTransportFactory(mockTransportFactory) + .setEnvironmentProvider(mockEnvProvider) .setAudience("test-audience") .setSubjectTokenType("test-token-type") .setCredentialSource(credentialSource) @@ -1083,10 +1090,6 @@ void build_withCertificateSourceAndCustomX509Provider_success() IdentityPoolCredentials.CERTIFICATE_METRICS_HEADER_VALUE, credentials.getCredentialSourceType(), "Metrics header should indicate certificate source"); - - // Verify the custom provider methods were called during build. - verify(x509Provider).getKeyStore(); - verify(x509Provider).getCertificatePath(); } @Test @@ -1148,15 +1151,17 @@ void build_withCustomProvider_throwsOnGetKeyStore() @Test void build_withCustomProvider_throwsOnGetCertificatePath() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - // Simulate a scenario where the X509Provider cannot access or read the certificate - // configuration file needed to determine the certificate path, resulting in an IOException. + // Simulate a scenario where path resolution fails during build with a custom + // provider. + // We achieve this by passing a non-existent configuration path which causes + // MtlsUtils to throw + // IOException. KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(null, null); TestX509Provider x509Provider = new TestX509Provider(keyStore, "/path/to/certificate.json"); - x509Provider.setShouldThrowOnGetCertificatePath(true); // Configure to throw Map certificateMap = new HashMap<>(); - certificateMap.put("certificate_config_location", "/path/to/certificate.json"); + certificateMap.put("certificate_config_location", "/non/existent/path.json"); // Expect RuntimeException because the constructor wraps the IOException. RuntimeException exception = @@ -1164,10 +1169,9 @@ void build_withCustomProvider_throwsOnGetCertificatePath() RuntimeException.class, () -> createCredentialsWithCertificate(x509Provider, certificateMap)); - // Verify the cause is the expected IOException from the mock. + // Verify the cause is the expected IOException (or subclass) from MtlsUtils. assertNotNull(exception.getCause()); assertTrue(exception.getCause() instanceof IOException); - assertEquals("Simulated IOException on certificate path", exception.getCause().getMessage()); // Verify the wrapper exception message assertEquals( @@ -1276,7 +1280,6 @@ private static class TestX509Provider extends X509Provider { private final KeyStore keyStore; private final String certificatePath; private boolean shouldThrowOnGetKeyStore = false; - private boolean shouldThrowOnGetCertificatePath = false; TestX509Provider(KeyStore keyStore, String certificatePath) { super(); @@ -1292,20 +1295,8 @@ public KeyStore getKeyStore() throws IOException { return keyStore; } - @Override - public String getCertificatePath() throws IOException { - if (shouldThrowOnGetCertificatePath) { - throw new IOException("Simulated IOException on certificate path"); - } - return certificatePath; - } - void setShouldThrowOnGetKeyStore(boolean shouldThrow) { this.shouldThrowOnGetKeyStore = shouldThrow; } - - void setShouldThrowOnGetCertificatePath(boolean shouldThrow) { - this.shouldThrowOnGetCertificatePath = shouldThrow; - } } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 0a70c1ec7839..044aa0ce6755 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -34,12 +34,12 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -48,6 +48,7 @@ import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonGenerator; +import com.google.api.client.json.JsonParser; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebToken.Payload; import com.google.api.client.testing.http.MockLowLevelHttpRequest; @@ -123,10 +124,6 @@ class ImpersonatedCredentialsTest extends BaseSerializationTest { private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final String TEST_UNIVERSE_DOMAIN = "test.xyz"; - private static final String OLD_IMPERSONATION_URL = - "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" - + IMPERSONATED_CLIENT_EMAIL - + ":generateAccessToken"; public static final String DEFAULT_IMPERSONATION_URL = String.format( IamUtils.IAM_ACCESS_TOKEN_ENDPOINT_FORMAT, @@ -194,8 +191,7 @@ void fromJson_userAsSource_WithQuotaProjectId() throws IOException { assertEquals(DELEGATES, credentials.getDelegates()); assertEquals(IMMUTABLE_SCOPES_LIST, credentials.getScopes()); assertEquals(3600, credentials.getLifetime()); - GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); - assertTrue(sourceCredentials instanceof UserCredentials); + assertInstanceOf(UserCredentials.class, credentials.getSourceCredentials()); } @Test() @@ -217,8 +213,7 @@ void fromJson_userAsSource_WithoutQuotaProjectId() throws IOException { assertEquals(DELEGATES, credentials.getDelegates()); assertEquals(IMMUTABLE_SCOPES_LIST, credentials.getScopes()); assertEquals(3600, credentials.getLifetime()); - GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); - assertTrue(sourceCredentials instanceof UserCredentials); + assertInstanceOf(UserCredentials.class, credentials.getSourceCredentials()); } @Test() @@ -241,8 +236,7 @@ void fromJson_userAsSource_MissingDelegatesField() throws IOException { assertEquals(new ArrayList(), credentials.getDelegates()); assertEquals(IMMUTABLE_SCOPES_LIST, credentials.getScopes()); assertEquals(3600, credentials.getLifetime()); - GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); - assertTrue(sourceCredentials instanceof UserCredentials); + assertInstanceOf(UserCredentials.class, credentials.getSourceCredentials()); } @Test() @@ -258,19 +252,17 @@ void fromJson_ServiceAccountAsSource() throws IOException { assertEquals(DELEGATES, credentials.getDelegates()); assertEquals(IMMUTABLE_SCOPES_LIST, credentials.getScopes()); assertEquals(3600, credentials.getLifetime()); - GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); - assertTrue(sourceCredentials instanceof ServiceAccountCredentials); + assertInstanceOf(ServiceAccountCredentials.class, credentials.getSourceCredentials()); } @Test() - void fromJson_InvalidFormat() throws IOException { + void fromJson_InvalidFormat() { GenericJson json = buildInvalidCredentialsJson(); - try { - ImpersonatedCredentials.fromJson(json, mockTransportFactory); - fail("An exception should be thrown."); - } catch (CredentialFormatException e) { - assertEquals("An invalid input stream was provided.", e.getMessage()); - } + CredentialFormatException e = + assertThrows( + CredentialFormatException.class, + () -> ImpersonatedCredentials.fromJson(json, mockTransportFactory)); + assertEquals("An invalid input stream was provided.", e.getMessage()); } @Test() @@ -280,7 +272,7 @@ void createScopedRequired_True() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - new ArrayList(), + new ArrayList<>(), VALID_LIFETIME, mockTransportFactory); assertTrue(targetCredentials.createScopedRequired()); @@ -311,15 +303,15 @@ void createScoped() { mockTransportFactory, QUOTA_PROJECT_ID); - ImpersonatedCredentials scoped_credentials = + ImpersonatedCredentials scopedCredentials = (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_LIST); - assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount()); - assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates()); - assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime()); + assertEquals(targetCredentials.getAccount(), scopedCredentials.getAccount()); + assertEquals(targetCredentials.getDelegates(), scopedCredentials.getDelegates()); + assertEquals(targetCredentials.getLifetime(), scopedCredentials.getLifetime()); assertEquals( - targetCredentials.getSourceCredentials(), scoped_credentials.getSourceCredentials()); - assertEquals(targetCredentials.getQuotaProjectId(), scoped_credentials.getQuotaProjectId()); - assertEquals(Arrays.asList("scope1", "scope2"), scoped_credentials.getScopes()); + targetCredentials.getSourceCredentials(), scopedCredentials.getSourceCredentials()); + assertEquals(targetCredentials.getQuotaProjectId(), scopedCredentials.getQuotaProjectId()); + assertEquals(Arrays.asList("scope1", "scope2"), scopedCredentials.getScopes()); } @Test @@ -341,10 +333,10 @@ void createScoped_existingAccessTokenInvalidated() throws IOException { targetCredentials.refresh(); assertEquals(ACCESS_TOKEN, targetCredentials.getAccessToken().getTokenValue()); - ImpersonatedCredentials scoped_credentials = + ImpersonatedCredentials scopedCredentials = (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_LIST); - assertNull(scoped_credentials.getAccessToken()); + assertNull(scopedCredentials.getAccessToken()); } @Test @@ -359,15 +351,15 @@ void createScopedWithImmutableScopes() { mockTransportFactory, QUOTA_PROJECT_ID); - ImpersonatedCredentials scoped_credentials = + ImpersonatedCredentials scopedCredentials = (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_SET); - assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount()); - assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates()); - assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime()); + assertEquals(targetCredentials.getAccount(), scopedCredentials.getAccount()); + assertEquals(targetCredentials.getDelegates(), scopedCredentials.getDelegates()); + assertEquals(targetCredentials.getLifetime(), scopedCredentials.getLifetime()); assertEquals( - targetCredentials.getSourceCredentials(), scoped_credentials.getSourceCredentials()); - assertEquals(targetCredentials.getQuotaProjectId(), scoped_credentials.getQuotaProjectId()); - assertEquals(Arrays.asList("scope1", "scope2"), scoped_credentials.getScopes()); + targetCredentials.getSourceCredentials(), scopedCredentials.getSourceCredentials()); + assertEquals(targetCredentials.getQuotaProjectId(), scopedCredentials.getQuotaProjectId()); + assertEquals(Arrays.asList("scope1", "scope2"), scopedCredentials.getScopes()); } @Test @@ -383,10 +375,10 @@ void createScopedWithIamEndpointOverride() { QUOTA_PROJECT_ID, IMPERSONATION_OVERRIDE_URL); - ImpersonatedCredentials scoped_credentials = + ImpersonatedCredentials scopedCredentials = (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_SET); assertEquals( - targetCredentials.getIamEndpointOverride(), scoped_credentials.getIamEndpointOverride()); + targetCredentials.getIamEndpointOverride(), scopedCredentials.getIamEndpointOverride()); } @Test @@ -409,13 +401,9 @@ void refreshAccessToken_unauthorized() throws IOException { VALID_LIFETIME, mockTransportFactory); - try { - targetCredentials.refreshAccessToken().getTokenValue(); - fail(String.format("Should throw exception with message containing '%s'", expectedMessage)); - } catch (IOException expected) { - assertEquals("Error requesting access token", expected.getMessage()); - assertTrue(expected.getCause().getMessage().contains(expectedMessage)); - } + IOException expected = assertThrows(IOException.class, targetCredentials::refreshAccessToken); + assertEquals("Error requesting access token", expected.getMessage()); + assertTrue(expected.getCause().getMessage().contains(expectedMessage)); } @Test() @@ -439,13 +427,9 @@ void refreshAccessToken_malformedTarget() throws IOException { VALID_LIFETIME, mockTransportFactory); - try { - targetCredentials.refreshAccessToken().getTokenValue(); - fail(String.format("Should throw exception with message containing '%s'", expectedMessage)); - } catch (IOException expected) { - assertEquals("Error requesting access token", expected.getMessage()); - assertTrue(expected.getCause().getMessage().contains(expectedMessage)); - } + IOException expected = assertThrows(IOException.class, targetCredentials::refreshAccessToken); + assertEquals("Error requesting access token", expected.getMessage()); + assertTrue(expected.getCause().getMessage().contains(expectedMessage)); } @Test() @@ -457,28 +441,22 @@ void credential_with_zero_lifetime() throws IllegalStateException { } @Test() - void credential_with_invalid_lifetime() throws IOException, IllegalStateException { - - try { - ImpersonatedCredentials targetCredentials = - ImpersonatedCredentials.create( - sourceCredentials, - IMPERSONATED_CLIENT_EMAIL, - null, - IMMUTABLE_SCOPES_LIST, - INVALID_LIFETIME); - targetCredentials.refreshAccessToken().getTokenValue(); - fail( - String.format( - "Should throw exception with message containing '%s'", - "lifetime must be less than or equal to 43200")); - } catch (IllegalStateException expected) { - assertTrue(expected.getMessage().contains("lifetime must be less than or equal to 43200")); - } + void credential_with_invalid_lifetime() throws IllegalStateException { + IllegalStateException expected = + assertThrows( + IllegalStateException.class, + () -> + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + IMMUTABLE_SCOPES_LIST, + INVALID_LIFETIME)); + assertTrue(expected.getMessage().contains("lifetime must be less than or equal to 43200")); } @Test() - void credential_with_invalid_scope() throws IOException, IllegalStateException { + void credential_with_invalid_scope() throws IllegalStateException { assertThrows( NullPointerException.class, () -> @@ -667,12 +645,8 @@ void refreshAccessToken_invalidDate() throws IllegalStateException { VALID_LIFETIME, mockTransportFactory); - try { - targetCredentials.refreshAccessToken().getTokenValue(); - fail(String.format("Should throw exception with message containing '%s'", expectedMessage)); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedMessage)); - } + IOException expected = assertThrows(IOException.class, targetCredentials::refreshAccessToken); + assertTrue(expected.getMessage().contains(expectedMessage)); } @Test @@ -738,13 +712,13 @@ void sign_requestIncludesDelegates() throws IOException { assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature)); MockLowLevelHttpRequest request = mockTransportFactory.getTransport().getRequest(); - GenericJson body = - JSON_FACTORY - .createJsonParser(request.getContentAsString()) - .parseAndClose(GenericJson.class); - List delegates = new ArrayList<>(); - delegates.add("delegate@example.com"); - assertEquals(delegates, body.get("delegates")); + + try (JsonParser jsonParser = JSON_FACTORY.createJsonParser(request.getContentAsString())) { + GenericJson body = jsonParser.parseAndClose(GenericJson.class); + List delegates = new ArrayList<>(); + delegates.add("delegate@example.com"); + assertEquals(delegates, body.get("delegates")); + } } @Test @@ -752,7 +726,7 @@ void sign_usesSourceCredentials() { Calendar c = Calendar.getInstance(); c.add(Calendar.DATE, 1); Date expiry = c.getTime(); - GoogleCredentials sourceCredentials = + GoogleCredentials sourceCred = new GoogleCredentials.Builder() .setAccessToken(new AccessToken("source-token", expiry)) .build(); @@ -763,7 +737,7 @@ void sign_usesSourceCredentials() { mockTransportFactory.getTransport().addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_OK, ""); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( - sourceCredentials, + sourceCred, IMPERSONATED_CLIENT_EMAIL, ImmutableList.of("delegate@example.com"), IMMUTABLE_SCOPES_LIST, @@ -803,15 +777,11 @@ void sign_accessDenied_throws() { .getTransport() .addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_FORBIDDEN, "Sign Error"); - try { - byte[] bytes = {0xD, 0xE, 0xA, 0xD}; - targetCredentials.sign(bytes); - fail("Signing should have failed"); - } catch (SigningException e) { - assertEquals("Failed to sign the provided bytes", e.getMessage()); - assertNotNull(e.getCause()); - assertTrue(e.getCause().getMessage().contains("403")); - } + SigningException e = + assertThrows(SigningException.class, () -> targetCredentials.sign(expectedSignature)); + assertEquals("Failed to sign the provided bytes", e.getMessage()); + assertNotNull(e.getCause()); + assertTrue(e.getCause().getMessage().contains("403")); } @Test @@ -836,15 +806,11 @@ void sign_serverError_throws() { .getTransport() .addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_NOT_FOUND, "Sign Error"); - try { - byte[] bytes = {0xD, 0xE, 0xA, 0xD}; - targetCredentials.sign(bytes); - fail("Signing should have failed"); - } catch (SigningException e) { - assertEquals("Failed to sign the provided bytes", e.getMessage()); - assertNotNull(e.getCause()); - assertTrue(e.getCause().getMessage().contains("404")); - } + SigningException e = + assertThrows(SigningException.class, () -> targetCredentials.sign(expectedSignature)); + assertEquals("Failed to sign the provided bytes", e.getMessage()); + assertNotNull(e.getCause()); + assertTrue(e.getCause().getMessage().contains("404")); } @Test @@ -1039,12 +1005,8 @@ void idToken_withServerError() { .setIdTokenProvider(targetCredentials) .setTargetAudience(targetAudience) .build(); - try { - tokenCredential.refresh(); - fail("Should not be able to use credential without exception."); - } catch (IOException e) { - assertTrue(e.getMessage().contains("Error code 500 trying to getIDToken")); - } + IOException e = assertThrows(IOException.class, tokenCredential::refresh); + assertTrue(e.getMessage().contains("Error code 500 trying to getIDToken")); } @Test @@ -1073,12 +1035,8 @@ void idToken_withOtherError() { .setIdTokenProvider(targetCredentials) .setTargetAudience(targetAudience) .build(); - try { - tokenCredential.refresh(); - fail("Should not be able to use credential without exception."); - } catch (IOException e) { - assertTrue(e.getMessage().contains("Unexpected Error code 301 trying to getIDToken")); - } + IOException e = assertThrows(IOException.class, tokenCredential::refresh); + assertTrue(e.getMessage().contains("Unexpected Error code 301 trying to getIDToken")); } @Test @@ -1115,22 +1073,20 @@ void universeDomain_getFromSourceCredentials() throws IOException { } @Test - void universeDomain_whenExplicit_notAllowedIfNotMatchToSourceUD() throws IOException { + void universeDomain_whenExplicit_notAllowedIfNotMatchToSourceUD() { GoogleCredentials sourceCredentialsNonGDU = sourceCredentials.toBuilder().setUniverseDomain("source.domain.xyz").build(); + GoogleCredentials.Builder builder = + ImpersonatedCredentials.newBuilder() + .setSourceCredentials(sourceCredentialsNonGDU) + .setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL) + .setDelegates(null) + .setScopes(IMMUTABLE_SCOPES_LIST) + .setLifetime(VALID_LIFETIME) + .setHttpTransportFactory(mockTransportFactory) + .setUniverseDomain("explicit.domain.com"); IllegalStateException illegalStateException = - assertThrows( - IllegalStateException.class, - () -> - ImpersonatedCredentials.newBuilder() - .setSourceCredentials(sourceCredentialsNonGDU) - .setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL) - .setDelegates(null) - .setScopes(IMMUTABLE_SCOPES_LIST) - .setLifetime(VALID_LIFETIME) - .setHttpTransportFactory(mockTransportFactory) - .setUniverseDomain("explicit.domain.com") - .build()); + assertThrows(IllegalStateException.class, builder::build); assertEquals( "Universe domain source.domain.xyz in source credentials" + " does not match explicit.domain.com universe domain set for impersonated credentials.", @@ -1195,7 +1151,7 @@ void scopes_userConfiguredAndFromJson() throws IOException { } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { mockTransportFactory.getTransport().setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); mockTransportFactory.getTransport().setAccessToken(ACCESS_TOKEN); mockTransportFactory.getTransport().setExpireTime(getDefaultExpireTime()); @@ -1240,7 +1196,7 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(targetCredentials, deserializedCredentials); assertEquals(targetCredentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(targetCredentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } /** diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java index 95a4e3f4d81a..dc93eede2141 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java @@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.gson.GsonFactory; @@ -95,51 +95,38 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.getClock(), Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.getClock()); } @Test void builder_requiresPrivateKey() { - try { - JwtClaims claims = - JwtClaims.newBuilder() - .setAudience("some-audience") - .setIssuer("some-issuer") - .setSubject("some-subject") - .build(); - JwtCredentials.newBuilder().setJwtClaims(claims).setPrivateKeyId(PRIVATE_KEY_ID).build(); - fail("Should throw exception"); - } catch (NullPointerException ex) { - // expected - } + JwtClaims claims = + JwtClaims.newBuilder() + .setAudience("some-audience") + .setIssuer("some-issuer") + .setSubject("some-subject") + .build(); + JwtCredentials.Builder builder = + JwtCredentials.newBuilder().setJwtClaims(claims).setPrivateKeyId(PRIVATE_KEY_ID); + assertThrows(NullPointerException.class, builder::build); } @Test void builder_requiresClaims() { - try { - JwtCredentials.newBuilder() - .setPrivateKeyId(PRIVATE_KEY_ID) - .setPrivateKey(getPrivateKey()) - .build(); - fail("Should throw exception"); - } catch (NullPointerException ex) { - // expected - } + JwtCredentials.Builder builder = + JwtCredentials.newBuilder().setPrivateKeyId(PRIVATE_KEY_ID).setPrivateKey(getPrivateKey()); + assertThrows(NullPointerException.class, builder::build); } @Test void builder_requiresCompleteClaims() { - try { - JwtClaims claims = JwtClaims.newBuilder().build(); - JwtCredentials.newBuilder() - .setJwtClaims(claims) - .setPrivateKeyId(PRIVATE_KEY_ID) - .setPrivateKey(getPrivateKey()) - .build(); - fail("Should throw exception"); - } catch (IllegalStateException ex) { - // expected - } + JwtClaims claims = JwtClaims.newBuilder().build(); + JwtCredentials.Builder builder = + JwtCredentials.newBuilder() + .setJwtClaims(claims) + .setPrivateKeyId(PRIVATE_KEY_ID) + .setPrivateKey(getPrivateKey()); + assertThrows(IllegalStateException.class, builder::build); } @Test diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index fc9f8ba3e80b..7719b08d2e7b 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -84,11 +84,11 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { static final String IAM_ENDPOINT = "https://iamcredentials.googleapis.com"; - private Queue responseSequence = new ArrayDeque<>(); - private Queue responseErrorSequence = new ArrayDeque<>(); - private Queue refreshTokenSequence = new ArrayDeque<>(); - private Queue> scopeSequence = new ArrayDeque<>(); - private List requests = new ArrayList<>(); + private final Queue responseSequence = new ArrayDeque<>(); + private final Queue responseErrorSequence = new ArrayDeque<>(); + private final Queue refreshTokenSequence = new ArrayDeque<>(); + private final Queue> scopeSequence = new ArrayDeque<>(); + private final List requests = new ArrayList<>(); private String expireTime; private String metadataServerContentType; private String stsContent; diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockIAMCredentialsServiceTransportFactory.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockIAMCredentialsServiceTransportFactory.java index a1df56b4b21a..c21a42f376cd 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockIAMCredentialsServiceTransportFactory.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockIAMCredentialsServiceTransportFactory.java @@ -35,7 +35,7 @@ import com.google.auth.http.HttpTransportFactory; public class MockIAMCredentialsServiceTransportFactory implements HttpTransportFactory { - private MockIAMCredentialsServiceTransport transport; + private final MockIAMCredentialsServiceTransport transport; MockIAMCredentialsServiceTransportFactory() { this(Credentials.GOOGLE_DEFAULT_UNIVERSE); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java index 725a124fcd15..1b218b73ef45 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java @@ -44,8 +44,7 @@ import com.google.common.io.BaseEncoding; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; @@ -168,10 +167,9 @@ public LowLevelHttpResponse execute() throws IOException { GenericJson signContents = new GenericJson(); signContents.setFactory(OAuth2Utils.JSON_FACTORY); signContents.put("signedBlob", BaseEncoding.base64().encode(signature)); - - String signature = signContents.toPrettyString(); - - return new MockLowLevelHttpResponse().setContentType(Json.MEDIA_TYPE).setContent(signature); + return new MockLowLevelHttpResponse() + .setContentType(Json.MEDIA_TYPE) + .setContent(signContents.toPrettyString()); } }; } @@ -228,7 +226,7 @@ public LowLevelHttpResponse execute() throws IOException { } private MockLowLevelHttpRequest getMockRequestForIdentityDocument(String url) - throws MalformedURLException, UnsupportedEncodingException { + throws UnsupportedEncodingException { if (statusCode != null && statusCode != HttpStatusCodes.STATUS_CODE_OK) { return new MockLowLevelHttpRequest(url) { @Override @@ -254,7 +252,7 @@ public LowLevelHttpResponse execute() { // https://cloud.google.com/compute/docs/instances/verifying-instance-identity#token_format Map queryPairs = new HashMap(); - String query = (new URL(url)).getQuery(); + String query = (URI.create(url)).getQuery(); String[] pairs = query.split("&"); for (String pair : pairs) { int idx = pair.indexOf("="); @@ -263,33 +261,29 @@ public LowLevelHttpResponse execute() { URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } - if (queryPairs.containsKey("format")) { - if (((String) queryPairs.get("format")).equals("full")) { - - // return licenses only if format=full is set - if (queryPairs.containsKey("licenses")) { - // The metadata server defaults to false and matches "on", "off" and ::absl::SimpleAtob. - // See https://abseil.io/docs/cpp/guides/strings#numericConversion for more information. - if (BOOL_PARAMETER_VALUE.matcher((String) queryPairs.get("licenses")).matches()) { - return new MockLowLevelHttpRequest(url) { - @Override - public LowLevelHttpResponse execute() throws IOException { - return new MockLowLevelHttpResponse() - .setContent(ComputeEngineCredentialsTest.FULL_ID_TOKEN_WITH_LICENSES); - } - }; - } - } - // otherwise return full format + if (queryPairs.containsKey("format") && queryPairs.get("format").equals("full")) { + // return licenses only if format=full is set + if (queryPairs.containsKey("licenses") + && BOOL_PARAMETER_VALUE.matcher(queryPairs.get("licenses")).matches()) { return new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { return new MockLowLevelHttpResponse() - .setContent(ComputeEngineCredentialsTest.FULL_ID_TOKEN); + .setContent(ComputeEngineCredentialsTest.FULL_ID_TOKEN_WITH_LICENSES); } }; } + + // otherwise return full format + return new MockLowLevelHttpRequest(url) { + @Override + public LowLevelHttpResponse execute() throws IOException { + return new MockLowLevelHttpResponse() + .setContent(ComputeEngineCredentialsTest.FULL_ID_TOKEN); + } + }; } + // Return default format if nothing is set return new MockLowLevelHttpRequest(url) { @Override diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenCheckingTransport.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenCheckingTransport.java index 3ef4df2fe942..11af02041343 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenCheckingTransport.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenCheckingTransport.java @@ -44,8 +44,8 @@ public class MockTokenCheckingTransport extends HttpTransport { public static final String SUCCESS_CONTENT = "{\"key\":\"value\"}"; - private MockTokenServerTransport tokenServer; - private String refreshToken; + private final MockTokenServerTransport tokenServer; + private final String refreshToken; public MockTokenCheckingTransport(MockTokenServerTransport tokenServer, String refreshToken) { this.tokenServer = tokenServer; diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java index a61c185b5704..5a6cd2e5d1a8 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java @@ -75,7 +75,6 @@ public class MockTokenServerTransport extends MockHttpTransport { private final Queue> responseSequence = new ArrayDeque<>(); private int expiresInSeconds = 3600; private MockLowLevelHttpRequest request; - private ClientAuthenticationType clientAuthenticationType; private PKCEProvider pkceProvider; public MockTokenServerTransport() {} @@ -88,9 +87,7 @@ public void setTokenServerUri(URI tokenServerUri) { this.tokenServerUri = tokenServerUri; } - public void setClientAuthType(ClientAuthenticationType type) { - this.clientAuthenticationType = type; - } + public void setClientAuthType(ClientAuthenticationType type) {} public void setPkceProvider(PKCEProvider pkceProvider) { this.pkceProvider = pkceProvider; @@ -141,9 +138,9 @@ public void setError(IOException error) { this.error = error; } - public void addResponseErrorSequence(IOException... errors) { - for (IOException error : errors) { - responseSequence.add(Futures.immediateFailedFuture(error)); + public void addResponseErrorSequence(IOException... ioExceptions) { + for (IOException ioException : ioExceptions) { + responseSequence.add(Futures.immediateFailedFuture(ioException)); } } @@ -173,7 +170,6 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce } int questionMarkPos = url.indexOf('?'); final String urlWithoutQuery = (questionMarkPos > 0) ? url.substring(0, questionMarkPos) : url; - final String query = (questionMarkPos > 0) ? url.substring(questionMarkPos + 1) : ""; if (!responseSequence.isEmpty()) { request = @@ -213,7 +209,7 @@ public LowLevelHttpResponse execute() throws IOException { } String content = this.getContentAsString(); - Map query = TestUtils.parseQuery(content); + Map query = parseRequestContent(content); String accessToken = null; String refreshToken = null; String grantedScopesString = null; @@ -255,16 +251,18 @@ public LowLevelHttpResponse execute() throws IOException { } else if (query.containsKey("grant_type")) { String grantType = query.get("grant_type"); String assertion = query.get("assertion"); + if (assertion == null) { + assertion = query.get("subject_token"); + } JsonWebSignature signature = JsonWebSignature.parse(JSON_FACTORY, assertion); if (OAuth2Utils.GRANT_TYPE_JWT_BEARER.equals(grantType)) { String foundEmail = signature.getPayload().getIssuer(); - if (!serviceAccounts.containsKey(foundEmail)) {} accessToken = serviceAccounts.get(foundEmail); String foundTargetAudience = (String) signature.getPayload().get("target_audience"); String foundScopes = (String) signature.getPayload().get("scope"); - if ((foundScopes == null || foundScopes.length() == 0) - && (foundTargetAudience == null || foundTargetAudience.length() == 0)) { + if ((foundScopes == null || foundScopes.isEmpty()) + && (foundTargetAudience == null || foundTargetAudience.isEmpty())) { throw new IOException("Either target_audience or scopes must be specified."); } @@ -284,7 +282,10 @@ public LowLevelHttpResponse execute() throws IOException { "GDCH Service Account Service Identity Name not found as issuer."); } accessToken = gdchServiceAccounts.get(foundServiceIdentityName); - String foundApiAudience = (String) signature.getPayload().get("api_audience"); + String foundApiAudience = query.get("audience"); + if (foundApiAudience == null || foundApiAudience.isEmpty()) { + foundApiAudience = (String) signature.getPayload().get("api_audience"); + } if ((foundApiAudience == null || foundApiAudience.length() == 0)) { throw new IOException("Api_audience must be specified."); } @@ -326,7 +327,7 @@ public LowLevelHttpResponse execute() throws IOException { new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { - Map parameters = TestUtils.parseQuery(this.getContentAsString()); + Map parameters = parseRequestContent(this.getContentAsString()); String token = parameters.get("token"); if (token == null) { throw new IOException("Token to revoke not found."); @@ -358,7 +359,7 @@ public LowLevelHttpResponse execute() throws IOException { } String content = this.getContentAsString(); - Map query = TestUtils.parseQuery(content); + Map query = parseRequestContent(content); // Validate required fields. if (!query.containsKey("code") @@ -414,6 +415,13 @@ public LowLevelHttpResponse execute() throws IOException { return super.buildRequest(method, url); } + private Map parseRequestContent(String content) throws IOException { + if (content != null && content.trim().startsWith("{")) { + return TestUtils.parseJson(content); + } + return TestUtils.parseQuery(content); + } + private void validateAdditionalParameters(Map query) { if (additionalParameters.containsKey(query.get("code"))) { Map additionalParametersMap = additionalParameters.get(query.get("code")); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java index a54765abcbbb..c726006180ca 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java @@ -32,14 +32,15 @@ package com.google.auth.oauth2; import static java.util.concurrent.TimeUnit.HOURS; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.util.Clock; import com.google.auth.TestClock; @@ -100,7 +101,7 @@ void tearDown() { void constructor_storesAccessToken() { OAuth2Credentials credentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(ACCESS_TOKEN, null)).build(); - assertEquals(credentials.getAccessToken().getTokenValue(), ACCESS_TOKEN); + assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue()); } @Test @@ -169,7 +170,7 @@ public AccessToken refreshAccessToken() throws IOException { assertEquals(lastMetadata, callback.awaitResult()); waitForRefreshTaskCompletion(credentials); assertEquals(1, refreshCount.get()); - lastMetadata = credentials.getRequestMetadata(); + credentials.getRequestMetadata(); refreshCount.set(0); // Fast forward to expired @@ -181,7 +182,7 @@ public AccessToken refreshAccessToken() throws IOException { TestUtils.assertContainsBearerToken(callback.awaitResult(), newToken.getTokenValue()); assertEquals(1, refreshCount.get()); waitForRefreshTaskCompletion(credentials); - lastMetadata = credentials.getRequestMetadata(); + credentials.getRequestMetadata(); } @Test @@ -192,7 +193,7 @@ void getAuthenticationType_returnsOAuth2() { .setClientSecret(CLIENT_SECRET) .setRefreshToken(REFRESH_TOKEN) .build(); - assertEquals(credentials.getAuthenticationType(), "OAuth2"); + assertEquals("OAuth2", credentials.getAuthenticationType()); } @Test @@ -214,7 +215,7 @@ void hasRequestMetadataOnly_returnsTrue() { .setClientSecret(CLIENT_SECRET) .setRefreshToken(REFRESH_TOKEN) .build(); - assertTrue(credentials.hasRequestMetadata()); + assertTrue(credentials.hasRequestMetadataOnly()); } @Test @@ -331,13 +332,9 @@ void getRequestMetadata_blocking_cachesExpiringToken() throws IOException { clock.addToCurrentTime(60 * 60 * 1000); assertEquals(0, transportFactory.transport.buildRequestCount); - try { - credentials.getRequestMetadata(CALL_URI); - fail("Should throw"); - } catch (IOException e) { - assertSame(error, e.getCause()); - assertEquals(1, transportFactory.transport.buildRequestCount--); - } + IOException e = assertThrows(IOException.class, () -> credentials.getRequestMetadata(CALL_URI)); + assertSame(error, e.getCause()); + assertEquals(1, transportFactory.transport.buildRequestCount--); // Reset the error and try again transportFactory.transport.setError(null); @@ -347,7 +344,7 @@ void getRequestMetadata_blocking_cachesExpiringToken() throws IOException { } @Test - void getRequestMetadata_async() throws IOException { + void getRequestMetadata_async() { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); @@ -668,12 +665,12 @@ public Map> call() throws Exception { // Get the error that getRequestMetadata(uri) created Throwable actualBlockingError = - assertThrows(ExecutionException.class, () -> blockingCall.get()).getCause(); + assertThrows(ExecutionException.class, blockingCall::get).getCause(); assertEquals(error, actualBlockingError); RuntimeException actualAsyncError = - assertThrows(RuntimeException.class, () -> callback1.awaitResult()); + assertThrows(RuntimeException.class, callback1::awaitResult); assertEquals(error, actualAsyncError); } @@ -692,12 +689,9 @@ public AccessToken refreshAccessToken() { expectedStacktrace = expectedStacktrace.subList(1, expectedStacktrace.size()); AtomicReference actualError = new AtomicReference<>(); - try { - creds.getRequestMetadata(CALL_URI); - fail("Should not be able to use credential without exception."); - } catch (Exception refreshError) { - actualError.set(refreshError); - } + Exception refreshError = + assertThrows(RuntimeException.class, () -> creds.getRequestMetadata(CALL_URI)); + actualError.set(refreshError); List actualStacktrace = Arrays.asList(actualError.get().getStackTrace()); actualStacktrace = @@ -789,37 +783,37 @@ void refreshIfExpired_refreshesToken() throws IOException { } @Test - void refresh_temporaryToken_throws() throws IOException { + void refresh_temporaryToken_throws() { OAuth2Credentials credentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(ACCESS_TOKEN, null)).build(); - assertThrows(IllegalStateException.class, () -> credentials.refresh()); + assertThrows(IllegalStateException.class, credentials::refresh); } @Test - void equals_true() throws IOException { + void equals_true() { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; OAuth2Credentials credentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(accessToken1, null)).build(); OAuth2Credentials otherCredentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(accessToken1, null)).build(); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test - void equals_false_accessToken() throws IOException { + void equals_false_accessToken() { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; OAuth2Credentials credentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(accessToken1, null)).build(); OAuth2Credentials otherCredentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(accessToken2, null)).build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void toString_containsFields() throws IOException { + void toString_containsFields() { AccessToken accessToken = new AccessToken("1/MkSJoj1xsli0AccessToken_NKPY2", null); OAuth2Credentials credentials = OAuth2Credentials.newBuilder().setAccessToken(accessToken).build(); @@ -829,12 +823,12 @@ void toString_containsFields() throws IOException { ImmutableMap.of( AuthHttpConstants.AUTHORIZATION, ImmutableList.of(OAuth2Utils.BEARER_PREFIX + accessToken.getTokenValue())), - accessToken.toString()); + accessToken); assertEquals(expectedToString, credentials.toString()); } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; OAuth2Credentials credentials = OAuth2Credentials.newBuilder().setAccessToken(new AccessToken(accessToken, null)).build(); @@ -852,7 +846,7 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } @Test @@ -862,65 +856,16 @@ void updateTokenValueBeforeWake() throws IOException, InterruptedException { AccessToken refreshedToken = new AccessToken("2/MkSJoj1xsli0AccessToken_NKPY2", null); refreshedTokenFuture.set(refreshedToken); - final ListenableFutureTask task = - ListenableFutureTask.create( - new Callable() { - @Override - public OAuthValue call() throws Exception { - return OAuthValue.create(refreshedToken, new HashMap<>()); - } - }); - - OAuth2Credentials creds = - new OAuth2Credentials() { - @Override - public AccessToken refreshAccessToken() { - synchronized (this) { - // Wake up the main thread. This is done now because the child thread (t) is known to - // have the refresh task. Now we want the main thread to wake up and create a future - // in order to wait for the refresh to complete. - this.notify(); - } - RefreshTaskListener listener = - new RefreshTaskListener(task) { - @Override - public void run() { - try { - // Sleep before setting accessToken to new accessToken. Refresh should not - // complete before this, and the accessToken is `null` until it is. - Thread.sleep(300); - super.run(); - } catch (Exception e) { - fail("Unexpected error. Exception: " + e); - } - } - }; - - this.refreshTask = new RefreshTask(task, listener); - - try { - // Sleep for 100 milliseconds to give parent thread time to create a refresh future. - Thread.sleep(100); - return refreshedTokenFuture.get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }; + OAuth2Credentials creds = getOAuth2Credentials(refreshedToken, refreshedTokenFuture); Thread t = new Thread( - new Runnable() { - @Override - public void run() { - try { - creds.refresh(); - assertNotNull(creds.getAccessToken()); - } catch (Exception e) { - fail("Unexpected error. Exception: " + e); - } - } - }); + () -> + assertDoesNotThrow( + () -> { + creds.refresh(); + assertNotNull(creds.getAccessToken()); + })); t.start(); synchronized (creds) { @@ -941,6 +886,47 @@ public void run() { t.join(); } + private OAuth2Credentials getOAuth2Credentials( + AccessToken refreshedToken, SettableFuture refreshedTokenFuture) { + final ListenableFutureTask task = + ListenableFutureTask.create(() -> OAuthValue.create(refreshedToken, new HashMap<>())); + + return new OAuth2Credentials() { + @Override + public AccessToken refreshAccessToken() { + synchronized (this) { + // Wake up the main thread. This is done now because the child thread (t) is known to + // have the refresh task. Now we want the main thread to wake up and create a future + // in order to wait for the refresh to complete. + this.notify(); + } + RefreshTaskListener listener = + new RefreshTaskListener(task) { + @Override + public void run() { + assertDoesNotThrow( + () -> { + // Sleep before setting accessToken to new accessToken. Refresh should not + // complete before this, and the accessToken is `null` until it is. + Thread.sleep(300); + super.run(); + }); + } + }; + + this.refreshTask = new RefreshTask(task, listener); + + try { + // Sleep for 100 milliseconds to give parent thread time to create a refresh future. + Thread.sleep(100); + return refreshedTokenFuture.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } + private void waitForRefreshTaskCompletion(OAuth2Credentials credentials) throws TimeoutException, InterruptedException { for (int i = 0; i < 100; i++) { diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsWithRefreshTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsWithRefreshTest.java index 2c285c5491a8..a6b437eabc3b 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsWithRefreshTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsWithRefreshTest.java @@ -33,8 +33,9 @@ import static com.google.auth.oauth2.OAuth2Credentials.DEFAULT_EXPIRATION_MARGIN; import static com.google.auth.oauth2.OAuth2Credentials.DEFAULT_REFRESH_MARGIN; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.auth.TestUtils; import java.io.IOException; @@ -150,37 +151,24 @@ public AccessToken refreshAccessToken() { @Test void builder_noAccessToken() { - OAuth2CredentialsWithRefresh.newBuilder() - .setRefreshHandler( - new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() { - @Override - public AccessToken refreshAccessToken() { - return null; - } - }) - .build(); + OAuth2CredentialsWithRefresh.Builder builder = + OAuth2CredentialsWithRefresh.newBuilder().setRefreshHandler(() -> null); + assertDoesNotThrow(builder::build); } @Test void builder_noRefreshHandler_throws() { - try { - OAuth2CredentialsWithRefresh.newBuilder().setAccessToken(ACCESS_TOKEN).build(); - fail("Should fail as a refresh handler must be provided."); - } catch (NullPointerException e) { - // Expected. - } + OAuth2CredentialsWithRefresh.Builder builder = + OAuth2CredentialsWithRefresh.newBuilder().setAccessToken(ACCESS_TOKEN); + assertThrows(NullPointerException.class, builder::build); } @Test void builder_noExpirationTimeInAccessToken_throws() { - try { - OAuth2CredentialsWithRefresh.newBuilder() - .setAccessToken(new AccessToken("accessToken", null)) - .build(); - fail("Should fail as a refresh handler must be provided."); - } catch (IllegalArgumentException e) { - // Expected. - } + OAuth2CredentialsWithRefresh.Builder builder = + OAuth2CredentialsWithRefresh.newBuilder() + .setAccessToken(new AccessToken("accessToken", null)); + assertThrows(IllegalArgumentException.class, builder::build); } @Test @@ -189,13 +177,7 @@ void refreshAccessToken_delegateToRefreshHandler() throws IOException { OAuth2CredentialsWithRefresh credentials = OAuth2CredentialsWithRefresh.newBuilder() .setAccessToken(ACCESS_TOKEN) - .setRefreshHandler( - new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() { - @Override - public AccessToken refreshAccessToken() { - return refreshedToken; - } - }) + .setRefreshHandler(() -> refreshedToken) .build(); AccessToken accessToken = credentials.refreshAccessToken(); @@ -210,13 +192,7 @@ void getRequestMetadata() throws IOException { OAuth2CredentialsWithRefresh credentials = OAuth2CredentialsWithRefresh.newBuilder() .setAccessToken(ACCESS_TOKEN) - .setRefreshHandler( - new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() { - @Override - public AccessToken refreshAccessToken() { - return refreshedToken; - } - }) + .setRefreshHandler(() -> refreshedToken) .build(); Map> metadata = credentials.getRequestMetadata(uri); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index 9832c78215c0..094b21f9dbb2 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -36,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; @@ -94,7 +93,7 @@ void retrieveSubjectToken_shouldDelegateToHandler() throws IOException { .setExecutableHandler(options -> "pluggableAuthToken") .build(); String subjectToken = credential.retrieveSubjectToken(); - assertEquals(subjectToken, "pluggableAuthToken"); + assertEquals("pluggableAuthToken", subjectToken); } @Test @@ -119,24 +118,24 @@ void retrieveSubjectToken_shouldPassAllOptionsToHandler() throws IOException { String subjectToken = credential.retrieveSubjectToken(); - assertEquals(subjectToken, "pluggableAuthToken"); + assertEquals("pluggableAuthToken", subjectToken); // Validate that the correct options were passed to the executable handler. ExecutableOptions options = providedOptions[0]; - assertEquals(options.getExecutableCommand(), command); + assertEquals(command, options.getExecutableCommand()); assertEquals(options.getExecutableTimeoutMs(), Integer.parseInt(timeout)); - assertEquals(options.getOutputFilePath(), outputFile); + assertEquals(outputFile, options.getOutputFilePath()); Map envMap = options.getEnvironmentMap(); - assertEquals(envMap.size(), 5); + assertEquals(5, envMap.size()); assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"), credential.getAudience()); assertEquals( envMap.get("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"), credential.getSubjectTokenType()); - assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"), "0"); + assertEquals("0", envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE")); assertEquals( envMap.get("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL"), credential.getServiceAccountEmail()); - assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"), outputFile); + assertEquals(outputFile, envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE")); } @Test @@ -159,20 +158,20 @@ void retrieveSubjectToken_shouldPassMinimalOptionsToHandler() throws IOException String subjectToken = credential.retrieveSubjectToken(); - assertEquals(subjectToken, "pluggableAuthToken"); + assertEquals("pluggableAuthToken", subjectToken); // Validate that the correct options were passed to the executable handler. ExecutableOptions options = providedOptions[0]; - assertEquals(options.getExecutableCommand(), command); - assertEquals(options.getExecutableTimeoutMs(), DEFAULT_EXECUTABLE_TIMEOUT_MS); + assertEquals(command, options.getExecutableCommand()); + assertEquals(DEFAULT_EXECUTABLE_TIMEOUT_MS, options.getExecutableTimeoutMs()); assertNull(options.getOutputFilePath()); Map envMap = options.getEnvironmentMap(); - assertEquals(envMap.size(), 3); + assertEquals(3, envMap.size()); assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"), credential.getAudience()); assertEquals( envMap.get("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"), credential.getSubjectTokenType()); - assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"), "0"); + assertEquals("0", envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE")); assertNull(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL")); assertNull(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE")); } @@ -198,7 +197,7 @@ void refreshAccessToken_withoutServiceAccountImpersonation() throws IOException // Validate that the correct subject token was passed to STS. Map query = TestUtils.parseQuery(transportFactory.transport.getRequests().get(0).getContentAsString()); - assertEquals(query.get("subject_token"), "pluggableAuthToken"); + assertEquals("pluggableAuthToken", query.get("subject_token")); // Validate metrics header is set correctly on the sts request. Map> headers = @@ -239,7 +238,7 @@ void refreshAccessToken_withServiceAccountImpersonation() throws IOException { // Validate that the correct subject token was passed to STS. Map query = TestUtils.parseQuery(transportFactory.transport.getRequests().get(0).getContentAsString()); - assertEquals(query.get("subject_token"), "pluggableAuthToken"); + assertEquals("pluggableAuthToken", query.get("subject_token")); // Validate metrics header is set correctly on the sts request. Map> headers = @@ -265,7 +264,7 @@ void refreshAccessToken_withServiceAccountImpersonationOptions() throws IOExcept .setServiceAccountImpersonationUrl( transportFactory.transport.getServiceAccountImpersonationUrl()) .setServiceAccountImpersonationOptions( - ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800)) + ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions()) .setHttpTransportFactory(transportFactory) .build(); @@ -304,9 +303,9 @@ void pluggableAuthCredentialSource_allFields() { PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); - assertEquals(credentialSource.getCommand(), "/path/to/executable"); - assertEquals(credentialSource.getTimeoutMs(), 10000); - assertEquals(credentialSource.getOutputFilePath(), "/path/to/output/file"); + assertEquals("/path/to/executable", credentialSource.getCommand()); + assertEquals(10000, credentialSource.getTimeoutMs()); + assertEquals("/path/to/output/file", credentialSource.getOutputFilePath()); } @Test @@ -317,8 +316,8 @@ void pluggableAuthCredentialSource_noTimeoutProvided_setToDefault() { executable.put("command", "command"); PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); - assertEquals(credentialSource.getCommand(), "command"); - assertEquals(credentialSource.getTimeoutMs(), DEFAULT_EXECUTABLE_TIMEOUT_MS); + assertEquals("command", credentialSource.getCommand()); + assertEquals(DEFAULT_EXECUTABLE_TIMEOUT_MS, credentialSource.getTimeoutMs()); assertNull(credentialSource.getOutputFilePath()); } @@ -335,16 +334,14 @@ void pluggableAuthCredentialSource_timeoutProvidedOutOfRange_throws() { for (int value : possibleOutOfRangeValues) { executable.put("timeout_millis", value); - try { - new PluggableAuthCredentialSource(source); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - String.format( - "The executable timeout must be between %s and %s milliseconds.", - MINIMUM_EXECUTABLE_TIMEOUT_MS, MAXIMUM_EXECUTABLE_TIMEOUT_MS), - exception.getMessage()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> new PluggableAuthCredentialSource(source)); + assertEquals( + String.format( + "The executable timeout must be between %s and %s milliseconds.", + MINIMUM_EXECUTABLE_TIMEOUT_MS, MAXIMUM_EXECUTABLE_TIMEOUT_MS), + exception.getMessage()); } } @@ -362,21 +359,21 @@ void pluggableAuthCredentialSource_validTimeoutProvided() { executable.put("timeout_millis", value); PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); - assertEquals(credentialSource.getCommand(), "command"); - assertEquals(credentialSource.getTimeoutMs(), 10000); + assertEquals("command", credentialSource.getCommand()); + assertEquals(10000, credentialSource.getTimeoutMs()); assertNull(credentialSource.getOutputFilePath()); } } @Test void pluggableAuthCredentialSource_missingExecutableField_throws() { - try { - new PluggableAuthCredentialSource(new HashMap<>()); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "Invalid credential source for PluggableAuth credentials.", exception.getMessage()); - } + HashMap credentialSourceMap = new HashMap<>(); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new PluggableAuthCredentialSource(credentialSourceMap)); + assertEquals( + "Invalid credential source for PluggableAuth credentials.", exception.getMessage()); } @Test @@ -385,18 +382,16 @@ void pluggableAuthCredentialSource_missingExecutableCommandField_throws() { Map executable = new HashMap<>(); source.put("executable", executable); - try { - new PluggableAuthCredentialSource(source); - fail("Should not be able to continue without exception."); - } catch (IllegalArgumentException exception) { - assertEquals( - "The PluggableAuthCredentialSource is missing the required 'command' field.", - exception.getMessage()); - } + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> new PluggableAuthCredentialSource(source)); + assertEquals( + "The PluggableAuthCredentialSource is missing the required 'command' field.", + exception.getMessage()); } @Test - void builder_allFields() throws IOException { + void builder_allFields() { List scopes = Arrays.asList("scope1", "scope2"); PluggableAuthCredentialSource source = buildCredentialSource(); @@ -436,7 +431,7 @@ void builder_allFields() throws IOException { } @Test - void builder_missingUniverseDomain_defaults() throws IOException { + void builder_missingUniverseDomain_defaults() { List scopes = Arrays.asList("scope1", "scope2"); PluggableAuthCredentialSource source = buildCredentialSource(); @@ -475,7 +470,7 @@ void builder_missingUniverseDomain_defaults() throws IOException { } @Test - void newBuilder_allFields() throws IOException { + void newBuilder_allFields() { List scopes = Arrays.asList("scope1", "scope2"); PluggableAuthCredentialSource source = buildCredentialSource(); @@ -517,7 +512,7 @@ void newBuilder_allFields() throws IOException { } @Test - void newBuilder_noUniverseDomain_defaults() throws IOException { + void newBuilder_noUniverseDomain_defaults() { List scopes = Arrays.asList("scope1", "scope2"); PluggableAuthCredentialSource source = buildCredentialSource(); @@ -558,7 +553,7 @@ void newBuilder_noUniverseDomain_defaults() throws IOException { } @Test - void createdScoped_clonedCredentialWithAddedScopes() throws IOException { + void createdScoped_clonedCredentialWithAddedScopes() { PluggableAuthCredentials credentials = PluggableAuthCredentials.newBuilder(CREDENTIAL) .setExecutableHandler(options -> "pluggableAuthToken") @@ -591,7 +586,7 @@ void createdScoped_clonedCredentialWithAddedScopes() throws IOException { } @Test - void serialize() throws IOException, ClassNotFoundException { + void serialize() { PluggableAuthCredentials testCredentials = PluggableAuthCredentials.newBuilder(CREDENTIAL) .setExecutableHandler(options -> "pluggableAuthToken") diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java index f924d4137724..534ad9f33a2b 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java @@ -39,8 +39,6 @@ /** Tests for {@link PluggableAuthException}. */ class PluggableAuthExceptionTest { - private static final String MESSAGE_FORMAT = "Error code %s: %s"; - @Test void constructor() { PluggableAuthException e = new PluggableAuthException("errorCode", "errorDescription"); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java index c31266d27360..1566c7eed418 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java @@ -33,8 +33,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -189,7 +189,7 @@ void retrieveTokenFromExecutable_samlResponse() throws IOException, InterruptedE } @Test - void retrieveTokenFromExecutable_errorResponse_throws() throws InterruptedException, IOException { + void retrieveTokenFromExecutable_errorResponse_throws() throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -211,13 +211,12 @@ void retrieveTokenFromExecutable_errorResponse_throws() throws InterruptedExcept PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call retrieveTokenFromExecutable(). - try { - handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("401", e.getErrorCode()); - assertEquals("Caller not authorized.", e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + assertEquals("401", e.getErrorCode()); + assertEquals("Caller not authorized.", e.getErrorDescription()); } @Test @@ -281,7 +280,7 @@ void retrieveTokenFromExecutable_successResponseWithoutExpirationTimeField() @Test void retrieveTokenFromExecutable_successResponseWithoutExpirationTimeFieldWithOutputFileSpecified_throws() - throws InterruptedException, IOException { + throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -333,19 +332,14 @@ public String getOutputFilePath() { PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); - // Call retrieveTokenFromExecutable() should throw an exception as the STDOUT response - // is missing - // the `expiration_time` field and an output file was specified in the configuration. - try { - handler.retrieveTokenFromExecutable(options); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain the " - + "`expiration_time` field for successful responses when an output_file has been specified in the" - + " configuration.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, () -> handler.retrieveTokenFromExecutable(options)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain the " + + "`expiration_time` field for successful responses when an output_file has been specified in the" + + " configuration.", + exception.getMessage()); verify(mockProcess, times(i + 1)).destroy(); verify(mockProcess, times(i + 1)) @@ -410,16 +404,14 @@ public String getOutputFilePath() { // Call retrieveTokenFromExecutable() which should throw an exception as the output file // response is missing // the `expiration_time` field. - try { - handler.retrieveTokenFromExecutable(options); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException exception) { - assertEquals( - "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain the " - + "`expiration_time` field for successful responses when an output_file has been specified in the" - + " configuration.", - exception.getMessage()); - } + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, () -> handler.retrieveTokenFromExecutable(options)); + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain the " + + "`expiration_time` field for successful responses when an output_file has been specified in the" + + " configuration.", + exception.getMessage()); // Validate executable not invoked. verify(mockProcess, times(0)).destroyForcibly(); @@ -485,8 +477,7 @@ public String getOutputFilePath() { } @Test - void retrieveTokenFromExecutable_withInvalidOutputFile_throws() - throws IOException, InterruptedException { + void retrieveTokenFromExecutable_withInvalidOutputFile_throws() throws IOException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -530,12 +521,10 @@ public String getOutputFilePath() { PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call retrieveTokenFromExecutable(). - try { - handler.retrieveTokenFromExecutable(options); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("INVALID_OUTPUT_FILE", e.getErrorCode()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.retrieveTokenFromExecutable(options)); + assertEquals("INVALID_OUTPUT_FILE", e.getErrorCode()); } @Test @@ -629,18 +618,16 @@ void retrieveTokenFromExecutable_expiredResponse_throws() PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call retrieveTokenFromExecutable(). - try { - handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("INVALID_RESPONSE", e.getErrorCode()); - assertEquals("The executable response is expired.", e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + assertEquals("INVALID_RESPONSE", e.getErrorCode()); + assertEquals("The executable response is expired.", e.getErrorDescription()); } @Test - void retrieveTokenFromExecutable_invalidVersion_throws() - throws InterruptedException, IOException { + void retrieveTokenFromExecutable_invalidVersion_throws() throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -663,22 +650,20 @@ void retrieveTokenFromExecutable_invalidVersion_throws() PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call retrieveTokenFromExecutable(). - try { - handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("UNSUPPORTED_VERSION", e.getErrorCode()); - assertEquals( - "The version of the executable response is not supported. " - + String.format( - "The maximum version currently supported is %s.", - EXECUTABLE_SUPPORTED_MAX_VERSION), - e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + assertEquals("UNSUPPORTED_VERSION", e.getErrorCode()); + assertEquals( + "The version of the executable response is not supported. " + + String.format( + "The maximum version currently supported is %s.", EXECUTABLE_SUPPORTED_MAX_VERSION), + e.getErrorDescription()); } @Test - void retrieveTokenFromExecutable_allowExecutablesDisabled_throws() throws IOException { + void retrieveTokenFromExecutable_allowExecutablesDisabled_throws() { // In order to use Pluggable Auth, GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES must be set to 1. // If set to 0, a runtime exception should be thrown. TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); @@ -686,16 +671,15 @@ void retrieveTokenFromExecutable_allowExecutablesDisabled_throws() throws IOExce PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider); - try { - handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("PLUGGABLE_AUTH_DISABLED", e.getErrorCode()); - assertEquals( - "Pluggable Auth executables need to be explicitly allowed to run by " - + "setting the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable to 1.", - e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + assertEquals("PLUGGABLE_AUTH_DISABLED", e.getErrorCode()); + assertEquals( + "Pluggable Auth executables need to be explicitly allowed to run by " + + "setting the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable to 1.", + e.getErrorDescription()); } @Test @@ -853,7 +837,7 @@ void getExecutableResponse_errorResponse() throws IOException, InterruptedExcept } @Test - void getExecutableResponse_timeoutExceeded_throws() throws InterruptedException, IOException { + void getExecutableResponse_timeoutExceeded_throws() throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -867,14 +851,12 @@ void getExecutableResponse_timeoutExceeded_throws() throws InterruptedException, PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call getExecutableResponse(). - try { - handler.getExecutableResponse(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("TIMEOUT_EXCEEDED", e.getErrorCode()); - assertEquals( - "The executable failed to finish within the timeout specified.", e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + assertEquals("TIMEOUT_EXCEEDED", e.getErrorCode()); + assertEquals( + "The executable failed to finish within the timeout specified.", e.getErrorDescription()); verify(mockProcess, times(1)) .waitFor( @@ -883,7 +865,7 @@ void getExecutableResponse_timeoutExceeded_throws() throws InterruptedException, } @Test - void getExecutableResponse_nonZeroExitCode_throws() throws InterruptedException, IOException { + void getExecutableResponse_nonZeroExitCode_throws() throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -899,15 +881,13 @@ void getExecutableResponse_nonZeroExitCode_throws() throws InterruptedException, PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call getExecutableResponse(). - try { - handler.getExecutableResponse(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("EXIT_CODE", e.getErrorCode()); - assertEquals( - String.format("The executable failed with exit code %s.", EXIT_CODE_FAIL), - e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + assertEquals("EXIT_CODE", e.getErrorCode()); + assertEquals( + String.format("The executable failed with exit code %s.", EXIT_CODE_FAIL), + e.getErrorDescription()); verify(mockProcess, times(1)) .waitFor( @@ -916,7 +896,7 @@ void getExecutableResponse_nonZeroExitCode_throws() throws InterruptedException, } @Test - void getExecutableResponse_processInterrupted_throws() throws InterruptedException, IOException { + void getExecutableResponse_processInterrupted_throws() throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -931,15 +911,13 @@ void getExecutableResponse_processInterrupted_throws() throws InterruptedExcepti PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call getExecutableResponse(). - try { - handler.getExecutableResponse(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("INTERRUPTED", e.getErrorCode()); - assertEquals( - String.format("The execution was interrupted: %s.", new InterruptedException()), - e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + assertEquals("INTERRUPTED", e.getErrorCode()); + assertEquals( + String.format("The execution was interrupted: %s.", new InterruptedException()), + e.getErrorDescription()); verify(mockProcess, times(1)) .waitFor( @@ -948,7 +926,7 @@ void getExecutableResponse_processInterrupted_throws() throws InterruptedExcepti } @Test - void getExecutableResponse_invalidResponse_throws() throws InterruptedException, IOException { + void getExecutableResponse_invalidResponse_throws() throws InterruptedException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); @@ -969,15 +947,13 @@ void getExecutableResponse_invalidResponse_throws() throws InterruptedException, PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); // Call getExecutableResponse(). - try { - handler.getExecutableResponse(DEFAULT_OPTIONS); - fail("Should not be able to continue without exception."); - } catch (PluggableAuthException e) { - assertEquals("INVALID_RESPONSE", e.getErrorCode()); - assertEquals( - String.format("The executable returned an invalid response: %s.", badResponse), - e.getErrorDescription()); - } + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + assertEquals("INVALID_RESPONSE", e.getErrorCode()); + assertEquals( + String.format("The executable returned an invalid response: %s.", badResponse), + e.getErrorDescription()); verify(mockProcess, times(1)) .waitFor( diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/SecureSessionAgentConfigTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/SecureSessionAgentConfigTest.java index b9249517c640..3dc07f494c6d 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/SecureSessionAgentConfigTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/SecureSessionAgentConfigTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.Test; -/** Test cases for {@linkSecureSessionAgentConfig}. */ +/** Test cases for {@link SecureSessionAgentConfig}. */ class SecureSessionAgentConfigTest { private static final String S2A_PLAINTEXT_ADDRESS = "plaintext"; private static final String S2A_MTLS_ADDRESS = "mtls"; diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java index 2c516a9b2b4a..ed26a0af3c6f 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java @@ -32,6 +32,7 @@ package com.google.auth.oauth2; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -139,17 +140,14 @@ private static ServiceAccountCredentials.Builder createDefaultBuilderWithScopes( private static ServiceAccountCredentials.Builder createDefaultBuilderWithKey( PrivateKey privateKey) { - ServiceAccountCredentials.Builder builder = - ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(PRIVATE_KEY_ID) - .setProjectId(PROJECT_ID) - .setQuotaProjectId(QUOTA_PROJECT) - .setHttpTransportFactory(new MockHttpTransportFactory()); - - return builder; + return ServiceAccountCredentials.newBuilder() + .setClientId(CLIENT_ID) + .setClientEmail(CLIENT_EMAIL) + .setPrivateKey(privateKey) + .setPrivateKeyId(PRIVATE_KEY_ID) + .setProjectId(PROJECT_ID) + .setQuotaProjectId(QUOTA_PROJECT) + .setHttpTransportFactory(new MockHttpTransportFactory()); } static ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException { @@ -172,16 +170,11 @@ void setLifetime() throws IOException { } @Test - void setLifetime_invalid_lifetime() throws IOException, IllegalStateException { - try { - createDefaultBuilder().setLifetime(INVALID_LIFETIME).build(); - fail( - String.format( - "Should throw exception with message containing '%s'", - "lifetime must be less than or equal to 43200")); - } catch (IllegalStateException expected) { - assertTrue(expected.getMessage().contains("lifetime must be less than or equal to 43200")); - } + void setLifetime_invalid_lifetime() throws IllegalStateException, IOException { + ServiceAccountCredentials.Builder builder = + createDefaultBuilder().setLifetime(INVALID_LIFETIME); + IllegalStateException exception = assertThrows(IllegalStateException.class, builder::build); + assertTrue(exception.getMessage().contains("lifetime must be less than or equal to 43200")); } @Test @@ -300,7 +293,6 @@ void createAssertion_custom_lifetime() throws IOException { @Test void createAssertionForIdToken_correct() throws IOException { - PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = createDefaultBuilder() .setPrivateKeyId(PRIVATE_KEY_ID) @@ -369,14 +361,11 @@ void createdScoped_withAud_noUniverse_jwtWithScopesDisabled_accessToken() throws GoogleCredentials credentials = createDefaultBuilderWithToken(ACCESS_TOKEN).build(); // No aud, no scopes gives an exception. - try { - credentials.getRequestMetadata(null); - fail("Should not be able to get token without scopes"); - } catch (IOException e) { - assertTrue( - e.getMessage().contains("Scopes and uri are not configured for service account"), - "expected to fail with exception"); - } + IOException exception = + assertThrows(IOException.class, () -> credentials.getRequestMetadata(null)); + assertTrue( + exception.getMessage().contains("Scopes and uri are not configured for service account"), + "expected to fail with exception"); GoogleCredentials scopedCredentials = credentials.createScoped(SCOPES); assertEquals(false, credentials.isExplicitUniverseDomain()); @@ -389,15 +378,10 @@ void createdScoped_withAud_noUniverse_jwtWithScopesDisabled_accessToken() throws void createdScoped_withUniverse_selfSignedJwt() throws IOException { ServiceAccountCredentials credentials = createDefaultBuilder().setUniverseDomain("foo.bar").build(); - - try { - credentials.getRequestMetadata(null); - fail("Should not be able to get token without scopes"); - } catch (IOException e) { - assertTrue( - e.getMessage().contains("Scopes and uri are not configured for service account"), - "expected to fail with exception"); - } + IOException exception = + assertThrows(IOException.class, () -> credentials.getRequestMetadata(null)); + assertTrue( + exception.getMessage().contains("Scopes and uri are not configured for service account")); GoogleCredentials scopedCredentials = credentials.createScoped("dummy.scope"); Map> metadata = scopedCredentials.getRequestMetadata(null); @@ -429,14 +413,10 @@ void createdScoped_withUniverse_selfSignedJwt() throws IOException { void noScopes_withUniverse_selfSignedJwt() throws IOException { GoogleCredentials credentials = createDefaultBuilder().setUniverseDomain("foo.bar").build(); - try { - credentials.getRequestMetadata(null); - fail("Should not be able to get token without scopes"); - } catch (IOException e) { - assertTrue( - e.getMessage().contains("Scopes and uri are not configured for service account"), - "expected to fail with exception"); - } + IOException exception = + assertThrows(IOException.class, () -> credentials.getRequestMetadata(null)); + assertTrue( + exception.getMessage().contains("Scopes and uri are not configured for service account")); Map> metadata = credentials.getRequestMetadata(CALL_URI); assertNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope()); @@ -588,8 +568,8 @@ void fromJson_hasQuotaProjectId() throws IOException { assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); assertEquals( - metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY), - Collections.singletonList(QUOTA_PROJECT)); + Collections.singletonList(QUOTA_PROJECT), + metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); } @Test @@ -602,15 +582,15 @@ void getRequestMetadata_hasAccessToken() throws IOException { @Test void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException { - final URI TOKEN_SERVER = URI.create("https://foo.com/bar"); + final URI tokenServerUri = URI.create("https://foo.com/bar"); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - transportFactory.transport.setTokenServerUri(TOKEN_SERVER); + transportFactory.transport.setTokenServerUri(tokenServerUri); OAuth2Credentials credentials = createDefaultBuilder() .setScopes(SCOPES) .setHttpTransportFactory(transportFactory) - .setTokenServerUri(TOKEN_SERVER) + .setTokenServerUri(tokenServerUri) .build(); Map> metadata = credentials.getRequestMetadata(CALL_URI); @@ -672,7 +652,6 @@ void refreshAccessToken_IOException_Retry() throws IOException { MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); - ; transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -743,15 +722,11 @@ void refreshAccessToken_defaultRetriesDisabled() throws IOException { MockLowLevelHttpResponse response429 = new MockLowLevelHttpResponse().setStatusCode(429); transport.addServiceAccount(CLIENT_EMAIL, accessToken2); - try { - transport.addResponseSequence(response408, response429); - credentials.refresh(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - assertTrue(ex.getMessage().contains("Error getting access token for service account: 408")); - assertTrue(ex.isRetryable()); - assertEquals(0, ex.getRetryCount()); - } + transport.addResponseSequence(response408, response429); + GoogleAuthException ex = assertThrows(GoogleAuthException.class, credentials::refresh); + assertTrue(ex.getMessage().contains("Error getting access token for service account: 408")); + assertTrue(ex.isRetryable()); + assertEquals(0, ex.getRetryCount()); } @Test @@ -770,21 +745,17 @@ void refreshAccessToken_maxRetries_maxDelay() throws IOException { MockLowLevelHttpResponse response503 = new MockLowLevelHttpResponse().setStatusCode(503); Instant start = Instant.now(); - try { - transport.addResponseSequence(response408, response429, response500, response503); - credentials.refresh(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - Instant finish = Instant.now(); - long timeElapsed = Duration.between(start, finish).toMillis(); - - // we expect max retry time of 7 sec +/- jitter - assertTrue(timeElapsed > 5500 && timeElapsed < 10000); - assertTrue(ex.getMessage().contains("Error getting access token for service account: 503")); - assertTrue(ex.isRetryable()); - assertEquals(3, ex.getRetryCount()); - assertTrue(ex.getCause() instanceof HttpResponseException); - } + transport.addResponseSequence(response408, response429, response500, response503); + GoogleAuthException ex = assertThrows(GoogleAuthException.class, credentials::refresh); + Instant finish = Instant.now(); + long timeElapsed = Duration.between(start, finish).toMillis(); + + // we expect max retry time of 7 sec +/- jitter + assertTrue(timeElapsed > 5500 && timeElapsed < 10000); + assertTrue(ex.getMessage().contains("Error getting access token for service account: 503")); + assertTrue(ex.isRetryable()); + assertEquals(3, ex.getRetryCount()); + assertTrue(ex.getCause() instanceof HttpResponseException); } @Test @@ -801,24 +772,19 @@ void refreshAccessToken_RequestFailure_retried() throws IOException { MockLowLevelHttpResponse response503 = new MockLowLevelHttpResponse().setStatusCode(503); Instant start = Instant.now(); - try { - transport.addResponseSequence(response503); - transport.addResponseErrorSequence(error, error, error); - credentials.refresh(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - Instant finish = Instant.now(); - long timeElapsed = Duration.between(start, finish).toMillis(); - - // we expect max retry time of 7 sec +/- jitter - assertTrue(timeElapsed > 5500 && timeElapsed < 10000); - assertTrue( - ex.getMessage() - .contains("Error getting access token for service account: Invalid grant")); - assertTrue(ex.isRetryable()); - assertEquals(3, ex.getRetryCount()); - assertTrue(ex.getCause() instanceof IOException); - } + transport.addResponseSequence(response503); + transport.addResponseErrorSequence(error, error, error); + GoogleAuthException ex = assertThrows(GoogleAuthException.class, credentials::refresh); + Instant finish = Instant.now(); + long timeElapsed = Duration.between(start, finish).toMillis(); + + // we expect max retry time of 7 sec +/- jitter + assertTrue(timeElapsed > 5500 && timeElapsed < 10000); + assertTrue( + ex.getMessage().contains("Error getting access token for service account: Invalid grant")); + assertTrue(ex.isRetryable()); + assertEquals(3, ex.getRetryCount()); + assertTrue(ex.getCause() instanceof IOException); } @Test @@ -839,15 +805,11 @@ void refreshAccessToken_4xx_5xx_NonRetryableFails() throws IOException { } MockLowLevelHttpResponse mockResponse = new MockLowLevelHttpResponse().setStatusCode(status); - try { - transport.addResponseSequence(mockResponse); - transport.addServiceAccount(CLIENT_EMAIL, accessToken2); - credentials.refresh(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - assertFalse(ex.isRetryable()); - assertEquals(0, ex.getRetryCount()); - } + transport.addResponseSequence(mockResponse); + transport.addServiceAccount(CLIENT_EMAIL, accessToken2); + GoogleAuthException ex = assertThrows(GoogleAuthException.class, credentials::refresh); + assertFalse(ex.isRetryable()); + assertEquals(0, ex.getRetryCount()); } } @@ -1071,8 +1033,8 @@ void equals_true() throws IOException { SCOPES, transportFactory, tokenServer); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test @@ -1097,8 +1059,8 @@ void equals_false_clientId() throws IOException { SCOPES, serverTransportFactory, tokenServer1); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1123,8 +1085,8 @@ void equals_false_email() throws IOException { SCOPES, serverTransportFactory, tokenServer1); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1152,8 +1114,8 @@ void equals_false_super() throws IOException { .toBuilder() .setUniverseDomain("universe.com") .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1178,8 +1140,8 @@ void equals_false_keyId() throws IOException { SCOPES, serverTransportFactory, tokenServer1); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1204,8 +1166,8 @@ void equals_false_scopes() throws IOException { ImmutableSet.of(), serverTransportFactory, tokenServer1); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1231,8 +1193,8 @@ void equals_false_transportFactory() throws IOException { SCOPES, httpTransportFactory, tokenServer1); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1258,8 +1220,8 @@ void equals_false_tokenServer() throws IOException { SCOPES, serverTransportFactory, tokenServer2); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -1373,38 +1335,30 @@ void serialize() throws IOException, ClassNotFoundException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { output.writeObject(credentials); - String s = output.toString(); } ServiceAccountCredentials deserializedCredentials = serializeAndDeserialize(credentials); assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); assertEquals( MockTokenServerTransportFactory.class, deserializedCredentials.toBuilder().getHttpTransportFactory().getClass()); } @Test - void fromStream_nullTransport_throws() throws IOException { + void fromStream_nullTransport_throws() { InputStream stream = new ByteArrayInputStream("foo".getBytes()); - try { - ServiceAccountCredentials.fromStream(stream, null); - fail("Should throw if HttpTransportFactory is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows( + NullPointerException.class, () -> ServiceAccountCredentials.fromStream(stream, null)); } @Test - void fromStream_nullStream_throws() throws IOException { + void fromStream_nullStream_throws() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); - try { - ServiceAccountCredentials.fromStream(null, transportFactory); - fail("Should throw if InputStream is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows( + NullPointerException.class, + () -> ServiceAccountCredentials.fromStream(null, transportFactory)); } @Test @@ -1428,7 +1382,13 @@ void fromStream_noClientId_throws() throws IOException { InputStream serviceAccountStream = writeServiceAccountStream(null, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID); - testFromStreamException(serviceAccountStream, "client_id"); + IOException exception = + assertThrows( + IOException.class, + () -> + ServiceAccountCredentials.fromStream( + serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("client_id")); } @Test @@ -1436,7 +1396,13 @@ void fromStream_noClientEmail_throws() throws IOException { InputStream serviceAccountStream = writeServiceAccountStream(CLIENT_ID, null, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID); - testFromStreamException(serviceAccountStream, "client_email"); + IOException exception = + assertThrows( + IOException.class, + () -> + ServiceAccountCredentials.fromStream( + serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("client_email")); } @Test @@ -1456,12 +1422,8 @@ void getIdTokenWithAudience_badEmailError_issClaimTraced() throws IOException { String expectedErrorMessage = String.format("iss: %s", CLIENT_EMAIL); - try { - tokenCredential.refresh(); - fail("Should not be able to use credential without exception."); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedErrorMessage)); - } + IOException exception = assertThrows(IOException.class, tokenCredential::refresh); + assertTrue(exception.getMessage().contains(expectedErrorMessage)); } @Test @@ -1469,7 +1431,13 @@ void fromStream_noPrivateKey_throws() throws IOException { InputStream serviceAccountStream = writeServiceAccountStream(CLIENT_ID, CLIENT_EMAIL, null, PRIVATE_KEY_ID); - testFromStreamException(serviceAccountStream, "private_key"); + IOException exception = + assertThrows( + IOException.class, + () -> + ServiceAccountCredentials.fromStream( + serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("private_key")); } @Test @@ -1477,7 +1445,13 @@ void fromStream_noPrivateKeyId_throws() throws IOException { InputStream serviceAccountStream = writeServiceAccountStream(CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, null); - testFromStreamException(serviceAccountStream, "private_key_id"); + IOException exception = + assertThrows( + IOException.class, + () -> + ServiceAccountCredentials.fromStream( + serviceAccountStream, DUMMY_TRANSPORT_FACTORY)); + assertTrue(exception.getMessage().contains("private_key_id")); } @Test @@ -1586,7 +1560,7 @@ public void onSuccess(Map> metadata) { @Override public void onFailure(Throwable exception) { - fail("Should not throw a failure."); + // Test framework should fail if this is called and success.set(true) is not reached. } }); @@ -1626,7 +1600,7 @@ public void onSuccess(Map> metadata) { @Override public void onFailure(Throwable exception) { - fail("Should not throw a failure."); + // Test framework should fail if this is called and success.set(true) is not reached. } }); @@ -1683,12 +1657,7 @@ void refreshAccessToken_withDomainDelegation_selfSignedJWT_disabled() throws IOE Map> metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, accessToken1); - try { - verifyJwtAccess(metadata, "dummy.scope"); - fail("jwt access should fail with ServiceAccountUser"); - } catch (Exception ex) { - // expected - } + assertThrows(IllegalArgumentException.class, () -> verifyJwtAccess(metadata, "dummy.scope")); transport.addServiceAccount(CLIENT_EMAIL, accessToken2); credentials.refresh(); @@ -1755,11 +1724,7 @@ void getRequestMetadataWithCallback_selfSignedJWT() throws IOException { new RequestMetadataCallback() { @Override public void onSuccess(Map> metadata) { - try { - verifyJwtAccess(metadata, "dummy.scope"); - } catch (IOException e) { - fail("Should not throw a failure"); - } + assertDoesNotThrow(() -> verifyJwtAccess(metadata, "dummy.scope")); success.set(true); } @@ -1888,15 +1853,4 @@ static InputStream writeServiceAccountStream( clientId, clientEmail, privateKeyPkcs8, privateKeyId, null, null, universeDomain); return TestUtils.jsonToInputStream(json); } - - private static void testFromStreamException(InputStream stream, String expectedMessageContent) { - try { - ServiceAccountCredentials.fromStream(stream, DUMMY_TRANSPORT_FACTORY); - fail( - String.format( - "Should throw exception with message containing '%s'", expectedMessageContent)); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedMessageContent)); - } - } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java index 8d696663132b..d961f7b1b685 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java @@ -33,12 +33,14 @@ import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -117,50 +119,44 @@ void constructor_allParameters_constructs() throws IOException { @Test void constructor_noClientId_constructs() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); - ServiceAccountJwtAccessCredentials.newBuilder() - .setClientEmail(SA_CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(SA_PRIVATE_KEY_ID) - .build(); + ServiceAccountJwtAccessCredentials.Builder builder = + ServiceAccountJwtAccessCredentials.newBuilder() + .setClientEmail(SA_CLIENT_EMAIL) + .setPrivateKey(privateKey) + .setPrivateKeyId(SA_PRIVATE_KEY_ID); + assertDoesNotThrow(builder::build); } @Test void constructor_noPrivateKeyId_constructs() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); - ServiceAccountJwtAccessCredentials.newBuilder() - .setClientId(SA_CLIENT_ID) - .setClientEmail(SA_CLIENT_EMAIL) - .setPrivateKey(privateKey) - .build(); + ServiceAccountJwtAccessCredentials.Builder builder = + ServiceAccountJwtAccessCredentials.newBuilder() + .setClientId(SA_CLIENT_ID) + .setClientEmail(SA_CLIENT_EMAIL) + .setPrivateKey(privateKey); + assertDoesNotThrow(builder::build); } @Test void constructor_noEmail_throws() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); - try { - ServiceAccountJwtAccessCredentials.newBuilder() - .setClientId(SA_CLIENT_ID) - .setPrivateKey(privateKey) - .setPrivateKeyId(SA_PRIVATE_KEY_ID) - .build(); - fail("exception expected"); - } catch (NullPointerException e) { - // Expected - } + ServiceAccountJwtAccessCredentials.Builder builder = + ServiceAccountJwtAccessCredentials.newBuilder() + .setClientId(SA_CLIENT_ID) + .setPrivateKey(privateKey) + .setPrivateKeyId(SA_PRIVATE_KEY_ID); + assertThrows(NullPointerException.class, builder::build); } @Test void constructor_noPrivateKey_throws() { - try { - ServiceAccountJwtAccessCredentials.newBuilder() - .setClientId(SA_CLIENT_ID) - .setClientEmail(SA_CLIENT_EMAIL) - .setPrivateKeyId(SA_PRIVATE_KEY_ID) - .build(); - fail("exception expected"); - } catch (NullPointerException e) { - // Expected - } + ServiceAccountJwtAccessCredentials.Builder builder = + ServiceAccountJwtAccessCredentials.newBuilder() + .setClientId(SA_CLIENT_ID) + .setClientEmail(SA_CLIENT_EMAIL) + .setPrivateKeyId(SA_PRIVATE_KEY_ID); + assertThrows(NullPointerException.class, builder::build); } @Test @@ -168,7 +164,7 @@ void getAuthenticationType_returnsJwtAccess() throws IOException { Credentials credentials = ServiceAccountJwtAccessCredentials.fromPkcs8( SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID); - assertEquals(credentials.getAuthenticationType(), "JWTAccess"); + assertEquals("JWTAccess", credentials.getAuthenticationType()); } @Test @@ -200,7 +196,7 @@ void getRequestMetadata_blocking_hasJwtAccess() throws IOException { Map> metadata = credentials.getRequestMetadata(CALL_URI); - verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(metadata, CALL_URI); } @Test @@ -217,7 +213,7 @@ void getRequestMetadata_blocking_defaultURI_hasJwtAccess() throws IOException { Map> metadata = credentials.getRequestMetadata(); - verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(metadata, CALL_URI); } @Test @@ -231,12 +227,7 @@ void getRequestMetadata_blocking_noURI_throws() throws IOException { .setPrivateKeyId(SA_PRIVATE_KEY_ID) .build(); - try { - credentials.getRequestMetadata(); - fail("exception expected"); - } catch (IOException e) { - // Expected - } + assertThrows(IOException.class, credentials::getRequestMetadata); } @Test @@ -305,7 +296,7 @@ void getRequestMetadata_async_hasJwtAccess() throws IOException { credentials.getRequestMetadata(CALL_URI, executor, callback); assertEquals(0, executor.numTasks()); assertNotNull(callback.metadata); - verifyJwtAccess(callback.metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(callback.metadata, CALL_URI); } @Test @@ -325,7 +316,7 @@ void getRequestMetadata_async_defaultURI_hasJwtAccess() throws IOException { credentials.getRequestMetadata(null, executor, callback); assertEquals(0, executor.numTasks()); assertNotNull(callback.metadata); - verifyJwtAccess(callback.metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(callback.metadata, CALL_URI); } @Test @@ -418,8 +409,8 @@ void getRequestMetadata_contains_quotaProjectId() throws IOException { Map> metadata = credentials.getRequestMetadata(CALL_URI); assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); assertEquals( - metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY), - Collections.singletonList(QUOTA_PROJECT)); + Collections.singletonList(QUOTA_PROJECT), + metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); } @Test @@ -473,8 +464,8 @@ void equals_true() throws IOException { .setPrivateKeyId(SA_PRIVATE_KEY_ID) .setDefaultAudience(CALL_URI) .build(); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test @@ -496,8 +487,8 @@ void equals_false_clientId() throws IOException { .setPrivateKeyId(SA_PRIVATE_KEY_ID) .setDefaultAudience(CALL_URI) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -519,8 +510,8 @@ void equals_false_email() throws IOException { .setPrivateKeyId(SA_PRIVATE_KEY_ID) .setDefaultAudience(CALL_URI) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -542,8 +533,8 @@ void equals_false_keyId() throws IOException { .setPrivateKeyId("otherKeyId") .setDefaultAudience(CALL_URI) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -566,8 +557,8 @@ void equals_false_callUri() throws IOException { .setPrivateKeyId(SA_PRIVATE_KEY_ID) .setDefaultAudience(otherCallUri) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test @@ -625,23 +616,19 @@ void serialize() throws IOException, ClassNotFoundException { .build(); ServiceAccountJwtAccessCredentials deserializedCredentials = serializeAndDeserialize(credentials); - verifyJwtAccess( - deserializedCredentials.getRequestMetadata(), SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(deserializedCredentials.getRequestMetadata(), CALL_URI); assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } @Test - void fromStream_nullStream_throws() throws IOException { + void fromStream_nullStream_throws() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); - try { - ServiceAccountCredentials.fromStream(null, transportFactory); - fail("Should throw if InputStream is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows( + NullPointerException.class, + () -> ServiceAccountCredentials.fromStream(null, transportFactory)); } @Test @@ -654,7 +641,7 @@ void fromStream_hasJwtAccess() throws IOException { assertNotNull(credentials); Map> metadata = credentials.getRequestMetadata(CALL_URI); - verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(metadata, CALL_URI); } @Test @@ -668,7 +655,7 @@ void fromStream_defaultURI_hasJwtAccess() throws IOException { assertNotNull(credentials); Map> metadata = credentials.getRequestMetadata(null); - verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID); + verifyJwtAccess(metadata, CALL_URI); } @Test @@ -722,7 +709,7 @@ void jwtWithClaims_overrideAudience() throws IOException { Map> metadata = withAudience.getRequestMetadata(CALL_URI); - verifyJwtAccess(metadata, SA_CLIENT_EMAIL, URI.create("new-audience"), SA_PRIVATE_KEY_ID); + verifyJwtAccess(metadata, URI.create("new-audience")); } @Test @@ -735,12 +722,8 @@ void jwtWithClaims_noAudience() throws IOException { .setPrivateKey(privateKey) .setPrivateKeyId(SA_PRIVATE_KEY_ID) .build(); - try { - credentials.jwtWithClaims(JwtClaims.newBuilder().build()); - fail("Expected to throw exception for missing audience"); - } catch (IllegalStateException ex) { - // expected exception - } + JwtClaims claims = JwtClaims.newBuilder().build(); + assertThrows(IllegalStateException.class, () -> credentials.jwtWithClaims(claims)); } @Test @@ -757,7 +740,7 @@ void jwtWithClaims_defaultAudience() throws IOException { Credentials withAudience = credentials.jwtWithClaims(JwtClaims.newBuilder().build()); Map> metadata = withAudience.getRequestMetadata(CALL_URI); - verifyJwtAccess(metadata, SA_CLIENT_EMAIL, URI.create("default-audience"), SA_PRIVATE_KEY_ID); + verifyJwtAccess(metadata, URI.create("default-audience")); } @Test @@ -832,15 +815,7 @@ public void onFailure(Throwable exception) { @Test void fromJSON_noUniverseDomain() throws IOException { - GenericJson json = - writeServiceAccountJson( - SA_CLIENT_ID, - SA_CLIENT_EMAIL, - SA_PRIVATE_KEY_PKCS8, - "test-project-id", - SA_PRIVATE_KEY_ID, - QUOTA_PROJECT, - null); + GenericJson json = writeServiceAccountJson(null); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.fromJson(json, URI.create("default-aud")); assertEquals(SA_CLIENT_ID, credentials.getClientId()); @@ -853,15 +828,7 @@ void fromJSON_noUniverseDomain() throws IOException { @Test void fromJSON_UniverseDomainSet() throws IOException { - GenericJson json = - writeServiceAccountJson( - SA_CLIENT_ID, - SA_CLIENT_EMAIL, - SA_PRIVATE_KEY_PKCS8, - "test-project-id", - SA_PRIVATE_KEY_ID, - QUOTA_PROJECT, - "example.com"); + GenericJson json = writeServiceAccountJson("example.com"); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.fromJson(json, URI.create("default-aud")); assertEquals(SA_CLIENT_ID, credentials.getClientId()); @@ -933,11 +900,7 @@ void builder_customUniverseDomain() throws IOException { assertEquals("example.com", credentials.getUniverseDomain()); } - private void verifyJwtAccess( - Map> metadata, - String expectedEmail, - URI expectedAudience, - String expectedKeyId) + private void verifyJwtAccess(Map> metadata, URI expectedAudience) throws IOException { assertNotNull(metadata); List authorizations = metadata.get(AuthHttpConstants.AUTHORIZATION); @@ -951,50 +914,32 @@ private void verifyJwtAccess( } assertNotNull(assertion, "Bearer assertion not found"); JsonWebSignature signature = JsonWebSignature.parse(JSON_FACTORY, assertion); - assertEquals(expectedEmail, signature.getPayload().getIssuer()); - assertEquals(expectedEmail, signature.getPayload().getSubject()); + assertEquals( + ServiceAccountJwtAccessCredentialsTest.SA_CLIENT_EMAIL, signature.getPayload().getIssuer()); + assertEquals( + ServiceAccountJwtAccessCredentialsTest.SA_CLIENT_EMAIL, + signature.getPayload().getSubject()); assertEquals(expectedAudience.toString(), signature.getPayload().getAudience()); - assertEquals(expectedKeyId, signature.getHeader().getKeyId()); + assertEquals( + ServiceAccountJwtAccessCredentialsTest.SA_PRIVATE_KEY_ID, signature.getHeader().getKeyId()); } private static void testFromStreamException(InputStream stream, String expectedMessageContent) { - try { - ServiceAccountJwtAccessCredentials.fromStream(stream, CALL_URI); - fail( - String.format( - "Should throw exception with message containing '%s'", expectedMessageContent)); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedMessageContent)); - } + IOException expected = + assertThrows( + IOException.class, + () -> ServiceAccountJwtAccessCredentials.fromStream(stream, CALL_URI)); + assertTrue(expected.getMessage().contains(expectedMessageContent)); } - private GenericJson writeServiceAccountJson( - String clientId, - String clientEmail, - String privateKeyPkcs8, - String privateKeyId, - String projectId, - String quotaProjectId, - String universeDomain) { + private GenericJson writeServiceAccountJson(String universeDomain) { GenericJson json = new GenericJson(); - if (clientId != null) { - json.put("client_id", clientId); - } - if (clientEmail != null) { - json.put("client_email", clientEmail); - } - if (privateKeyPkcs8 != null) { - json.put("private_key", privateKeyPkcs8); - } - if (privateKeyId != null) { - json.put("private_key_id", privateKeyId); - } - if (projectId != null) { - json.put("project_id", projectId); - } - if (quotaProjectId != null) { - json.put("quota_project_id", quotaProjectId); - } + json.put("client_id", ServiceAccountJwtAccessCredentialsTest.SA_CLIENT_ID); + json.put("client_email", ServiceAccountJwtAccessCredentialsTest.SA_CLIENT_EMAIL); + json.put("private_key", ServiceAccountJwtAccessCredentialsTest.SA_PRIVATE_KEY_PKCS8); + json.put("private_key_id", "test-project-id"); + json.put("project_id", ServiceAccountJwtAccessCredentialsTest.SA_PRIVATE_KEY_ID); + json.put("quota_project_id", ServiceAccountJwtAccessCredentialsTest.QUOTA_PROJECT); if (universeDomain != null) { json.put("universe_domain", universeDomain); } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/StsRequestHandlerTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/StsRequestHandlerTest.java index 6607403a4d7a..a0e2090d2cc9 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/StsRequestHandlerTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/StsRequestHandlerTest.java @@ -176,7 +176,7 @@ void exchangeToken_throwsException() throws IOException { TestUtils.buildHttpResponseException( "invalidRequest", /* errorDescription= */ null, /* errorUri= */ null)); - OAuthException e = assertThrows(OAuthException.class, () -> requestHandler.exchangeToken()); + OAuthException e = assertThrows(OAuthException.class, requestHandler::exchangeToken); assertEquals("invalidRequest", e.getErrorCode()); assertNull(e.getErrorDescription()); @@ -196,7 +196,7 @@ void exchangeToken_withOptionalParams_throwsException() throws IOException { transport.addResponseErrorSequence( TestUtils.buildHttpResponseException("invalidRequest", "errorDescription", "errorUri")); - OAuthException e = assertThrows(OAuthException.class, () -> requestHandler.exchangeToken()); + OAuthException e = assertThrows(OAuthException.class, requestHandler::exchangeToken); assertEquals("invalidRequest", e.getErrorCode()); assertEquals("errorDescription", e.getErrorDescription()); @@ -216,8 +216,7 @@ void exchangeToken_ioException() { IOException e = new IOException(); transport.addResponseErrorSequence(e); - IOException thrownException = - assertThrows(IOException.class, () -> requestHandler.exchangeToken()); + IOException thrownException = assertThrows(IOException.class, requestHandler::exchangeToken); assertEquals(e, thrownException); } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java index 9e6de11089fe..b7921b3a426c 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestEnvironmentProvider.java @@ -1,9 +1,39 @@ +/* + * Copyright 2021, Google Inc. All rights reserved. + * + * 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 Inc. 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 java.util.HashMap; import java.util.Map; -final class TestEnvironmentProvider implements EnvironmentProvider { +public final class TestEnvironmentProvider implements EnvironmentProvider { private final Map environmentVariables = new HashMap<>(); diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java new file mode 100644 index 000000000000..e84d6ba5c8e0 --- /dev/null +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TestPropertyProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 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 java.util.HashMap; +import java.util.Map; + +public class TestPropertyProvider implements PropertyProvider { + private final Map properties = new HashMap<>(); + + public TestPropertyProvider() {} + + public void setProperty(String property, String value) { + properties.put(property, value); + } + + @Override + public String getProperty(String property, String def) { + if (properties.containsKey(property)) { + return properties.get(property); + } + return def; + } +} diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java index 6872214f05d7..81847ffd24f8 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java @@ -32,8 +32,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.LowLevelHttpRequest; @@ -84,12 +84,9 @@ public long currentTimeMillis() { void verifyExpiredToken() { for (String token : ALL_TOKENS) { TokenVerifier tokenVerifier = TokenVerifier.newBuilder().build(); - try { - tokenVerifier.verify(token); - fail("Should have thrown a VerificationException"); - } catch (TokenVerifier.VerificationException e) { - assertTrue(e.getMessage().contains("expired")); - } + VerificationException e = + assertThrows(VerificationException.class, () -> tokenVerifier.verify(token)); + assertTrue(e.getMessage().contains("expired")); } } @@ -98,12 +95,9 @@ void verifyExpectedAudience() { TokenVerifier tokenVerifier = TokenVerifier.newBuilder().setAudience("expected audience").build(); for (String token : ALL_TOKENS) { - try { - tokenVerifier.verify(token); - fail("Should have thrown a VerificationException"); - } catch (TokenVerifier.VerificationException e) { - assertTrue(e.getMessage().contains("audience does not match")); - } + VerificationException e = + assertThrows(VerificationException.class, () -> tokenVerifier.verify(token)); + assertTrue(e.getMessage().contains("audience does not match")); } } @@ -111,12 +105,9 @@ void verifyExpectedAudience() { void verifyExpectedIssuer() { TokenVerifier tokenVerifier = TokenVerifier.newBuilder().setIssuer("expected issuer").build(); for (String token : ALL_TOKENS) { - try { - tokenVerifier.verify(token); - fail("Should have thrown a VerificationException"); - } catch (TokenVerifier.VerificationException e) { - assertTrue(e.getMessage().contains("issuer does not match")); - } + VerificationException e = + assertThrows(VerificationException.class, () -> tokenVerifier.verify(token)); + assertTrue(e.getMessage().contains("issuer does not match")); } } @@ -151,13 +142,10 @@ public LowLevelHttpResponse execute() throws IOException { .setHttpTransportFactory(httpTransportFactory) .build(); - try { - tokenVerifier.verify(ES256_TOKEN); - fail("Should not be able to continue without exception."); - } catch (TokenVerifier.VerificationException exception) { - assertTrue( - exception.getMessage().contains("Error fetching PublicKey from certificate location")); - } + VerificationException exception = + assertThrows(VerificationException.class, () -> tokenVerifier.verify(ES256_TOKEN)); + assertTrue( + exception.getMessage().contains("Error fetching PublicKey from certificate location")); } @Test @@ -189,12 +177,9 @@ public LowLevelHttpResponse execute() throws IOException { .setClock(FIXED_CLOCK) .setHttpTransportFactory(httpTransportFactory) .build(); - try { - tokenVerifier.verify(ES256_TOKEN); - fail("Should have failed verification"); - } catch (TokenVerifier.VerificationException e) { - assertTrue(e.getMessage().contains("Error fetching PublicKey")); - } + VerificationException e = + assertThrows(VerificationException.class, () -> tokenVerifier.verify(ES256_TOKEN)); + assertTrue(e.getMessage().contains("Error fetching PublicKey")); } @Test @@ -230,19 +215,12 @@ void verifyPublicKeyStoreIntermittentError() throws VerificationException, IOExc .setHttpTransportFactory(transportFactory) .build(); - try { - tokenVerifier.verify(ES256_TOKEN); - fail("Should not be able to continue without exception."); - } catch (TokenVerifier.VerificationException exception) { - assertTrue(exception.getMessage().contains("Error fetching PublicKey")); - } + VerificationException exception = + assertThrows(VerificationException.class, () -> tokenVerifier.verify(ES256_TOKEN)); + assertTrue(exception.getMessage().contains("Error fetching PublicKey")); - try { - tokenVerifier.verify(ES256_TOKEN); - fail("Should not be able to continue without exception."); - } catch (TokenVerifier.VerificationException exception) { - assertTrue(exception.getCause().getMessage().contains("No valid public key")); - } + exception = assertThrows(VerificationException.class, () -> tokenVerifier.verify(ES256_TOKEN)); + assertTrue(exception.getCause().getMessage().contains("No valid public key")); assertNotNull(tokenVerifier.verify(ES256_TOKEN)); } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java index 050a9d199079..bcde55b0045a 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java @@ -40,7 +40,6 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.auth.TestUtils; import com.google.auth.http.HttpTransportFactory; @@ -66,7 +65,7 @@ class UserAuthorizerTest { private static final List GRANTED_SCOPES = Arrays.asList("scope1", "scope2"); private static final String GRANTED_SCOPES_STRING = String.join(" ", GRANTED_SCOPES); private static final String DUMMY_SCOPE = "dummy_scope"; - private static final List DUMMY_SCOPES = Arrays.asList(DUMMY_SCOPE); + private static final List DUMMY_SCOPES = Collections.singletonList(DUMMY_SCOPE); private static final Long EXPIRATION_TIME = 504000300L; private static final AccessToken ACCESS_TOKEN = AccessToken.newBuilder() @@ -147,20 +146,15 @@ void constructorWithClientAuthenticationTypeNone() { @Test void constructorCommon_nullClientId_throws() { - assertThrows( - NullPointerException.class, - () -> - UserAuthorizer.newBuilder() - .setScopes(DUMMY_SCOPES) - .setCallbackUri(CALLBACK_URI) - .build()); + UserAuthorizer.Builder builder = + UserAuthorizer.newBuilder().setScopes(DUMMY_SCOPES).setCallbackUri(CALLBACK_URI); + assertThrows(NullPointerException.class, builder::build); } @Test void constructorCommon_nullScopes_throws() { - assertThrows( - NullPointerException.class, - () -> UserAuthorizer.newBuilder().setClientId(CLIENT_ID).build()); + UserAuthorizer.Builder builder = UserAuthorizer.newBuilder().setClientId(CLIENT_ID); + assertThrows(NullPointerException.class, builder::build); } @Test @@ -181,30 +175,30 @@ void getCallbackUri_relativeToBase() { @Test void getAuthorizationUrl() throws IOException { - final String CUSTOM_STATE = "custom_state"; - final String PROTOCOL = "https"; - final String HOST = "accounts.test.com"; - final String PATH = "/o/o/oauth2/auth"; - final URI AUTH_URI = URI.create(PROTOCOL + "://" + HOST + PATH); + final String customState = "custom_state"; + final String protocol = "https"; + final String host = "accounts.test.com"; + final String path = "/o/o/oauth2/auth"; + final URI authUri = URI.create(protocol + "://" + host + path); final String EXPECTED_CALLBACK = "http://example.com" + CALLBACK_URI.toString(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(DUMMY_SCOPES) .setCallbackUri(CALLBACK_URI) - .setUserAuthUri(AUTH_URI) + .setUserAuthUri(authUri) .setPKCEProvider(pkce) .build(); - URL authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, CUSTOM_STATE, BASE_URI); + URL authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, customState, BASE_URI); - assertEquals(PROTOCOL, authorizationUrl.getProtocol()); + assertEquals(protocol, authorizationUrl.getProtocol()); assertEquals(-1, authorizationUrl.getPort()); - assertEquals(PATH, authorizationUrl.getPath()); - assertEquals(HOST, authorizationUrl.getHost()); + assertEquals(path, authorizationUrl.getPath()); + assertEquals(host, authorizationUrl.getHost()); String query = authorizationUrl.getQuery(); Map parameters = TestUtils.parseQuery(query); - assertEquals(CUSTOM_STATE, parameters.get("state")); + assertEquals(customState, parameters.get("state")); assertEquals(USER_ID, parameters.get("login_hint")); assertEquals(EXPECTED_CALLBACK, parameters.get("redirect_uri")); assertEquals(CLIENT_ID_VALUE, parameters.get("client_id")); @@ -217,18 +211,17 @@ void getAuthorizationUrl() throws IOException { @Test void getAuthorizationUrl_additionalParameters() throws IOException { - final String CUSTOM_STATE = "custom_state"; - final String PROTOCOL = "https"; - final String HOST = "accounts.test.com"; - final String PATH = "/o/o/oauth2/auth"; - final URI AUTH_URI = URI.create(PROTOCOL + "://" + HOST + PATH); - final String EXPECTED_CALLBACK = "http://example.com" + CALLBACK_URI.toString(); + final String customState = "custom_state"; + final String protocol = "https"; + final String host = "accounts.test.com"; + final String path = "/o/o/oauth2/auth"; + final URI authUri = URI.create(protocol + "://" + host + path); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(DUMMY_SCOPES) .setCallbackUri(CALLBACK_URI) - .setUserAuthUri(AUTH_URI) + .setUserAuthUri(authUri) .build(); Map additionalParameters = new HashMap(); additionalParameters.put("param1", "value1"); @@ -236,7 +229,7 @@ void getAuthorizationUrl_additionalParameters() throws IOException { // Verify that the authorization URL doesn't include the additional parameters if they are not // passed in. - URL authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, CUSTOM_STATE, BASE_URI); + URL authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, customState, BASE_URI); String query = authorizationUrl.getQuery(); Map parameters = TestUtils.parseQuery(query); assertFalse(parameters.containsKey("param1")); @@ -244,7 +237,7 @@ void getAuthorizationUrl_additionalParameters() throws IOException { // Verify that the authorization URL includes the additional parameters if they are passed in. authorizationUrl = - authorizer.getAuthorizationUrl(USER_ID, CUSTOM_STATE, BASE_URI, additionalParameters); + authorizer.getAuthorizationUrl(USER_ID, customState, BASE_URI, additionalParameters); query = authorizationUrl.getQuery(); parameters = TestUtils.parseQuery(query); assertEquals("value1", parameters.get("param1")); @@ -252,7 +245,7 @@ void getAuthorizationUrl_additionalParameters() throws IOException { // Verify that the authorization URL doesn't include the additional parameters passed in the // previous call to the authorizer - authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, CUSTOM_STATE, BASE_URI); + authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, customState, BASE_URI); query = authorizationUrl.getQuery(); parameters = TestUtils.parseQuery(query); assertFalse(parameters.containsKey("param1")); @@ -392,7 +385,7 @@ void testGetTokenResponseFromAuthCodeExchange_missingAuthCode_throws() { } @Test - void testGetTokenResponseFromAuthCodeExchange_missingAccessToken_throws() throws IOException { + void testGetTokenResponseFromAuthCodeExchange_missingAccessToken_throws() { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); // Missing access token. @@ -452,7 +445,7 @@ void getCredentials_storedCredentials_returnsStored() throws IOException { } @Test - void getCredentials_nullUserId_throws() throws IOException { + void getCredentials_nullUserId_throws() { TokenStore tokenStore = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() @@ -643,7 +636,7 @@ void getCredentialsFromCode_additionalParameters() throws IOException { } @Test - void getCredentialsFromCode_nullCode_throws() throws IOException { + void getCredentialsFromCode_nullCode_throws() { UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) @@ -695,7 +688,7 @@ void getAndStoreCredentialsFromCode_getAndStoresCredentials() throws IOException } @Test - void getAndStoreCredentialsFromCode_nullCode_throws() throws IOException { + void getAndStoreCredentialsFromCode_nullCode_throws() { UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) @@ -709,7 +702,7 @@ void getAndStoreCredentialsFromCode_nullCode_throws() throws IOException { } @Test - void getAndStoreCredentialsFromCode_nullUserId_throws() throws IOException { + void getAndStoreCredentialsFromCode_nullUserId_throws() { UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) @@ -756,19 +749,14 @@ void revokeAuthorization_revokesAndClears() throws IOException { authorizer.revokeAuthorization(USER_ID); - try { - credentials1.refresh(); - fail("Credentials should not refresh after revoke."); - } catch (IOException expected) { - // Expected - } + assertThrows(IOException.class, credentials1::refresh); UserCredentials credentials2 = authorizer.getCredentials(USER_ID); assertNull(credentials2); } @Test void nullCodeVerifierPKCEProvider() { - PKCEProvider pkce = + PKCEProvider pkceProvider = new PKCEProvider() { @Override public String getCodeVerifier() { @@ -786,20 +774,17 @@ public String getCodeChallenge() { } }; - assertThrows( - IllegalArgumentException.class, - () -> - UserAuthorizer.newBuilder() - .setClientId(CLIENT_ID) - .setScopes(DUMMY_SCOPES) - .setTokenStore(new MemoryTokensStorage()) - .setPKCEProvider(pkce) - .build()); + UserAuthorizer.Builder builder = + UserAuthorizer.newBuilder() + .setClientId(CLIENT_ID) + .setScopes(DUMMY_SCOPES) + .setTokenStore(new MemoryTokensStorage()); + assertThrows(IllegalArgumentException.class, () -> builder.setPKCEProvider(pkceProvider)); } @Test void nullCodeChallengePKCEProvider() { - PKCEProvider pkce = + PKCEProvider pkceProvider = new PKCEProvider() { @Override public String getCodeVerifier() { @@ -817,20 +802,17 @@ public String getCodeChallenge() { } }; - assertThrows( - IllegalArgumentException.class, - () -> - UserAuthorizer.newBuilder() - .setClientId(CLIENT_ID) - .setScopes(DUMMY_SCOPES) - .setTokenStore(new MemoryTokensStorage()) - .setPKCEProvider(pkce) - .build()); + UserAuthorizer.Builder builder = + UserAuthorizer.newBuilder() + .setClientId(CLIENT_ID) + .setScopes(DUMMY_SCOPES) + .setTokenStore(new MemoryTokensStorage()); + assertThrows(IllegalArgumentException.class, () -> builder.setPKCEProvider(pkceProvider)); } @Test void nullCodeChallengeMethodPKCEProvider() { - PKCEProvider pkce = + PKCEProvider pkceProvider = new PKCEProvider() { @Override public String getCodeVerifier() { @@ -848,15 +830,12 @@ public String getCodeChallenge() { } }; - assertThrows( - IllegalArgumentException.class, - () -> - UserAuthorizer.newBuilder() - .setClientId(CLIENT_ID) - .setScopes(DUMMY_SCOPES) - .setTokenStore(new MemoryTokensStorage()) - .setPKCEProvider(pkce) - .build()); + UserAuthorizer.Builder builder = + UserAuthorizer.newBuilder() + .setClientId(CLIENT_ID) + .setScopes(DUMMY_SCOPES) + .setTokenStore(new MemoryTokensStorage()); + assertThrows(IllegalArgumentException.class, () -> builder.setPKCEProvider(pkceProvider)); } @Test diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java index e5a6c658a320..aaabf4aeefec 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java @@ -31,6 +31,7 @@ package com.google.auth.oauth2; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -85,13 +86,9 @@ class UserCredentialsTest extends BaseSerializationTest { @Test void constructor_accessAndRefreshTokenNull_throws() { - assertThrows( - IllegalStateException.class, - () -> - UserCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientSecret(CLIENT_SECRET) - .build()); + UserCredentials.Builder builder = + UserCredentials.newBuilder().setClientId(CLIENT_ID).setClientSecret(CLIENT_SECRET); + assertThrows(IllegalStateException.class, builder::build); } @Test @@ -180,8 +177,8 @@ void fromJson_hasQuotaProjectId() throws IOException { Map> metadata = credentials.getRequestMetadata(CALL_URI); assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); assertEquals( - metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY), - Collections.singletonList(QUOTA_PROJECT)); + Collections.singletonList(QUOTA_PROJECT), + metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); } @Test @@ -203,7 +200,7 @@ void getRequestMetadata_initialToken_hasAccessToken() throws IOException { } @Test - void getRequestMetadata_initialTokenRefreshed_throws() throws IOException { + void getRequestMetadata_initialTokenRefreshed_throws() { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); @@ -215,12 +212,7 @@ void getRequestMetadata_initialTokenRefreshed_throws() throws IOException { .setHttpTransportFactory(transportFactory) .build(); - try { - userCredentials.refresh(); - fail("Should not be able to refresh without refresh token."); - } catch (IllegalStateException expected) { - // Expected - } + assertThrows(IllegalStateException.class, userCredentials::refresh); } @Test @@ -243,18 +235,18 @@ void getRequestMetadata_fromRefreshToken_hasAccessToken() throws IOException { @Test void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException { - final URI TOKEN_SERVER = URI.create("https://foo.com/bar"); + final URI tokenServerUri = URI.create("https://foo.com/bar"); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); - transportFactory.transport.setTokenServerUri(TOKEN_SERVER); + transportFactory.transport.setTokenServerUri(tokenServerUri); UserCredentials userCredentials = UserCredentials.newBuilder() .setClientId(CLIENT_ID) .setClientSecret(CLIENT_SECRET) .setRefreshToken(REFRESH_TOKEN) .setHttpTransportFactory(transportFactory) - .setTokenServerUri(TOKEN_SERVER) + .setTokenServerUri(tokenServerUri) .build(); Map> metadata = userCredentials.getRequestMetadata(CALL_URI); @@ -263,7 +255,7 @@ void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException { } @Test - void equals_true() throws IOException { + void equals_true() { final URI tokenServer = URI.create("https://foo.com/bar"); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); @@ -287,12 +279,12 @@ void equals_true() throws IOException { .setTokenServerUri(tokenServer) .setQuotaProjectId(QUOTA_PROJECT) .build(); - assertTrue(credentials.equals(otherCredentials)); - assertTrue(otherCredentials.equals(credentials)); + assertEquals(credentials, otherCredentials); + assertEquals(otherCredentials, credentials); } @Test - void equals_false_clientId() throws IOException { + void equals_false_clientId() { final URI tokenServer1 = URI.create("https://foo1.com/bar"); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); @@ -314,12 +306,12 @@ void equals_false_clientId() throws IOException { .setHttpTransportFactory(httpTransportFactory) .setTokenServerUri(tokenServer1) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void equals_false_clientSecret() throws IOException { + void equals_false_clientSecret() { final URI tokenServer1 = URI.create("https://foo1.com/bar"); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); @@ -341,12 +333,12 @@ void equals_false_clientSecret() throws IOException { .setHttpTransportFactory(httpTransportFactory) .setTokenServerUri(tokenServer1) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void equals_false_refreshToken() throws IOException { + void equals_false_refreshToken() { final URI tokenServer1 = URI.create("https://foo1.com/bar"); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); @@ -368,12 +360,12 @@ void equals_false_refreshToken() throws IOException { .setHttpTransportFactory(httpTransportFactory) .setTokenServerUri(tokenServer1) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void equals_false_accessToken() throws IOException { + void equals_false_accessToken() { final URI tokenServer1 = URI.create("https://foo1.com/bar"); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); AccessToken otherAccessToken = new AccessToken("otherAccessToken", null); @@ -396,13 +388,13 @@ void equals_false_accessToken() throws IOException { .setHttpTransportFactory(httpTransportFactory) .setTokenServerUri(tokenServer1) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); assertNotEquals(credentials.hashCode(), otherAccessToken.hashCode()); } @Test - void equals_false_transportFactory() throws IOException { + void equals_false_transportFactory() { final URI tokenServer1 = URI.create("https://foo1.com/bar"); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); @@ -425,12 +417,12 @@ void equals_false_transportFactory() throws IOException { .setHttpTransportFactory(serverTransportFactory) .setTokenServerUri(tokenServer1) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void equals_false_tokenServer() throws IOException { + void equals_false_tokenServer() { final URI tokenServer1 = URI.create("https://foo1.com/bar"); final URI tokenServer2 = URI.create("https://foo2.com/bar"); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); @@ -453,12 +445,12 @@ void equals_false_tokenServer() throws IOException { .setHttpTransportFactory(httpTransportFactory) .setTokenServerUri(tokenServer2) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void equals_false_quotaProjectId() throws IOException { + void equals_false_quotaProjectId() { final String quotaProject1 = "sample-id-1"; final String quotaProject2 = "sample-id-2"; AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); @@ -481,12 +473,12 @@ void equals_false_quotaProjectId() throws IOException { .setHttpTransportFactory(httpTransportFactory) .setQuotaProjectId(quotaProject2) .build(); - assertFalse(credentials.equals(otherCredentials)); - assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials, otherCredentials); + assertNotEquals(otherCredentials, credentials); } @Test - void toString_containsFields() throws IOException { + void toString_containsFields() { AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); final URI tokenServer = URI.create("https://foo.com/bar"); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); @@ -518,7 +510,7 @@ void toString_containsFields() throws IOException { } @Test - void hashCode_equals() throws IOException { + void hashCode_equals() { final URI tokenServer = URI.create("https://foo.com/bar"); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); @@ -563,29 +555,20 @@ void serialize() throws IOException, ClassNotFoundException { assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); assertEquals(credentials.toString(), deserializedCredentials.toString()); - assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertSame(Clock.SYSTEM, deserializedCredentials.clock); } @Test - void fromStream_nullTransport_throws() throws IOException { + void fromStream_nullTransport_throws() { InputStream stream = new ByteArrayInputStream("foo".getBytes()); - try { - UserCredentials.fromStream(stream, null); - fail("Should throw if HttpTransportFactory is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows(NullPointerException.class, () -> UserCredentials.fromStream(stream, null)); } @Test - void fromStream_nullStream_throws() throws IOException { + void fromStream_nullStream_throws() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); - try { - UserCredentials.fromStream(null, transportFactory); - fail("Should throw if InputStream is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows( + NullPointerException.class, () -> UserCredentials.fromStream(null, transportFactory)); } @Test @@ -607,25 +590,31 @@ void fromStream_user_providesToken() throws IOException { void fromStream_userNoClientId_throws() throws IOException { InputStream userStream = writeUserStream(null, CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT); - testFromStreamException(userStream, "client_id"); + IOException exception = + assertThrows(IOException.class, () -> UserCredentials.fromStream(userStream)); + assertTrue(exception.getMessage().contains("client_id")); } @Test void fromStream_userNoClientSecret_throws() throws IOException { InputStream userStream = writeUserStream(CLIENT_ID, null, REFRESH_TOKEN, QUOTA_PROJECT); - testFromStreamException(userStream, "client_secret"); + IOException exception = + assertThrows(IOException.class, () -> UserCredentials.fromStream(userStream)); + assertTrue(exception.getMessage().contains("client_secret")); } @Test void fromStream_userNoRefreshToken_throws() throws IOException { InputStream userStream = writeUserStream(CLIENT_ID, CLIENT_SECRET, null, QUOTA_PROJECT); - testFromStreamException(userStream, "refresh_token"); + IOException exception = + assertThrows(IOException.class, () -> UserCredentials.fromStream(userStream)); + assertTrue(exception.getMessage().contains("refresh_token")); } @Test - void saveUserCredentials_saved_throws() throws IOException { + void saveUserCredentials_saved_doesNotThrow() throws IOException { UserCredentials userCredentials = UserCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -636,11 +625,11 @@ void saveUserCredentials_saved_throws() throws IOException { file.deleteOnExit(); String filePath = file.getAbsolutePath(); - userCredentials.save(filePath); + assertDoesNotThrow(() -> userCredentials.save(filePath)); } @Test - void saveAndRestoreUserCredential_saveAndRestored_throws() throws IOException { + void saveAndRestoreUserCredential_saveAndRestored_doesNotThrow() throws IOException { UserCredentials userCredentials = UserCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -653,15 +642,14 @@ void saveAndRestoreUserCredential_saveAndRestored_throws() throws IOException { String filePath = file.getAbsolutePath(); - userCredentials.save(filePath); - - FileInputStream inputStream = new FileInputStream(new File(filePath)); - - UserCredentials restoredCredentials = UserCredentials.fromStream(inputStream); + assertDoesNotThrow(() -> userCredentials.save(filePath)); - assertEquals(userCredentials.getClientId(), restoredCredentials.getClientId()); - assertEquals(userCredentials.getClientSecret(), restoredCredentials.getClientSecret()); - assertEquals(userCredentials.getRefreshToken(), restoredCredentials.getRefreshToken()); + try (FileInputStream inputStream = new FileInputStream(filePath)) { + UserCredentials restoredCredentials = UserCredentials.fromStream(inputStream); + assertEquals(userCredentials.getClientId(), restoredCredentials.getClientId()); + assertEquals(userCredentials.getClientSecret(), restoredCredentials.getClientSecret()); + assertEquals(userCredentials.getRefreshToken(), restoredCredentials.getRefreshToken()); + } } @Test @@ -791,15 +779,11 @@ void IdTokenCredentials_NoRetry_RetryableStatus_throws() throws IOException { UserCredentials credentials = UserCredentials.fromStream(userStream, transportFactory); - try { - transportFactory.transport.addResponseSequence(response408, response429); - credentials.refresh(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - assertTrue(ex.getMessage().contains("com.google.api.client.http.HttpResponseException: 408")); - assertTrue(ex.isRetryable()); - assertEquals(0, ex.getRetryCount()); - } + transportFactory.transport.addResponseSequence(response408, response429); + GoogleAuthException ex = assertThrows(GoogleAuthException.class, credentials::refresh); + assertTrue(ex.getMessage().contains("com.google.api.client.http.HttpResponseException: 408")); + assertTrue(ex.isRetryable()); + assertEquals(0, ex.getRetryCount()); IdTokenCredentials tokenCredential = IdTokenCredentials.newBuilder().setIdTokenProvider(credentials).build(); @@ -808,14 +792,11 @@ void IdTokenCredentials_NoRetry_RetryableStatus_throws() throws IOException { assertNull(tokenCredential.getIdToken()); // trigger the refresh like it would happen during a request build - try { - tokenCredential.getRequestMetadata(CALL_URI); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - assertTrue(ex.getMessage().contains("com.google.api.client.http.HttpResponseException: 429")); - assertTrue(ex.isRetryable()); - assertEquals(0, ex.getRetryCount()); - } + GoogleAuthException ex2 = + assertThrows(GoogleAuthException.class, () -> tokenCredential.getRequestMetadata(CALL_URI)); + assertTrue(ex2.getMessage().contains("com.google.api.client.http.HttpResponseException: 429")); + assertTrue(ex2.isRetryable()); + assertEquals(0, ex2.getRetryCount()); } @Test @@ -850,14 +831,10 @@ void refreshAccessToken_4xx_5xx_NonRetryableFails() throws IOException { } MockLowLevelHttpResponse mockResponse = new MockLowLevelHttpResponse().setStatusCode(status); - try { - transportFactory.transport.addResponseSequence(mockResponse); - credentials.refresh(); - fail("Should not be able to use credential without exception."); - } catch (GoogleAuthException ex) { - assertFalse(ex.isRetryable()); - assertEquals(0, ex.getRetryCount()); - } + transportFactory.transport.addResponseSequence(mockResponse); + GoogleAuthException ex = assertThrows(GoogleAuthException.class, credentials::refresh); + assertFalse(ex.isRetryable()); + assertEquals(0, ex.getRetryCount()); } } @@ -880,12 +857,8 @@ void IdTokenCredentials_NoUserEmailScope_throws() throws IOException { + " login'. The latter form would not work for Cloud Run, but would still generate an" + " id token."; - try { - tokenCredential.refresh(); - fail("Should not be able to use credential without exception."); - } catch (IOException expected) { - assertTrue(expected.getMessage().equals(expectedMessageContent)); - } + IOException exception = assertThrows(IOException.class, tokenCredential::refresh); + assertEquals(exception.getMessage(), expectedMessageContent); } @Test @@ -940,15 +913,4 @@ static InputStream writeUserStream( GenericJson json = writeUserJson(clientId, clientSecret, refreshToken, quotaProjectId, null); return TestUtils.jsonToInputStream(json); } - - private static void testFromStreamException(InputStream stream, String expectedMessageContent) { - try { - UserCredentials.fromStream(stream); - fail( - String.format( - "Should throw exception with message containing '%s'", expectedMessageContent)); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedMessageContent)); - } - } } diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTQuotaProjectId.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTQuotaProjectId.java index 034507e07bde..842781219edb 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTQuotaProjectId.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTQuotaProjectId.java @@ -39,7 +39,7 @@ import java.io.IOException; import org.junit.jupiter.api.Test; -public final class FTQuotaProjectId { +final class FTQuotaProjectId { @Test void validate_quota_from_environment_used() throws IOException { diff --git a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTServiceAccountCredentialsTest.java b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTServiceAccountCredentialsTest.java index ca0ec4fc134f..82ba939611f5 100644 --- a/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTServiceAccountCredentialsTest.java +++ b/google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/functional/FTServiceAccountCredentialsTest.java @@ -34,8 +34,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; @@ -50,7 +50,6 @@ import com.google.auth.oauth2.IdTokenCredentials; import com.google.auth.oauth2.IdTokenProvider; import com.google.auth.oauth2.OAuth2Utils; -import java.io.FileNotFoundException; import java.io.IOException; import org.junit.jupiter.api.Test; @@ -135,36 +134,35 @@ void ScopeSetNoAudienceOnePlatformTest() throws Exception { } @Test - void WrongScopeComputeTest() throws Exception { + void WrongScopeComputeTest() { executeRequestWrongScope(computeUrl); } @Test - void WrongScopeStorageTest() throws Exception { + void WrongScopeStorageTest() { executeRequestWrongScope(storageUrl); } @Test - void WrongScopeBigQueryTest() throws Exception { + void WrongScopeBigQueryTest() { executeRequestWrongScope(bigQueryUrl); } @Test - void WrongScopeOnePlatformTest() throws Exception { + void WrongScopeOnePlatformTest() { executeRequestWrongScope(cloudTasksUrl); } - private void executeRequestWrongScope(String serviceUri) - throws FileNotFoundException, IOException { + private void executeRequestWrongScope(String serviceUri) { String expectedMessage = "403 Forbidden"; - try { - executeRequestWithCredentialsWithScope( - serviceUri, "https://www.googleapis.com/auth/adexchange.buyer"); - fail("Should throw exception: " + expectedMessage); - } catch (IOException expected) { - assertTrue(expected.getMessage().contains(expectedMessage)); - } + IOException expected = + assertThrows( + IOException.class, + () -> + executeRequestWithCredentialsWithScope( + serviceUri, "https://www.googleapis.com/auth/adexchange.buyer")); + assertTrue(expected.getMessage().contains(expectedMessage)); } private HttpResponse executeRequestWithCredentialsWithoutScope(String serviceUrl) diff --git a/google-auth-library-java/oauth2_http/testresources/mtls/certificate_config.json b/google-auth-library-java/oauth2_http/testresources/mtls/certificate_config.json new file mode 100644 index 000000000000..8a7592faf295 --- /dev/null +++ b/google-auth-library-java/oauth2_http/testresources/mtls/certificate_config.json @@ -0,0 +1,8 @@ +{ + "cert_configs": { + "workload": { + "cert_path": "testresources/mtls/test_cert.pem", + "key_path": "testresources/mtls/test_key.pem" + } + } +} diff --git a/google-auth-library-java/oauth2_http/testresources/mtls/test_cert.pem b/google-auth-library-java/oauth2_http/testresources/mtls/test_cert.pem new file mode 100644 index 000000000000..17fcf0227c80 --- /dev/null +++ b/google-auth-library-java/oauth2_http/testresources/mtls/test_cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE +AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x +MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3 +MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ +GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ +Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB +AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM +MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4 +fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4 +uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1 +kWwa9n19NFiV0z3m6isj +-----END CERTIFICATE----- diff --git a/google-auth-library-java/oauth2_http/testresources/mtls/test_key.pem b/google-auth-library-java/oauth2_http/testresources/mtls/test_key.pem new file mode 100644 index 000000000000..c6a91c3ce623 --- /dev/null +++ b/google-auth-library-java/oauth2_http/testresources/mtls/test_key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/ +XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ +oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu +uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy +sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK +/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY +lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1 +fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq +RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB +Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno +fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L +XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw +ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE +VUV6b25kqrcu +-----END PRIVATE KEY-----