From d3e828ae068d0ba7d91866cf45bd97c9df26f280 Mon Sep 17 00:00:00 2001 From: Anna-Karin Salander Date: Wed, 3 Apr 2024 01:26:58 +0300 Subject: [PATCH] Recording identity provider names in user agent string (#5047) * Adding source to identity (provider name) (#5008) * Adding identity provider source to user agent string (#5029) * Adds provider name to all SDK identity providers (#5040) --- .../feature-AWSSDKforJavav2-5ae2bad.json | 6 + .../amazon/awssdk/spotbugs-suppressions.xml | 5 + .../AnonymousCredentialsProvider.java | 9 +- .../auth/credentials/AwsBasicCredentials.java | 117 ++++++++++++-- .../credentials/AwsSessionCredentials.java | 44 ++++- .../ContainerCredentialsProvider.java | 5 +- ...nvironmentVariableCredentialsProvider.java | 9 +- .../InstanceProfileCredentialsProvider.java | 6 +- .../ProcessCredentialsProvider.java | 20 ++- .../StaticCredentialsProvider.java | 16 +- .../SystemPropertyCredentialsProvider.java | 9 +- .../internal/HttpCredentialsLoader.java | 33 ++-- .../SystemSettingsCredentialsProvider.java | 16 +- .../AnonymousCredentialsProviderTest.java | 31 ++++ .../ContainerCredentialsProviderTest.java | 20 +-- .../HttpCredentialsLoaderTest.java | 9 +- ...nstanceProfileCredentialsProviderTest.java | 7 +- .../ProcessCredentialsProviderTest.java | 123 +++++--------- .../StaticCredentialsProviderTest.java | 34 ++-- ...SystemSettingsCredentialsProviderTest.java | 60 +++++++ .../internal/AwsBasicCredentialsTest.java | 80 ++++++++++ .../internal/AwsSessionCredentialsTest.java | 31 +++- .../internal/ProcessCredentialsTestUtils.java | 75 +++++++++ .../internal/ProfileCredentialsUtilsTest.java | 7 +- .../identity/spi/AwsCredentialsIdentity.java | 8 + .../spi/AwsSessionCredentialsIdentity.java | 3 + .../amazon/awssdk/identity/spi/Identity.java | 9 ++ .../DefaultAwsCredentialsIdentity.java | 16 ++ .../DefaultAwsSessionCredentialsIdentity.java | 16 ++ .../spi/AwsCredentialsIdentityTest.java | 1 + .../AwsSessionCredentialsIdentityTest.java | 1 + .../pipeline/stages/ApplyUserAgentStage.java | 67 ++++++-- .../IdentityProviderNameMapping.java | 95 +++++++++++ .../stages/ApplyUserAgentStageTest.java | 150 ++++++++++++++++++ .../IdentityProviderNameMappingTest.java | 61 +++++++ .../sso/auth/SsoCredentialsProvider.java | 10 +- .../sso/auth/SsoCredentialsProviderTest.java | 1 + .../ssooidc/internal/OnDiskTokenManager.java | 2 +- .../ssooidc/internal/SsoOidcToken.java | 17 ++ .../internal/SsoOidcTokenTransformer.java | 1 + .../internal/OnDiskTokenManagerTest.java | 4 + .../internal/SsoOidcTokenProviderTest.java | 43 +++-- .../ssooidc/internal/SsoOidcTokenTest.java | 1 + .../StsAssumeRoleCredentialsProvider.java | 12 +- ...AssumeRoleWithSamlCredentialsProvider.java | 14 +- ...oleWithWebIdentityCredentialsProvider.java | 14 +- .../sts/auth/StsCredentialsProvider.java | 8 + ...GetFederationTokenCredentialsProvider.java | 15 +- ...StsGetSessionTokenCredentialsProvider.java | 15 +- ...bIdentityTokenFileCredentialsProvider.java | 14 +- .../services/sts/internal/StsAuthUtils.java | 3 +- .../StsAssumeRoleCredentialsProviderTest.java | 5 + ...meRoleWithSamlCredentialsProviderTest.java | 5 + ...ithWebIdentityCredentialsProviderTest.java | 5 + .../auth/StsCredentialsProviderTestBase.java | 5 +- ...ederationTokenCredentialsProviderTest.java | 5 + ...etSessionTokenCredentialsProviderTest.java | 5 + ...ntityTokenCredentialsProviderBaseTest.java | 5 + test/auth-tests/pom.xml | 23 +++ .../auth/source/UserAgentProviderTest.java | 102 ++++++++++++ 60 files changed, 1267 insertions(+), 266 deletions(-) create mode 100644 .changes/next-release/feature-AWSSDKforJavav2-5ae2bad.json create mode 100644 core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProviderTest.java create mode 100644 core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java create mode 100644 core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsBasicCredentialsTest.java create mode 100644 core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProcessCredentialsTestUtils.java create mode 100644 core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMapping.java create mode 100644 core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java create mode 100644 core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMappingTest.java create mode 100644 test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java diff --git a/.changes/next-release/feature-AWSSDKforJavav2-5ae2bad.json b/.changes/next-release/feature-AWSSDKforJavav2-5ae2bad.json new file mode 100644 index 000000000000..eb26897c7cb4 --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-5ae2bad.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Records identity provider names in a resolved identity and adds this information to the user agent string" +} diff --git a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml index 85fb5754cd6f..9ece65ccac3a 100644 --- a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml +++ b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml @@ -293,6 +293,11 @@ + + + + + diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProvider.java index b4d7d22f4580..b547f5d84b34 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProvider.java @@ -25,6 +25,8 @@ @SdkPublicApi public final class AnonymousCredentialsProvider implements AwsCredentialsProvider { + private static final String PROVIDER_NAME = "AnonymousCredentialsProvider"; + private AnonymousCredentialsProvider() { } @@ -34,11 +36,14 @@ public static AnonymousCredentialsProvider create() { @Override public AwsCredentials resolveCredentials() { - return AwsBasicCredentials.ANONYMOUS_CREDENTIALS; + return AwsBasicCredentials.builder() + .validateCredentials(false) + .providerName(PROVIDER_NAME) + .build(); } @Override public String toString() { - return ToString.create("AnonymousCredentialsProvider"); + return ToString.create(PROVIDER_NAME); } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java index 1c8b32cd928e..52c2ec149ad6 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsBasicCredentials.java @@ -18,11 +18,15 @@ import static software.amazon.awssdk.utils.StringUtils.trimToNull; import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** * Provides access to the AWS credentials used for accessing services: AWS access key ID and secret access key. These @@ -36,17 +40,31 @@ */ @Immutable @SdkPublicApi -public final class AwsBasicCredentials implements AwsCredentials { +public final class AwsBasicCredentials implements AwsCredentials, + ToCopyableBuilder { /** * A set of AWS credentials without an access key or secret access key, indicating that anonymous access should be used. - * - * This should be accessed via {@link AnonymousCredentialsProvider#resolveCredentials()}. */ + // TODO(sra-identity-and-auth): Check if this static member can be removed after cleanup @SdkInternalApi - static final AwsBasicCredentials ANONYMOUS_CREDENTIALS = new AwsBasicCredentials(null, null, false); + static final AwsBasicCredentials ANONYMOUS_CREDENTIALS = builder().validateCredentials(false).build(); private final String accessKeyId; private final String secretAccessKey; + private final boolean validateCredentials; + private final String providerName; + + private AwsBasicCredentials(Builder builder) { + this.accessKeyId = trimToNull(builder.accessKeyId); + this.secretAccessKey = trimToNull(builder.secretAccessKey); + this.validateCredentials = builder.validateCredentials; + this.providerName = builder.providerName; + + if (builder.validateCredentials) { + Validate.notNull(this.accessKeyId, "Access key ID cannot be blank."); + Validate.notNull(this.secretAccessKey, "Secret access key cannot be blank."); + } + } /** * Constructs a new credentials object, with the specified AWS access key and AWS secret key. @@ -55,17 +73,14 @@ public final class AwsBasicCredentials implements AwsCredentials { * @param secretAccessKey The AWS secret access key, used to authenticate the user interacting with AWS. */ protected AwsBasicCredentials(String accessKeyId, String secretAccessKey) { - this(accessKeyId, secretAccessKey, true); - } - - private AwsBasicCredentials(String accessKeyId, String secretAccessKey, boolean validateCredentials) { this.accessKeyId = trimToNull(accessKeyId); this.secretAccessKey = trimToNull(secretAccessKey); + this.validateCredentials = false; + this.providerName = null; + } - if (validateCredentials) { - Validate.notNull(this.accessKeyId, "Access key ID cannot be blank."); - Validate.notNull(this.secretAccessKey, "Secret access key cannot be blank."); - } + public static Builder builder() { + return new Builder(); } /** @@ -75,7 +90,9 @@ private AwsBasicCredentials(String accessKeyId, String secretAccessKey, boolean * @param secretAccessKey The AWS secret access key, used to authenticate the user interacting with AWS. * */ public static AwsBasicCredentials create(String accessKeyId, String secretAccessKey) { - return new AwsBasicCredentials(accessKeyId, secretAccessKey); + return builder().accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .build(); } /** @@ -94,10 +111,19 @@ public String secretAccessKey() { return secretAccessKey; } + /** + * The name of the identity provider that created this credential identity. + */ + @Override + public Optional providerName() { + return Optional.ofNullable(providerName); + } + @Override public String toString() { return ToString.builder("AwsCredentials") .add("accessKeyId", accessKeyId) + .add("providerName", providerName) .build(); } @@ -121,4 +147,69 @@ public int hashCode() { hashCode = 31 * hashCode + Objects.hashCode(secretAccessKey()); return hashCode; } + + @Override + public Builder toBuilder() { + return builder().accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .validateCredentials(validateCredentials) + .providerName(providerName); + } + + @Override + public AwsBasicCredentials copy(Consumer modifier) { + return ToCopyableBuilder.super.copy(modifier); + } + + /** + * A builder for creating an instance of {@link AwsBasicCredentials}. This can be created with the static + * {@link #builder()} method. + */ + public static final class Builder implements CopyableBuilder { + private String accessKeyId; + private String secretAccessKey; + private String providerName; + private boolean validateCredentials = true; + + private Builder() { + } + + /** + * The AWS access key, used to identify the user interacting with services. + */ + public Builder accessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + return this; + } + + /** + * The AWS secret access key, used to authenticate the user interacting with services. + */ + public Builder secretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + return this; + } + + /** + * The name of the identity provider that created this credential identity. + */ + public Builder providerName(String providerName) { + this.providerName = providerName; + return this; + } + + /** + * Whether this class should verify that accessKeyId and secretAccessKey are not null. + * Used internally by the SDK for legacy reasons. + */ + @SdkInternalApi + public Builder validateCredentials(Boolean validateCredentials) { + this.validateCredentials = validateCredentials; + return this; + } + + public AwsBasicCredentials build() { + return new AwsBasicCredentials(this); + } + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java index a63c3e9e7b00..9eb8bd62e3fa 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/AwsSessionCredentials.java @@ -18,11 +18,14 @@ import java.time.Instant; import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** * A special type of {@link AwsCredentials} that provides a session token to be used in service authentication. Session @@ -31,19 +34,21 @@ */ @Immutable @SdkPublicApi -public final class AwsSessionCredentials implements AwsCredentials, AwsSessionCredentialsIdentity { - +public final class AwsSessionCredentials implements AwsCredentials, AwsSessionCredentialsIdentity, + ToCopyableBuilder { private final String accessKeyId; private final String secretAccessKey; private final String sessionToken; private final Instant expirationTime; + private final String providerName; private AwsSessionCredentials(Builder builder) { this.accessKeyId = Validate.paramNotNull(builder.accessKeyId, "accessKey"); this.secretAccessKey = Validate.paramNotNull(builder.secretAccessKey, "secretKey"); this.sessionToken = Validate.paramNotNull(builder.sessionToken, "sessionToken"); this.expirationTime = builder.expirationTime; + this.providerName = builder.providerName; } /** @@ -93,14 +98,24 @@ public Optional expirationTime() { * Retrieve the AWS session token. This token is retrieved from an AWS token service, and is used for authenticating that this * user has received temporary permission to access some resource. */ + @Override public String sessionToken() { return sessionToken; } + /** + * The name of the identity provider that created this credential identity. + */ + @Override + public Optional providerName() { + return Optional.ofNullable(providerName); + } + @Override public String toString() { return ToString.builder("AwsSessionCredentials") .add("accessKeyId", accessKeyId()) + .add("providerName", providerName) .build(); } @@ -130,15 +145,30 @@ public int hashCode() { return hashCode; } + @Override + public Builder toBuilder() { + return builder().accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .sessionToken(sessionToken) + .expirationTime(expirationTime) + .providerName(providerName); + } + + @Override + public AwsSessionCredentials copy(Consumer modifier) { + return ToCopyableBuilder.super.copy(modifier); + } + /** * A builder for creating an instance of {@link AwsSessionCredentials}. This can be created with the static * {@link #builder()} method. */ - public static final class Builder { + public static final class Builder implements CopyableBuilder { private String accessKeyId; private String secretAccessKey; private String sessionToken; private Instant expirationTime; + private String providerName; /** * The AWS access key, used to identify the user interacting with services. Required. @@ -175,6 +205,14 @@ public Builder expirationTime(Instant expirationTime) { return this; } + /** + * The name of the identity provider that created this credential identity. + */ + public Builder providerName(String providerName) { + this.providerName = providerName; + return this; + } + public AwsSessionCredentials build() { return new AwsSessionCredentials(this); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java index a728024db243..41df4d250738 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java @@ -72,6 +72,7 @@ public final class ContainerCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { + private static final String PROVIDER_NAME = "ContainerCredentialsProvider"; private static final Predicate IS_LOOPBACK_ADDRESS = InetAddress::isLoopbackAddress; private static final Predicate ALLOWED_HOSTS_RULES = IS_LOOPBACK_ADDRESS; private static final String HTTPS = "https"; @@ -97,7 +98,7 @@ private ContainerCredentialsProvider(BuilderImpl builder) { this.endpoint = builder.endpoint; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; - this.httpCredentialsLoader = HttpCredentialsLoader.create(); + this.httpCredentialsLoader = HttpCredentialsLoader.create(PROVIDER_NAME); if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) { Validate.paramNotBlank(builder.asyncThreadName, "asyncThreadName"); @@ -121,7 +122,7 @@ public static Builder builder() { @Override public String toString() { - return ToString.create("ContainerCredentialsProvider"); + return ToString.create(PROVIDER_NAME); } private RefreshResult refreshCredentials() { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java index 965d68b29ec6..e05c24eed05a 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java @@ -28,6 +28,8 @@ @SdkPublicApi public final class EnvironmentVariableCredentialsProvider extends SystemSettingsCredentialsProvider { + private static final String PROVIDER_NAME = "EnvironmentVariableCredentialsProvider"; + private EnvironmentVariableCredentialsProvider() { } @@ -43,8 +45,13 @@ protected Optional loadSetting(SystemSetting setting) { // CHECKSTYLE:ON } + @Override + protected String provider() { + return PROVIDER_NAME; + } + @Override public String toString() { - return ToString.create("EnvironmentVariableCredentialsProvider"); + return ToString.create(PROVIDER_NAME); } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java index db84a1b13ecf..72c825c9fdcf 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java @@ -68,8 +68,8 @@ public final class InstanceProfileCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { private static final Logger log = Logger.loggerFor(InstanceProfileCredentialsProvider.class); + private static final String PROVIDER_NAME = "InstanceProfileCredentialsProvider"; private static final String EC2_METADATA_TOKEN_HEADER = "x-aws-ec2-metadata-token"; - private static final String SECURITY_CREDENTIALS_RESOURCE = "/latest/meta-data/iam/security-credentials/"; private static final String TOKEN_RESOURCE = "/latest/api/token"; private static final String EC2_METADATA_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds"; @@ -103,7 +103,7 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) { this.profileName = Optional.ofNullable(builder.profileName) .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); - this.httpCredentialsLoader = HttpCredentialsLoader.create(); + this.httpCredentialsLoader = HttpCredentialsLoader.create(PROVIDER_NAME); this.configProvider = Ec2MetadataConfigProvider.builder() .profileFile(profileFile) @@ -203,7 +203,7 @@ public void close() { @Override public String toString() { - return ToString.create("InstanceProfileCredentialsProvider"); + return ToString.create(PROVIDER_NAME); } private ResourcesEndpointProvider createEndpointProvider() { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java index 3e7be883aa82..6bac35476e1a 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java @@ -64,6 +64,7 @@ public final class ProcessCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable, ToCopyableBuilder { + private static final String PROVIDER_NAME = "ProcessCredentialsProvider"; private static final JsonNodeParser PARSER = JsonNodeParser.builder() .removeErrorLocations(true) .build(); @@ -170,11 +171,18 @@ private AwsCredentials credentials(JsonNode credentialsJson) { Validate.notEmpty(accessKeyId, "AccessKeyId cannot be empty."); Validate.notEmpty(secretAccessKey, "SecretAccessKey cannot be empty."); - if (sessionToken != null) { - return AwsSessionCredentials.create(accessKeyId, secretAccessKey, sessionToken); - } else { - return AwsBasicCredentials.create(accessKeyId, secretAccessKey); - } + return sessionToken != null ? + AwsSessionCredentials.builder() + .accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .sessionToken(sessionToken) + .providerName(PROVIDER_NAME) + .build() : + AwsBasicCredentials.builder() + .accessKeyId(accessKeyId) + .secretAccessKey(secretAccessKey) + .providerName(PROVIDER_NAME) + .build(); } /** @@ -307,7 +315,7 @@ public ProcessCredentialsProvider build() { @Override public String toString() { - return ToString.builder("ProcessCredentialsProvider") + return ToString.builder(PROVIDER_NAME) .add("cmd", executableCommand) .build(); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java index aa67c4dd68b4..7e340f634969 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java @@ -24,10 +24,22 @@ */ @SdkPublicApi public final class StaticCredentialsProvider implements AwsCredentialsProvider { + private static final String PROVIDER_NAME = "StaticCredentialsProvider"; private final AwsCredentials credentials; private StaticCredentialsProvider(AwsCredentials credentials) { - this.credentials = Validate.notNull(credentials, "Credentials must not be null."); + Validate.notNull(credentials, "Credentials must not be null."); + this.credentials = withProviderName(credentials); + } + + private AwsCredentials withProviderName(AwsCredentials credentials) { + if (credentials instanceof AwsBasicCredentials) { + return ((AwsBasicCredentials) credentials).copy(c -> c.providerName(PROVIDER_NAME)); + } + if (credentials instanceof AwsSessionCredentials) { + return ((AwsSessionCredentials) credentials).copy(c -> c.providerName(PROVIDER_NAME)); + } + return credentials; } /** @@ -44,7 +56,7 @@ public AwsCredentials resolveCredentials() { @Override public String toString() { - return ToString.builder("StaticCredentialsProvider") + return ToString.builder(PROVIDER_NAME) .add("credentials", credentials) .build(); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java index 135b6ce88565..d2b5a93973eb 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java @@ -28,6 +28,8 @@ @SdkPublicApi public final class SystemPropertyCredentialsProvider extends SystemSettingsCredentialsProvider { + private static final String PROVIDER_NAME = "SystemSettingsCredentialsProvider"; + private SystemPropertyCredentialsProvider() { } @@ -43,8 +45,13 @@ protected Optional loadSetting(SystemSetting setting) { // CHECKSTYLE:ON } + @Override + protected String provider() { + return PROVIDER_NAME; + } + @Override public String toString() { - return ToString.create("SystemPropertyCredentialsProvider"); + return ToString.create(PROVIDER_NAME); } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/HttpCredentialsLoader.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/HttpCredentialsLoader.java index 77a8c6ddb231..507ae7c6f44f 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/HttpCredentialsLoader.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/HttpCredentialsLoader.java @@ -45,11 +45,14 @@ public final class HttpCredentialsLoader { private static final Pattern TRAILING_ZERO_OFFSET_TIME_PATTERN = Pattern.compile("\\+0000$"); - private HttpCredentialsLoader() { + private final String providerName; + + private HttpCredentialsLoader(String providerName) { + this.providerName = providerName; } - public static HttpCredentialsLoader create() { - return new HttpCredentialsLoader(); + public static HttpCredentialsLoader create(String providerName) { + return new HttpCredentialsLoader(providerName); } public LoadedCredentials loadCredentials(ResourcesEndpointProvider endpoint) { @@ -68,7 +71,8 @@ public LoadedCredentials loadCredentials(ResourcesEndpointProvider endpoint) { return new LoadedCredentials(accessKey.text(), secretKey.text(), token != null ? token.text() : null, - expiration != null ? expiration.text() : null); + expiration != null ? expiration.text() : null, + providerName); } catch (SdkClientException e) { throw e; } catch (RuntimeException | IOException e) { @@ -84,20 +88,29 @@ public static final class LoadedCredentials { private final String secretKey; private final String token; private final Instant expiration; + private final String providerName; - private LoadedCredentials(String accessKeyId, String secretKey, String token, String expiration) { + private LoadedCredentials(String accessKeyId, String secretKey, String token, String expiration, String providerName) { this.accessKeyId = Validate.paramNotBlank(accessKeyId, "accessKeyId"); this.secretKey = Validate.paramNotBlank(secretKey, "secretKey"); this.token = token; this.expiration = expiration == null ? null : parseExpiration(expiration); + this.providerName = providerName; } public AwsCredentials getAwsCredentials() { - if (token == null) { - return AwsBasicCredentials.create(accessKeyId, secretKey); - } else { - return AwsSessionCredentials.create(accessKeyId, secretKey, token); - } + return token != null ? + AwsSessionCredentials.builder() + .accessKeyId(accessKeyId) + .secretAccessKey(secretKey) + .sessionToken(token) + .providerName(providerName) + .build() : + AwsBasicCredentials.builder() + .accessKeyId(accessKeyId) + .secretAccessKey(secretKey) + .providerName(providerName) + .build(); } public Optional getExpiration() { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/SystemSettingsCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/SystemSettingsCredentialsProvider.java index 9c4a642c251c..1f980b0b358d 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/SystemSettingsCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/SystemSettingsCredentialsProvider.java @@ -43,6 +43,7 @@ */ @SdkInternalApi public abstract class SystemSettingsCredentialsProvider implements AwsCredentialsProvider { + @Override public AwsCredentials resolveCredentials() { String accessKey = trim(loadSetting(SdkSystemSetting.AWS_ACCESS_KEY_ID).orElse(null)); @@ -67,12 +68,23 @@ public AwsCredentials resolveCredentials() { .build(); } - return StringUtils.isBlank(sessionToken) ? AwsBasicCredentials.create(accessKey, secretKey) - : AwsSessionCredentials.create(accessKey, secretKey, sessionToken); + return StringUtils.isBlank(sessionToken) ? AwsBasicCredentials.builder() + .accessKeyId(accessKey) + .secretAccessKey(secretKey) + .providerName(provider()) + .build() + : AwsSessionCredentials.builder() + .accessKeyId(accessKey) + .secretAccessKey(secretKey) + .sessionToken(sessionToken) + .providerName(provider()) + .build(); } /** * Implemented by child classes to load the requested setting. */ protected abstract Optional loadSetting(SystemSetting setting); + + protected abstract String provider(); } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProviderTest.java new file mode 100644 index 000000000000..a2e12c9d81b8 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/AnonymousCredentialsProviderTest.java @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.credentials; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class AnonymousCredentialsProviderTest { + + @Test + void resolveCredentials_returnsAnonymousCredentials() { + AwsCredentials credentials = AnonymousCredentialsProvider.create().resolveCredentials(); + assertThat(credentials.accessKeyId()).isNull(); + assertThat(credentials.secretAccessKey()).isNull(); + assertThat(credentials.providerName()).isPresent().contains("AnonymousCredentialsProvider"); + } +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java index 3de68e16a4c9..0f20fe51a5a6 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java @@ -23,10 +23,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static software.amazon.awssdk.core.SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI; -import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import java.net.URI; import org.apache.commons.lang.exception.ExceptionUtils; import org.junit.AfterClass; import org.junit.Before; @@ -34,7 +32,6 @@ import org.junit.Test; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.util.SdkUserAgent; -import software.amazon.awssdk.regions.util.ResourcesEndpointProvider; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; /** @@ -89,6 +86,7 @@ public void testGetCredentialsReturnsValidResponseFromEcsEndpoint() { assertThat(credentials.accessKeyId()).isEqualTo(ACCESS_KEY_ID); assertThat(credentials.secretAccessKey()).isEqualTo(SECRET_ACCESS_KEY); assertThat(credentials.sessionToken()).isEqualTo(TOKEN); + assertThat(credentials.providerName()).isPresent().contains("ContainerCredentialsProvider"); } /** @@ -131,20 +129,4 @@ private String getSuccessfulBody() { "\"Token\":\"TOKEN_TOKEN_TOKEN\"," + "\"Expiration\":\"3000-05-03T04:55:54Z\"}"; } - - /** - * Dummy CredentialsPathProvider that overrides the endpoint and connects to the WireMock server. - */ - private static class TestCredentialsEndpointProvider implements ResourcesEndpointProvider { - private final String host; - - public TestCredentialsEndpointProvider(String host) { - this.host = host; - } - - @Override - public URI endpoint() { - return invokeSafely(() -> new URI(host + CREDENTIALS_PATH)); - } - } } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/HttpCredentialsLoaderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/HttpCredentialsLoaderTest.java index afa1ec0fa4df..09d9e0b6a296 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/HttpCredentialsLoaderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/HttpCredentialsLoaderTest.java @@ -27,8 +27,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.time.Duration; -import java.time.Instant; import java.util.Date; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -48,6 +46,7 @@ public class HttpCredentialsLoaderTest { private static final long ONE_MINUTE = 1000L * 60; /** Environment variable name for the AWS ECS Container credentials path. */ private static final String CREDENTIALS_PATH = "/dummy/credentials/path"; + private static final String PROVIDER_NAME = "HttpCredentialsProvider"; private static String successResponse; private static String successResponseWithInvalidBody; @@ -70,7 +69,7 @@ public static void setup() throws IOException { public void testLoadCredentialsParsesJsonResponseProperly() { stubForSuccessResponseWithCustomBody(successResponse); - HttpCredentialsLoader credentialsProvider = HttpCredentialsLoader.create(); + HttpCredentialsLoader credentialsProvider = HttpCredentialsLoader.create(PROVIDER_NAME); AwsSessionCredentials credentials = (AwsSessionCredentials) credentialsProvider.loadCredentials(testEndpointProvider()) .getAwsCredentials(); @@ -88,7 +87,7 @@ public void testLoadCredentialsThrowsAceWhenClientResponseDontHaveKeys() { // Stub for success response but without keys in the response body stubForSuccessResponseWithCustomBody(successResponseWithInvalidBody); - HttpCredentialsLoader credentialsProvider = HttpCredentialsLoader.create(); + HttpCredentialsLoader credentialsProvider = HttpCredentialsLoader.create(PROVIDER_NAME); assertThatExceptionOfType(SdkClientException.class).isThrownBy(() -> credentialsProvider.loadCredentials(testEndpointProvider())) .withMessage("Failed to load credentials from metadata service."); @@ -102,7 +101,7 @@ public void testLoadCredentialsThrowsAceWhenClientResponseDontHaveKeys() { public void testNoMetadataService() throws Exception { stubForErrorResponse(); - HttpCredentialsLoader credentialsProvider = HttpCredentialsLoader.create(); + HttpCredentialsLoader credentialsProvider = HttpCredentialsLoader.create(PROVIDER_NAME); // When there are no credentials, the provider should throw an exception if we can't connect assertThatExceptionOfType(SdkClientException.class).isThrownBy(() -> credentialsProvider.loadCredentials(testEndpointProvider())); diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java index 05db14c7a2fd..c54a2ca3d4d1 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java @@ -131,10 +131,13 @@ private void verifyImdsCallInsecure() { } @Test - public void resolveCredentials_queriesTokenResource_includesTokenInCredentialsRequests() { + public void resolveCredentials_usesTokenByDefault() { stubSecureCredentialsResponse(aResponse().withBody(STUB_CREDENTIALS)); InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build(); - provider.resolveCredentials(); + AwsCredentials credentials = provider.resolveCredentials(); + assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID"); + assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY"); + assertThat(credentials.providerName()).isPresent().contains("InstanceProfileCredentialsProvider"); verifyImdsCallWithToken(); } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java index 5d6e36e411b4..d794421c40bf 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java @@ -16,39 +16,31 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertNotNull; +import static software.amazon.awssdk.auth.credentials.internal.ProcessCredentialsTestUtils.copyErrorCaseProcessCredentialsScript; +import static software.amazon.awssdk.auth.credentials.internal.ProcessCredentialsTestUtils.copyHappyCaseProcessCredentialsScript; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; import java.time.Duration; import java.time.Instant; -import org.assertj.core.api.Assertions; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import software.amazon.awssdk.utils.DateUtils; -import software.amazon.awssdk.utils.IoUtils; -import software.amazon.awssdk.utils.Platform; -public class ProcessCredentialsProviderTest { +class ProcessCredentialsProviderTest { private static final String PROCESS_RESOURCE_PATH = "/resources/process/"; private static final String RANDOM_SESSION_TOKEN = "RANDOM_TOKEN"; private static String scriptLocation; private static String errorScriptLocation; - @BeforeClass + @BeforeAll public static void setup() { scriptLocation = copyHappyCaseProcessCredentialsScript(); errorScriptLocation = copyErrorCaseProcessCredentialsScript(); } - @AfterClass + @AfterAll public static void teardown() { if (scriptLocation != null && !new File(scriptLocation).delete()) { throw new IllegalStateException("Failed to delete file: " + scriptLocation); @@ -60,20 +52,21 @@ public static void teardown() { } @Test - public void staticCredentialsCanBeLoaded() { + void staticCredentialsCanBeLoaded() { AwsCredentials credentials = ProcessCredentialsProvider.builder() .command(scriptLocation + " accessKeyId secretAccessKey") .build() .resolveCredentials(); - Assert.assertFalse(credentials instanceof AwsSessionCredentials); - Assert.assertEquals("accessKeyId", credentials.accessKeyId()); - Assert.assertEquals("secretAccessKey", credentials.secretAccessKey()); + assertThat(credentials).isInstanceOf(AwsBasicCredentials.class); + assertThat(credentials.accessKeyId()).isEqualTo("accessKeyId"); + assertThat(credentials.secretAccessKey()).isEqualTo("secretAccessKey"); + assertThat(credentials.providerName()).isPresent().contains("ProcessCredentialsProvider"); } @Test - public void sessionCredentialsCanBeLoaded() { + void sessionCredentialsCanBeLoaded() { ProcessCredentialsProvider credentialsProvider = ProcessCredentialsProvider.builder() .command(scriptLocation + " accessKeyId secretAccessKey sessionToken " + @@ -83,17 +76,17 @@ public void sessionCredentialsCanBeLoaded() { AwsCredentials credentials = credentialsProvider.resolveCredentials(); - Assert.assertTrue(credentials instanceof AwsSessionCredentials); + assertThat(credentials).isInstanceOf(AwsSessionCredentials.class); AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentials; - Assert.assertEquals("accessKeyId", sessionCredentials.accessKeyId()); - Assert.assertEquals("secretAccessKey", sessionCredentials.secretAccessKey()); - assertNotNull(sessionCredentials.sessionToken()); + assertThat(credentials.accessKeyId()).isEqualTo("accessKeyId"); + assertThat(credentials.secretAccessKey()).isEqualTo("secretAccessKey"); + assertThat(sessionCredentials.sessionToken()).isNotNull(); } @Test - public void resultsAreCached() { + void resultsAreCached() { ProcessCredentialsProvider credentialsProvider = ProcessCredentialsProvider.builder() .command(scriptLocation + " accessKeyId secretAccessKey sessionToken " + @@ -103,11 +96,11 @@ public void resultsAreCached() { AwsCredentials request1 = credentialsProvider.resolveCredentials(); AwsCredentials request2 = credentialsProvider.resolveCredentials(); - Assert.assertEquals(request1, request2); + assertThat(request1).isEqualTo(request2); } @Test - public void expirationBufferOverrideIsApplied() { + void expirationBufferOverrideIsApplied() { ProcessCredentialsProvider credentialsProvider = ProcessCredentialsProvider.builder() .command(String.format("%s accessKeyId secretAccessKey %s %s", @@ -120,11 +113,11 @@ public void expirationBufferOverrideIsApplied() { AwsCredentials request1 = credentialsProvider.resolveCredentials(); AwsCredentials request2 = credentialsProvider.resolveCredentials(); - Assert.assertNotEquals(request1, request2); + assertThat(request1).isNotEqualTo(request2); } @Test - public void processFailed_shouldContainErrorMessage() { + void processFailed_shouldContainErrorMessage() { ProcessCredentialsProvider credentialsProvider = ProcessCredentialsProvider.builder() .command(errorScriptLocation) @@ -137,7 +130,7 @@ public void processFailed_shouldContainErrorMessage() { } @Test - public void lackOfExpirationIsCachedForever() { + void lackOfExpirationIsCachedForever() { ProcessCredentialsProvider credentialsProvider = ProcessCredentialsProvider.builder() .command(scriptLocation + " accessKeyId secretAccessKey sessionToken") @@ -147,20 +140,21 @@ public void lackOfExpirationIsCachedForever() { AwsCredentials request1 = credentialsProvider.resolveCredentials(); AwsCredentials request2 = credentialsProvider.resolveCredentials(); - Assert.assertEquals(request1, request2); + assertThat(request1).isEqualTo(request2); } - @Test(expected = IllegalStateException.class) - public void processOutputLimitIsEnforced() { - ProcessCredentialsProvider.builder() - .command(scriptLocation + " accessKeyId secretAccessKey") - .processOutputLimit(1) - .build() - .resolveCredentials(); + @Test + void processOutputLimitIsEnforced() { + ProcessCredentialsProvider credentialsProvider = + ProcessCredentialsProvider.builder() + .command(scriptLocation + " accessKeyId secretAccessKey") + .processOutputLimit(1) + .build(); + assertThatThrownBy(credentialsProvider::resolveCredentials).isInstanceOf(IllegalStateException.class); } @Test - public void processOutputLimitDefaultPassesLargeInput() { + void processOutputLimitDefaultPassesLargeInput() { String LONG_SESSION_TOKEN = "lYzvmByqdS1E69QQVEavDDHabQ2GuYKYABKRA4xLbAXpdnFtV030UH4" + "bQoZWCDcfADFvBwBm3ixEFTYMjn5XQozpFV2QAsWHirCVcEJ5DC60KPCNBcDi4KLNJfbsp3r6kKTOmYOeqhEyiC4emDX33X2ppZsa5" + @@ -181,12 +175,12 @@ public void processOutputLimitDefaultPassesLargeInput() { AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentialsProvider.resolveCredentials(); - Assertions.assertThat(sessionCredentials.accessKeyId()).isEqualTo("accessKeyId"); - Assertions.assertThat(sessionCredentials.sessionToken()).isNotNull(); + assertThat(sessionCredentials.accessKeyId()).isEqualTo("accessKeyId"); + assertThat(sessionCredentials.sessionToken()).isNotNull(); } @Test - public void closeDoesNotRaise() { + void closeDoesNotRaise() { ProcessCredentialsProvider credentialsProvider = ProcessCredentialsProvider.builder() .command(scriptLocation + " accessKeyId secretAccessKey sessionToken") @@ -194,47 +188,4 @@ public void closeDoesNotRaise() { credentialsProvider.resolveCredentials(); credentialsProvider.close(); } - - public static String copyHappyCaseProcessCredentialsScript() { - String scriptClasspathFilename = Platform.isWindows() ? "windows-credentials-script.bat" - : "linux-credentials-script.sh"; - - return copyProcessCredentialsScript(scriptClasspathFilename); - } - - public static String copyErrorCaseProcessCredentialsScript() { - String scriptClasspathFilename = Platform.isWindows() ? "windows-credentials-error-script.bat" - : "linux-credentials-error-script.sh"; - - return copyProcessCredentialsScript(scriptClasspathFilename); - } - - public static String copyProcessCredentialsScript(String scriptClasspathFilename) { - String scriptClasspathLocation = PROCESS_RESOURCE_PATH + scriptClasspathFilename; - - InputStream scriptInputStream = null; - OutputStream scriptOutputStream = null; - - try { - scriptInputStream = ProcessCredentialsProviderTest.class.getResourceAsStream(scriptClasspathLocation); - - File scriptFileOnDisk = File.createTempFile("ProcessCredentialsProviderTest", scriptClasspathFilename); - scriptFileOnDisk.deleteOnExit(); - - if (!scriptFileOnDisk.setExecutable(true)) { - throw new IllegalStateException("Could not make " + scriptFileOnDisk + " executable."); - } - - scriptOutputStream = new FileOutputStream(scriptFileOnDisk); - - IoUtils.copy(scriptInputStream, scriptOutputStream); - - return scriptFileOnDisk.getAbsolutePath(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } finally { - IoUtils.closeQuietly(scriptInputStream, null); - IoUtils.closeQuietly(scriptOutputStream, null); - } - } } \ No newline at end of file diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java index 19c99236eee6..8254e04c0ab1 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java @@ -15,28 +15,32 @@ package software.amazon.awssdk.auth.credentials; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class StaticCredentialsProviderTest { +class StaticCredentialsProviderTest { @Test - public void getAwsCredentials_ReturnsSameCredentials() throws Exception { - final AwsCredentials credentials = new AwsBasicCredentials("akid", "skid"); - final AwsCredentials actualCredentials = - StaticCredentialsProvider.create(credentials).resolveCredentials(); - assertEquals(credentials, actualCredentials); + void getAwsCredentials_ReturnsSameCredentials() { + AwsCredentials credentials = new AwsBasicCredentials("akid", "skid"); + AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials(); + assertThat(credentials).isEqualTo(actualCredentials); + assertThat(credentials.providerName()).isNotPresent(); + assertThat(actualCredentials.providerName()).isPresent(); } @Test - public void getSessionAwsCredentials_ReturnsSameCredentials() throws Exception { - final AwsSessionCredentials credentials = AwsSessionCredentials.create("akid", "skid", "token"); - final AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials(); - assertEquals(credentials, actualCredentials); + void getSessionAwsCredentials_ReturnsSameCredentials() { + AwsSessionCredentials credentials = AwsSessionCredentials.create("akid", "skid", "token"); + AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials(); + assertThat(credentials).isEqualTo(actualCredentials); + assertThat(credentials.providerName()).isNotPresent(); + assertThat(actualCredentials.providerName()).isPresent(); } - @Test(expected = RuntimeException.class) - public void nullCredentials_ThrowsIllegalArgumentException() { - StaticCredentialsProvider.create(null); + @Test + void nullCredentials_ThrowsRuntimeException() { + assertThatThrownBy(() -> StaticCredentialsProvider.create(null)).isInstanceOf(RuntimeException.class); } } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java new file mode 100644 index 000000000000..615bcb63f1d9 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.credentials; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; + +class SystemSettingsCredentialsProviderTest { + + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); + + @BeforeAll + public static void methodSetup() { + System.setProperty(SdkSystemSetting.AWS_ACCESS_KEY_ID.property(), "akid1"); + System.setProperty(SdkSystemSetting.AWS_SECRET_ACCESS_KEY.property(), "skid1"); + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_ACCESS_KEY_ID.environmentVariable(), "akid2"); + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_SECRET_ACCESS_KEY.environmentVariable(), "skid2"); + } + + @AfterAll + public static void teardown() { + System.clearProperty(SdkSystemSetting.AWS_ACCESS_KEY_ID.property()); + System.clearProperty(SdkSystemSetting.AWS_SECRET_ACCESS_KEY.property()); + ENVIRONMENT_VARIABLE_HELPER.reset(); + } + + @Test + void systemPropertyCredentialsProvider_resolveCredentials_returnsCredentialsWithProvider() { + AwsCredentials credentials = SystemPropertyCredentialsProvider.create().resolveCredentials(); + assertThat(credentials.accessKeyId()).isEqualTo("akid1"); + assertThat(credentials.secretAccessKey()).isEqualTo("skid1"); + assertThat(credentials.providerName()).isPresent().contains("SystemSettingsCredentialsProvider"); + } + + @Test + void environmentVariableCredentialsProvider_resolveCredentials_returnsCredentialsWithProvider() { + AwsCredentials credentials = EnvironmentVariableCredentialsProvider.create().resolveCredentials(); + assertThat(credentials.accessKeyId()).isEqualTo("akid2"); + assertThat(credentials.secretAccessKey()).isEqualTo("skid2"); + assertThat(credentials.providerName()).isPresent().contains("EnvironmentVariableCredentialsProvider"); + } +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsBasicCredentialsTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsBasicCredentialsTest.java new file mode 100644 index 000000000000..9986118deda3 --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsBasicCredentialsTest.java @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.credentials.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; + +class AwsBasicCredentialsTest { + + private static final String ACCESS_KEY_ID = "accessKeyId"; + private static final String SECRET_ACCESS_KEY = "secretAccessKey"; + private static final String PROVIDER_NAME = "StaticCredentialsProvider"; + + @Test + void equalsHashcode() { + EqualsVerifier.forClass(AwsBasicCredentials.class) + .withIgnoredFields("validateCredentials") + .withIgnoredFields("providerName") + .verify(); + } + + @Test + void emptyBuilder_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsBasicCredentials.builder().build()); + } + + @Test + void builderMissingAccessKeyId_ThrowsException() { + assertThrows(NullPointerException.class, () -> AwsBasicCredentials.builder() + .secretAccessKey(SECRET_ACCESS_KEY) + .build()); + } + + @Test + void create_isSuccessful() { + AwsBasicCredentials identity = AwsBasicCredentials.create(ACCESS_KEY_ID, + SECRET_ACCESS_KEY); + assertEquals(ACCESS_KEY_ID, identity.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); + } + + @Test + void build_isSuccessful() { + AwsBasicCredentials identity = AwsBasicCredentials.builder() + .accessKeyId(ACCESS_KEY_ID) + .secretAccessKey(SECRET_ACCESS_KEY) + .build(); + assertEquals(ACCESS_KEY_ID, identity.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); + } + + @Test + void copy_isSuccessful() { + AwsBasicCredentials identity = AwsBasicCredentials.builder() + .accessKeyId(ACCESS_KEY_ID) + .secretAccessKey(SECRET_ACCESS_KEY) + .build(); + AwsBasicCredentials copy = identity.copy(c -> c.providerName(PROVIDER_NAME)); + assertEquals(ACCESS_KEY_ID, copy.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, copy.secretAccessKey()); + assertEquals(PROVIDER_NAME, copy.providerName().get()); + } +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsSessionCredentialsTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsSessionCredentialsTest.java index e0ccc19c5954..cb3a62600494 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsSessionCredentialsTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/AwsSessionCredentialsTest.java @@ -22,24 +22,27 @@ import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; -public class AwsSessionCredentialsTest { +class AwsSessionCredentialsTest { private static final String ACCESS_KEY_ID = "accessKeyId"; private static final String SECRET_ACCESS_KEY = "secretAccessKey"; private static final String SESSION_TOKEN = "sessionToken"; + private static final String PROVIDER_NAME = "StaticCredentialsProvider"; - public void equalsHashcode() { + @Test + void equalsHashcode() { EqualsVerifier.forClass(AwsSessionCredentials.class) + .withIgnoredFields("providerName") .verify(); } @Test - public void emptyBuilder_ThrowsException() { + void emptyBuilder_ThrowsException() { assertThrows(NullPointerException.class, () -> AwsSessionCredentials.builder().build()); } @Test - public void builderMissingSessionToken_ThrowsException() { + void builderMissingSessionToken_ThrowsException() { assertThrows(NullPointerException.class, () -> AwsSessionCredentials.builder() .accessKeyId(ACCESS_KEY_ID) .secretAccessKey(SECRET_ACCESS_KEY) @@ -47,7 +50,7 @@ public void builderMissingSessionToken_ThrowsException() { } @Test - public void builderMissingAccessKeyId_ThrowsException() { + void builderMissingAccessKeyId_ThrowsException() { assertThrows(NullPointerException.class, () -> AwsSessionCredentials.builder() .secretAccessKey(SECRET_ACCESS_KEY) .sessionToken(SESSION_TOKEN) @@ -55,7 +58,7 @@ public void builderMissingAccessKeyId_ThrowsException() { } @Test - public void create_isSuccessful() { + void create_isSuccessful() { AwsSessionCredentials identity = AwsSessionCredentials.create(ACCESS_KEY_ID, SECRET_ACCESS_KEY, SESSION_TOKEN); @@ -65,7 +68,7 @@ public void create_isSuccessful() { } @Test - public void build_isSuccessful() { + void build_isSuccessful() { AwsSessionCredentials identity = AwsSessionCredentials.builder() .accessKeyId(ACCESS_KEY_ID) .secretAccessKey(SECRET_ACCESS_KEY) @@ -75,4 +78,18 @@ public void build_isSuccessful() { assertEquals(SECRET_ACCESS_KEY, identity.secretAccessKey()); assertEquals(SESSION_TOKEN, identity.sessionToken()); } + + @Test + void copy_isSuccessful() { + AwsSessionCredentials identity = AwsSessionCredentials.builder() + .accessKeyId(ACCESS_KEY_ID) + .secretAccessKey(SECRET_ACCESS_KEY) + .sessionToken(SESSION_TOKEN) + .build(); + AwsSessionCredentials copy = identity.copy(c -> c.providerName(PROVIDER_NAME)); + assertEquals(ACCESS_KEY_ID, copy.accessKeyId()); + assertEquals(SECRET_ACCESS_KEY, copy.secretAccessKey()); + assertEquals(SESSION_TOKEN, copy.sessionToken()); + assertEquals(PROVIDER_NAME, copy.providerName().get()); + } } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProcessCredentialsTestUtils.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProcessCredentialsTestUtils.java new file mode 100644 index 000000000000..e42fe772fb4b --- /dev/null +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProcessCredentialsTestUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.credentials.internal; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.Platform; + +public final class ProcessCredentialsTestUtils { + + private static final String PROCESS_RESOURCE_PATH = "/resources/process/"; + + private ProcessCredentialsTestUtils() { + } + + public static String copyErrorCaseProcessCredentialsScript() { + String scriptClasspathFilename = Platform.isWindows() ? "windows-credentials-error-script.bat" + : "linux-credentials-error-script.sh"; + + return copyProcessCredentialsScript(scriptClasspathFilename); + } + + public static String copyHappyCaseProcessCredentialsScript() { + String scriptClasspathFilename = Platform.isWindows() ? "windows-credentials-script.bat" + : "linux-credentials-script.sh"; + + return copyProcessCredentialsScript(scriptClasspathFilename); + } + + public static String copyProcessCredentialsScript(String scriptClasspathFilename) { + InputStream scriptInputStream = null; + OutputStream scriptOutputStream = null; + + try { + scriptInputStream = ProcessCredentialsTestUtils.class + .getResourceAsStream(PROCESS_RESOURCE_PATH + scriptClasspathFilename); + + File scriptFileOnDisk = File.createTempFile("ProcessCredentialsProviderTest", scriptClasspathFilename); + scriptFileOnDisk.deleteOnExit(); + + if (!scriptFileOnDisk.setExecutable(true)) { + throw new IllegalStateException("Could not make " + scriptFileOnDisk + " executable."); + } + + scriptOutputStream = Files.newOutputStream(scriptFileOnDisk.toPath()); + + IoUtils.copy(scriptInputStream, scriptOutputStream); + + return scriptFileOnDisk.getAbsolutePath(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + IoUtils.closeQuietly(scriptInputStream, null); + IoUtils.closeQuietly(scriptOutputStream, null); + } + } +} diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtilsTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtilsTest.java index 54d028c98dbb..ef95613e5efc 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtilsTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtilsTest.java @@ -17,9 +17,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.awssdk.auth.credentials.internal.ProcessCredentialsTestUtils.copyHappyCaseProcessCredentialsScript; import java.io.File; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; @@ -32,9 +32,6 @@ import org.junit.jupiter.params.provider.MethodSource; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; -import software.amazon.awssdk.auth.credentials.ProcessCredentialsProviderTest; -import software.amazon.awssdk.core.checksums.Algorithm; -import software.amazon.awssdk.core.checksums.SdkChecksum; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileProperty; import software.amazon.awssdk.utils.StringInputStream; @@ -44,7 +41,7 @@ public class ProfileCredentialsUtilsTest { @BeforeAll public static void setup() { - scriptLocation = ProcessCredentialsProviderTest.copyHappyCaseProcessCredentialsScript(); + scriptLocation = copyHappyCaseProcessCredentialsScript(); } @AfterAll diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java index d39e7eb53b2b..f738df91fcb0 100644 --- a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentity.java @@ -42,6 +42,7 @@ public interface AwsCredentialsIdentity extends Identity { */ String secretAccessKey(); + static Builder builder() { return DefaultAwsCredentialsIdentity.builder(); } @@ -69,6 +70,13 @@ interface Builder { */ Builder secretAccessKey(String secretAccessKey); + /** + * The name of the identity provider that created this credential identity. + */ + default Builder providerName(String providerName) { + return this; + } + AwsCredentialsIdentity build(); } } diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java index 10e0ec0634ce..bf3ae020fbe0 100644 --- a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentity.java @@ -66,6 +66,9 @@ interface Builder extends AwsCredentialsIdentity.Builder { */ Builder sessionToken(String sessionToken); + @Override + Builder providerName(String providerName); + @Override AwsSessionCredentialsIdentity build(); } diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java index 130fc9d67290..ea1e9fed091b 100644 --- a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/Identity.java @@ -38,4 +38,13 @@ public interface Identity { default Optional expirationTime() { return Optional.empty(); } + + /** + * The source that resolved this identity, normally an identity provider. Note that + * this string value would be set by an identity provider implementation and is + * intended to be used for for tracking purposes. Avoid building logic on its value. + */ + default Optional providerName() { + return Optional.empty(); + } } diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java index 7cca889709b1..2e38b32ded6a 100644 --- a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsCredentialsIdentity.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.identity.spi.internal; import java.util.Objects; +import java.util.Optional; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.utils.ToString; @@ -26,10 +27,12 @@ public final class DefaultAwsCredentialsIdentity implements AwsCredentialsIdenti private final String accessKeyId; private final String secretAccessKey; + private final String providerName; private DefaultAwsCredentialsIdentity(Builder builder) { this.accessKeyId = builder.accessKeyId; this.secretAccessKey = builder.secretAccessKey; + this.providerName = builder.providerName; Validate.paramNotNull(accessKeyId, "accessKeyId"); Validate.paramNotNull(secretAccessKey, "secretAccessKey"); @@ -49,10 +52,16 @@ public String secretAccessKey() { return secretAccessKey; } + @Override + public Optional providerName() { + return Optional.ofNullable(providerName); + } + @Override public String toString() { return ToString.builder("AwsCredentialsIdentity") .add("accessKeyId", accessKeyId) + .add("providerName", providerName) .build(); } @@ -80,6 +89,7 @@ public int hashCode() { private static final class Builder implements AwsCredentialsIdentity.Builder { private String accessKeyId; private String secretAccessKey; + private String providerName; private Builder() { } @@ -96,6 +106,12 @@ public Builder secretAccessKey(String secretAccessKey) { return this; } + @Override + public Builder providerName(String providerName) { + this.providerName = providerName; + return this; + } + @Override public AwsCredentialsIdentity build() { return new DefaultAwsCredentialsIdentity(this); diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java index 067d490b3838..7b07dfb4f31c 100644 --- a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultAwsSessionCredentialsIdentity.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.identity.spi.internal; import java.util.Objects; +import java.util.Optional; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; import software.amazon.awssdk.utils.ToString; @@ -27,11 +28,13 @@ public final class DefaultAwsSessionCredentialsIdentity implements AwsSessionCre private final String accessKeyId; private final String secretAccessKey; private final String sessionToken; + private final String providerName; private DefaultAwsSessionCredentialsIdentity(Builder builder) { this.accessKeyId = builder.accessKeyId; this.secretAccessKey = builder.secretAccessKey; this.sessionToken = builder.sessionToken; + this.providerName = builder.providerName; Validate.paramNotNull(accessKeyId, "accessKeyId"); Validate.paramNotNull(secretAccessKey, "secretAccessKey"); @@ -57,10 +60,16 @@ public String sessionToken() { return sessionToken; } + @Override + public Optional providerName() { + return Optional.ofNullable(providerName); + } + @Override public String toString() { return ToString.builder("AwsSessionCredentialsIdentity") .add("accessKeyId", accessKeyId) + .add("providerName", providerName) .build(); } @@ -91,6 +100,7 @@ private static final class Builder implements AwsSessionCredentialsIdentity.Buil private String accessKeyId; private String secretAccessKey; private String sessionToken; + private String providerName; private Builder() { } @@ -113,6 +123,12 @@ public Builder sessionToken(String sessionToken) { return this; } + @Override + public Builder providerName(String providerName) { + this.providerName = providerName; + return this; + } + @Override public AwsSessionCredentialsIdentity build() { return new DefaultAwsSessionCredentialsIdentity(this); diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java index 33974427ab65..a75cfafa340f 100644 --- a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsCredentialsIdentityTest.java @@ -30,6 +30,7 @@ public class AwsCredentialsIdentityTest { @Test public void equalsHashcode() { EqualsVerifier.forClass(DefaultAwsCredentialsIdentity.class) + .withIgnoredFields("providerName") .verify(); } diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java index 3702694e4fdd..b687b7b89348 100644 --- a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/AwsSessionCredentialsIdentityTest.java @@ -31,6 +31,7 @@ public class AwsSessionCredentialsIdentityTest { @Test public void equalsHashcode() { EqualsVerifier.forClass(DefaultAwsSessionCredentialsIdentity.class) + .withIgnoredFields("providerName") .verify(); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java index ee592eaeefde..12acc1a2e665 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java @@ -16,43 +16,60 @@ package software.amazon.awssdk.core.internal.http.pipeline.stages; import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BinaryOperator; +import java.util.function.UnaryOperator; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.ApiName; import software.amazon.awssdk.core.ClientType; import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.http.HttpClientDependencies; import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.internal.http.pipeline.MutableRequestToRequestPipeline; +import software.amazon.awssdk.core.internal.useragent.IdentityProviderNameMapping; import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.util.SdkUserAgent; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.http.SdkHttpUtils; /** - * Apply any custom user agent supplied, otherwise instrument the user agent with info about the SDK and environment. + * A stage for adding the user agent header to the request, after retrieving the current string + * from execution attributes and adding any additional information. */ @SdkInternalApi public class ApplyUserAgentStage implements MutableRequestToRequestPipeline { + + public static final String HEADER_USER_AGENT = "User-Agent"; + private static final Logger log = Logger.loggerFor(ApplyUserAgentStage.class); private static final String COMMA = ", "; + private static final String SLASH = "/"; private static final String SPACE = " "; - + private static final String HASH = "#"; private static final String IO = "io"; private static final String HTTP = "http"; private static final String CONFIG = "cfg"; private static final String RETRY_MODE = "retry-mode"; - + private static final String AUTH_HEADER = "auth-source"; private static final String AWS_EXECUTION_ENV_PREFIX = "exec-env/"; - private static final String HEADER_USER_AGENT = "User-Agent"; + private static final BinaryOperator API_NAMES = (name, version) -> name + "/" + version; + private static final BinaryOperator CONFIG_METADATA = (param, name) -> CONFIG + SLASH + param + HASH + name; + private static final UnaryOperator AUTH_CONFIG = name -> CONFIG_METADATA.apply(AUTH_HEADER, name); private final SdkClientConfiguration clientConfig; @@ -115,10 +132,10 @@ public static String resolveClientUserAgent(String userAgentPrefix, @Override public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, RequestExecutionContext context) throws Exception { - return request.putHeader(HEADER_USER_AGENT, getUserAgent(clientConfig, context.requestConfig().apiNames())); + return request.putHeader(HEADER_USER_AGENT, getUserAgent(clientConfig, context)); } - private String getUserAgent(SdkClientConfiguration config, List requestApiNames) { + private String getUserAgent(SdkClientConfiguration config, RequestExecutionContext context) { String clientUserAgent = clientConfig.option(SdkClientOption.CLIENT_USER_AGENT); if (clientUserAgent == null) { log.warn(() -> "Client user agent configuration is missing, so request user agent will be incomplete."); @@ -126,12 +143,14 @@ private String getUserAgent(SdkClientConfiguration config, List request } StringBuilder userAgent = new StringBuilder(clientUserAgent); - if (!requestApiNames.isEmpty()) { - requestApiNames.forEach(apiName -> { - userAgent.append(SPACE).append(apiName.name()).append("/").append(apiName.version()); - }); - } + //additional cfg information + identityProviderName(context.executionAttributes()) + .ifPresent(providerName -> userAgent.append(SPACE).append(AUTH_CONFIG.apply(providerName))); + + //request API names + requestApiNames(context.requestConfig().apiNames()).ifPresent(userAgent::append); + //suffix String userDefinedSuffix = config.option(SdkAdvancedClientOption.USER_AGENT_SUFFIX); if (!StringUtils.isEmpty(userDefinedSuffix)) { userAgent.append(COMMA).append(userDefinedSuffix.trim()); @@ -140,6 +159,32 @@ private String getUserAgent(SdkClientConfiguration config, List request return userAgent.toString(); } + private static Optional identityProviderName(ExecutionAttributes executionAttributes) { + SelectedAuthScheme selectedAuthScheme = executionAttributes + .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (selectedAuthScheme == null) { + return Optional.empty(); + } + return providerNameFromIdentity(selectedAuthScheme); + } + + private static Optional providerNameFromIdentity(SelectedAuthScheme selectedAuthScheme) { + CompletableFuture identityFuture = selectedAuthScheme.identity(); + T identity = CompletableFutureUtils.joinLikeSync(identityFuture); + return identity.providerName().flatMap(IdentityProviderNameMapping::mapFrom); + } + + private Optional requestApiNames(List requestApiNames) { + if (requestApiNames.isEmpty()) { + return Optional.empty(); + } + StringBuilder concatenatedNames = new StringBuilder(); + requestApiNames.forEach(apiName -> concatenatedNames.append(SPACE) + .append(API_NAMES.apply(apiName.name(), + apiName.version()))); + return Optional.of(concatenatedNames.toString()); + } + private static String clientName(ClientType clientType, SdkHttpClient syncHttpClient, SdkAsyncHttpClient asyncHttpClient) { if (clientType == ClientType.SYNC) { return syncHttpClient == null ? "null" : syncHttpClient.clientName(); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMapping.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMapping.java new file mode 100644 index 000000000000..d76b0b250a42 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMapping.java @@ -0,0 +1,95 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.internal.useragent; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.StringUtils; +import software.amazon.awssdk.utils.internal.EnumUtils; + +/** + * A enum class representing a short form of identity providers to record in the UA string. + */ +@SdkInternalApi +public enum IdentityProviderNameMapping { + + SYS("SystemPropertyCredentialsProvider"), + ENV("EnvironmentVariableCredentialsProvider"), + STSWEB("StsAssumeRoleWithWebIdentity"), + STSROLE("StsAssumeRoleCredentialsProvider"), + STSSAML("StsAssumeRoleWithWebIdentityCredentialsProvider"), + STSFED("StsGetFederationTokenCredentialsProvider"), + STSSESS("StsGetSessionTokenCredentialsProvider"), + SSO("SsoCredentialsProvider"), + PROF("ProfileCredentialsProvider"), + CONT("ContainerCredentialsProvider"), + IMDS("InstanceProfileCredentialsProvider"), + STAT("StaticCredentialsProvider"), + PROC("ProcessCredentialsProvider"), + ANON("AnonymousCredentialsProvider"), + UNKNOWN("Unknown"); + + private static final Pattern CLASS_NAME_CHARACTERS = Pattern.compile("[a-zA-Z_$\\d]{0,62}"); + private static final Map VALUE_MAP = + EnumUtils.uniqueIndex(IdentityProviderNameMapping.class, IdentityProviderNameMapping::toString); + private final String value; + + IdentityProviderNameMapping(String value) { + this.value = value; + } + + public String value() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + /** + * Map the given provider name to a shorter form. If null or empty, return unknown. + * If not recognized, use the given string if it conforms to the accepted pattern. + */ + public static Optional mapFrom(String source) { + if (StringUtils.isBlank(source)) { + return Optional.of(UNKNOWN.name().toLowerCase(Locale.US)); + } + return mappedName(source).map(mapping -> Optional.of(mapping.name().toLowerCase(Locale.US))) + .orElseGet(() -> sanitizedProviderOrNull(source)); + } + + private static Optional mappedName(String value) { + if (VALUE_MAP.containsKey(value)) { + return Optional.of(VALUE_MAP.get(value)); + } + return Optional.empty(); + } + + private static Optional sanitizedProviderOrNull(String value) { + if (hasAcceptedFormat(value)) { + return Optional.of(value); + } + return Optional.empty(); + } + + private static boolean hasAcceptedFormat(String input) { + return CLASS_NAME_CHARACTERS.matcher(input).matches(); + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java new file mode 100644 index 000000000000..50b72aa58031 --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java @@ -0,0 +1,150 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.internal.http.pipeline.stages; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage.HEADER_USER_AGENT; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkRequestOverrideConfiguration; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.http.ExecutionContext; +import software.amazon.awssdk.core.http.NoopTestRequest; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.internal.http.HttpClientDependencies; +import software.amazon.awssdk.core.internal.http.RequestExecutionContext; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity; +import software.amazon.awssdk.identity.spi.Identity; + +@RunWith(MockitoJUnitRunner.class) +public class ApplyUserAgentStageTest { + private static final SelectedAuthScheme EMPTY_SELECTED_AUTH_SCHEME = + new SelectedAuthScheme<>(CompletableFuture.completedFuture(Mockito.mock(Identity.class)), + (HttpSigner) Mockito.mock(HttpSigner.class), + AuthSchemeOption.builder().schemeId("mock").build()); + + private static final String SDK_UA_STRING = "aws-sdk-java/version vendor/unknown"; + private static final String PROVIDER_SOURCE = "ProcessCredentialsProvider"; + private static final AwsCredentialsIdentity IDENTITY_WITHOUT_SOURCE = + AwsCredentialsIdentity.create("akid", "secret"); + + private static final AwsCredentialsIdentity IDENTITY_WITH_SOURCE = + AwsSessionCredentialsIdentity.builder().accessKeyId("akid").secretAccessKey("secret").sessionToken("token") + .providerName(PROVIDER_SOURCE).build(); + + @Test + public void when_noAdditionalDataIsPresent_outputStringEqualsInputString() throws Exception { + String clientBuildTimeUserAgentString = SDK_UA_STRING; + + ApplyUserAgentStage stage = new ApplyUserAgentStage(dependenciesWithUserAgent(clientBuildTimeUserAgentString)); + + RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITHOUT_SOURCE), noOpRequest()); + SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx); + + List userAgentHeaders = request.headers().get(HEADER_USER_AGENT); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + assertThat(userAgentHeaders.get(0)).isEqualTo(SDK_UA_STRING); + } + + @Test + public void when_identityContainsProvider_authSourceIsPresent() throws Exception { + String clientBuildTimeUserAgentString = SDK_UA_STRING; + + ApplyUserAgentStage stage = new ApplyUserAgentStage(dependenciesWithUserAgent(clientBuildTimeUserAgentString)); + + RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITH_SOURCE), noOpRequest()); + SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx); + + List userAgentHeaders = request.headers().get(HEADER_USER_AGENT); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + assertThat(userAgentHeaders.get(0)).contains("auth-source#proc"); + } + + @Test + public void when_requestContainsApiName_apiNamesArePresent() throws Exception { + String clientBuildTimeUserAgentString = SDK_UA_STRING; + + ApplyUserAgentStage stage = new ApplyUserAgentStage(dependenciesWithUserAgent(clientBuildTimeUserAgentString)); + + RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITH_SOURCE), + requestWithApiName("myLib", "1.0")); + SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx); + + List userAgentHeaders = request.headers().get(HEADER_USER_AGENT); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + assertThat(userAgentHeaders.get(0)).contains("myLib/1.0"); + } + + private static HttpClientDependencies dependenciesWithUserAgent(String userAgent) { + SdkClientConfiguration clientConfiguration = SdkClientConfiguration.builder() + .option(SdkClientOption.CLIENT_USER_AGENT, userAgent) + .build(); + return HttpClientDependencies.builder() + .clientConfiguration(clientConfiguration) + .build(); + } + + private static SdkRequest noOpRequest() { + return requestWithOverrideConfig(null); + } + + private static SdkRequest requestWithApiName(String apiName, String version) { + SdkRequestOverrideConfiguration requestOverrideConfiguration = + SdkRequestOverrideConfiguration.builder() + .addApiName(a -> a.name(apiName).version(version)) + .build(); + return requestWithOverrideConfig(requestOverrideConfiguration); + } + + private static SdkRequest requestWithOverrideConfig(SdkRequestOverrideConfiguration overrideConfiguration) { + return NoopTestRequest.builder() + .overrideConfiguration(overrideConfiguration) + .build(); + } + + private static ExecutionAttributes executionAttributes(AwsCredentialsIdentity identity) { + ExecutionAttributes executionAttributes = new ExecutionAttributes(); + executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, + new SelectedAuthScheme<>(CompletableFuture.completedFuture(identity), + EMPTY_SELECTED_AUTH_SCHEME.signer(), + EMPTY_SELECTED_AUTH_SCHEME.authSchemeOption())); + return executionAttributes; + } + + private RequestExecutionContext requestExecutionContext(ExecutionAttributes executionAttributes, + SdkRequest request) { + ExecutionContext executionContext = ExecutionContext.builder() + .executionAttributes(executionAttributes) + .build(); + return RequestExecutionContext.builder() + .executionContext(executionContext) + .originalRequest(request).build(); + + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMappingTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMappingTest.java new file mode 100644 index 000000000000..e669ba82917e --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/IdentityProviderNameMappingTest.java @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.internal.useragent; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class IdentityProviderNameMappingTest { + + @Test + void when_providerIsNull_unknownValueIsReturned() { + Optional mappedProviderName = IdentityProviderNameMapping.mapFrom(null); + assertThat(mappedProviderName).isPresent().contains("unknown"); + } + + @Test + void when_providerIsEmpty_unknownValueIsReturned() { + Optional mappedProviderName = IdentityProviderNameMapping.mapFrom(""); + assertThat(mappedProviderName).isPresent().contains("unknown"); + } + + @Test + void when_providerIsKnown_shortValueIsReturned() { + Optional mappedProviderName = IdentityProviderNameMapping.mapFrom("StaticCredentialsProvider"); + assertThat(mappedProviderName).isPresent().contains("stat"); + } + + @Test + void when_providerIsUnknown_stringIsReturned() { + Optional mappedProviderName = IdentityProviderNameMapping.mapFrom("MyHomebrewedCredentialsProvider"); + assertThat(mappedProviderName).isPresent().contains("MyHomebrewedCredentialsProvider"); + } + + @Test + void when_providerIsIllegal_noValueIsReturned() { + Optional mappedProviderName = IdentityProviderNameMapping.mapFrom("My@#$%$CredentialsProvider"); + assertThat(mappedProviderName).isNotPresent(); + } + + @Test + void when_providerIsTooLong_noValueIsReturned() { + Optional mappedProviderName = IdentityProviderNameMapping.mapFrom( + "MyMegaGinormousBubbaBubbaBubbaBubbaBubbaBubbaCredentialsProvider"); + assertThat(mappedProviderName).isNotPresent(); + } +} diff --git a/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java b/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java index aab6fb110019..6df1de085cba 100644 --- a/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java +++ b/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java @@ -51,6 +51,7 @@ @SdkPublicApi public final class SsoCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable, ToCopyableBuilder { + private static final String PROVIDER_NAME = "SsoCredentialsProvider"; private static final Duration DEFAULT_STALE_TIME = Duration.ofMinutes(1); private static final Duration DEFAULT_PREFETCH_TIME = Duration.ofMinutes(5); @@ -106,9 +107,12 @@ private SessionCredentialsHolder getUpdatedCredentials(SsoClient ssoClient) { GetRoleCredentialsRequest request = getRoleCredentialsRequestSupplier.get(); notNull(request, "GetRoleCredentialsRequest can't be null."); RoleCredentials roleCredentials = ssoClient.getRoleCredentials(request).roleCredentials(); - AwsSessionCredentials sessionCredentials = AwsSessionCredentials.create(roleCredentials.accessKeyId(), - roleCredentials.secretAccessKey(), - roleCredentials.sessionToken()); + AwsSessionCredentials sessionCredentials = AwsSessionCredentials.builder() + .accessKeyId(roleCredentials.accessKeyId()) + .secretAccessKey(roleCredentials.secretAccessKey()) + .sessionToken(roleCredentials.sessionToken()) + .providerName(PROVIDER_NAME) + .build(); return new SessionCredentialsHolder(sessionCredentials, Instant.ofEpochMilli(roleCredentials.expiration())); } diff --git a/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java b/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java index 98f292ddb790..8d171b4edb23 100644 --- a/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java +++ b/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java @@ -133,6 +133,7 @@ private void callClientWithCredentialsProvider(Instant credentialsExpirationDate assertThat(actualCredentials.accessKeyId()).isEqualTo("a"); assertThat(actualCredentials.secretAccessKey()).isEqualTo("b"); assertThat(actualCredentials.sessionToken()).isEqualTo("c"); + assertThat(actualCredentials.providerName()).isPresent().contains("SsoCredentialsProvider"); } } diff --git a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManager.java b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManager.java index 914188dcc83d..267385b4d66c 100644 --- a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManager.java +++ b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManager.java @@ -118,7 +118,7 @@ private SsoOidcToken unmarshalToken(String contents) { .ifPresent(tokenBuilder::registrationExpiresAt); node.field("region").map(JsonNode::text).ifPresent(tokenBuilder::region); node.field("startUrl").map(JsonNode::text).ifPresent(tokenBuilder::startUrl); - + tokenBuilder.providerName(SsoOidcToken.PROVIDER_NAME); return tokenBuilder.build(); } diff --git a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcToken.java b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcToken.java index d1e42c8a7ebe..9f50a3154130 100644 --- a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcToken.java +++ b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcToken.java @@ -41,6 +41,7 @@ */ @SdkInternalApi public final class SsoOidcToken implements SdkToken { + public static final String PROVIDER_NAME = "SsoOidcTokenProvider"; private final String accessToken; private final Instant expiresAt; private final String refreshToken; @@ -49,6 +50,7 @@ public final class SsoOidcToken implements SdkToken { private final Instant registrationExpiresAt; private final String region; private final String startUrl; + private final String providerName; private SsoOidcToken(BuilderImpl builder) { Validate.paramNotNull(builder.accessToken, "accessToken"); @@ -61,6 +63,7 @@ private SsoOidcToken(BuilderImpl builder) { this.registrationExpiresAt = builder.registrationExpiresAt; this.region = builder.region; this.startUrl = builder.startUrl; + this.providerName = builder.providerName; } @Override @@ -73,6 +76,11 @@ public Optional expirationTime() { return Optional.of(expiresAt); } + @Override + public Optional providerName() { + return Optional.of(providerName); + } + public String refreshToken() { return refreshToken; } @@ -166,6 +174,8 @@ public interface Builder { Builder startUrl(String startUrl); + Builder providerName(String providerName); + SsoOidcToken build(); } @@ -178,6 +188,7 @@ private static class BuilderImpl implements Builder { private Instant registrationExpiresAt; private String region; private String startUrl; + private String providerName; @Override public Builder accessToken(String accessToken) { @@ -227,6 +238,12 @@ public Builder startUrl(String startUrl) { return this; } + @Override + public Builder providerName(String providerName) { + this.providerName = providerName; + return this; + } + @Override public SsoOidcToken build() { return new SsoOidcToken(this); diff --git a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTransformer.java b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTransformer.java index a0f5e56ddb41..feef372c41f9 100644 --- a/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTransformer.java +++ b/services/ssooidc/src/main/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTransformer.java @@ -54,6 +54,7 @@ public SsoOidcToken transform(CreateTokenResponse awsResponse) { .region(baseToken.region()) .clientSecret(baseToken.clientSecret()) .clientId(baseToken.clientId()) + .providerName(SsoOidcToken.PROVIDER_NAME) .build(); } } diff --git a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManagerTest.java b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManagerTest.java index 302732ac9eb8..724634980218 100644 --- a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManagerTest.java +++ b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/OnDiskTokenManagerTest.java @@ -68,6 +68,7 @@ public void loadToken_loadsCorrectFile(String sessionName, String expectedLocati SsoOidcToken token = SsoOidcToken.builder() .accessToken("accesstoken") .expiresAt(expiresAt) + .providerName("test") .build(); String tokenJson = String.format("{\n" + " \"accessToken\": \"accesstoken\",\n" @@ -110,6 +111,7 @@ public void loadToken_maximal() throws IOException { .registrationExpiresAt(registrationExpiresAt) .region("region") .startUrl("starturl") + .providerName("test") .build(); String ssoSession = "admin"; @@ -157,6 +159,7 @@ public void storeToken_maximal() throws IOException { .registrationExpiresAt(registrationExpiresAt) .region("region") .startUrl(startUrl) + .providerName("test") .build(); String expectedFile = "d033e22ae348aeb5660fc2140aec35850c4da997.json"; @@ -188,6 +191,7 @@ public void storeToken_loadToken_roundTrip() { .registrationExpiresAt(registrationExpiresAt) .region("region") .startUrl(startUrl) + .providerName("test") .build(); OnDiskTokenManager onDiskTokenManager = OnDiskTokenManager.create(cache, sessionName); diff --git a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenProviderTest.java b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenProviderTest.java index 7499fa85f636..627585a58405 100644 --- a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenProviderTest.java +++ b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenProviderTest.java @@ -19,8 +19,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -28,8 +26,6 @@ import static org.mockito.Mockito.when; import static software.amazon.awssdk.utils.UserHomeDirectoryUtils.userHomeDirectory; -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Jimfs; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -44,7 +40,6 @@ import java.util.Locale; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.token.credentials.SdkToken; import software.amazon.awssdk.awscore.internal.token.TokenManager; @@ -101,7 +96,7 @@ public void teardown() throws IOException { } @Test - public void resolveToken_usesTokenManager() { + void resolveToken_usesTokenManager() { SsoOidcToken ssoOidcToken = SsoOidcToken.builder() .accessToken("accesstoken") .expiresAt(Instant.now().plus(Duration.ofDays(1))) @@ -109,7 +104,9 @@ public void resolveToken_usesTokenManager() { mockTokenManager.storeToken(ssoOidcToken); SsoOidcTokenProvider tokenProvider = getDefaultSsoOidcTokenProviderBuilder().build(); - assertThat(tokenProvider.resolveToken()).isEqualTo(ssoOidcToken); + SdkToken resolvedToken = tokenProvider.resolveToken(); + assertThat(resolvedToken).isEqualTo(ssoOidcToken); + assertThat(resolvedToken.providerName()).isPresent().contains("SsoOidcTokenProvider"); } private SsoOidcTokenProvider.Builder getDefaultSsoOidcTokenProviderBuilder() { @@ -117,7 +114,7 @@ private SsoOidcTokenProvider.Builder getDefaultSsoOidcTokenProviderBuilder() { } @Test - public void resolveToken_cachedValueNotPresent_throws() { + void resolveToken_cachedValueNotPresent_throws() { SsoOidcTokenProvider tokenProvider = getDefaultSsoOidcTokenProviderBuilder().build(); @@ -145,7 +142,7 @@ public void resolveToken_cachedValueNotPresent_throws() { // } // }, @Test - public void standardTest_Valid_token_with_all_fields() { + void standardTest_Valid_token_with_all_fields() { SsoOidcToken token = getDefaultTokenBuilder() .expiresAt(Instant.now().plusSeconds(10000)).registrationExpiresAt(Instant.now().plusSeconds(90000)) .build(); @@ -160,7 +157,7 @@ public void standardTest_Valid_token_with_all_fields() { } @Test - public void refresh_returns_cached_token_when_service_calls_fails() { + void refresh_returns_cached_token_when_service_calls_fails() { SsoOidcToken nearToExpiryToken = getDefaultTokenBuilder() .expiresAt(Instant.now().plusSeconds(5000)).registrationExpiresAt(Instant.now().plusSeconds(10000)) .build(); @@ -177,7 +174,7 @@ public void refresh_returns_cached_token_when_service_calls_fails() { } @Test - public void refresh_fails_when_supplier_fails_due_to_Non_service_issues() { + void refresh_fails_when_supplier_fails_due_to_Non_service_issues() { SsoOidcToken nearToExpiryToken = getDefaultTokenBuilder() .expiresAt(Instant.now().minusSeconds(2)).registrationExpiresAt(Instant.now().minusSeconds(2)) .build(); @@ -201,7 +198,7 @@ public void refresh_fails_when_supplier_fails_due_to_Non_service_issues() { // } // }, @Test - public void standardTest_Minimal_valid_cached_token() { + void standardTest_Minimal_valid_cached_token() { Instant expiresAt = Instant.now().plusSeconds(3600); SsoOidcToken ssoOidcToken = SsoOidcToken.builder().accessToken("cachedtoken").expiresAt(expiresAt).build(); mockTokenManager.storeToken(ssoOidcToken); @@ -219,7 +216,7 @@ public void standardTest_Minimal_valid_cached_token() { // "expectedException": "ExpiredToken" // } @Test - public void standardTest_Minimal_expired_cached_token() { + void standardTest_Minimal_expired_cached_token() { String startUrl = START_URL; Instant expiresAt = Instant.parse("2021-12-25T13:00:00Z"); SsoOidcToken ssoOidcToken = @@ -247,7 +244,7 @@ public void standardTest_Minimal_expired_cached_token() { // "expectedException": "InvalidToken" // }, @Test - public void standardTest_Token_missing_the_expiresAt_field() { + void standardTest_Token_missing_the_expiresAt_field() { SsoOidcToken ssoOidcToken = SsoOidcToken.builder() .startUrl(START_URL) .accessToken("cachedtoken").clientId("client").clientSecret("secret") @@ -272,7 +269,7 @@ public void standardTest_Token_missing_the_expiresAt_field() { // "expectedException": "InvalidToken" // }, @Test - public void standardTest_Token_missing_the_accessToken_field() { + void standardTest_Token_missing_the_accessToken_field() { SsoOidcToken ssoOidcToken = SsoOidcToken.builder() .startUrl(START_URL) .accessToken("cachedtoken").clientId("client").clientSecret("secret") @@ -290,7 +287,7 @@ public void standardTest_Token_missing_the_accessToken_field() { } @Test - public void refresh_token_from_service_when_token_outside_expiry_window() { + void refresh_token_from_service_when_token_outside_expiry_window() { SsoOidcToken nearToExpiryToken = getDefaultTokenBuilder() .expiresAt(Instant.now().plusSeconds(59)) .registrationExpiresAt(Instant.now().plusSeconds(59)).build(); @@ -319,7 +316,7 @@ public void refresh_token_from_service_when_token_outside_expiry_window() { } @Test - public void refresh_token_does_not_fetch_from_service_when_token_inside_expiry_window() { + void refresh_token_does_not_fetch_from_service_when_token_inside_expiry_window() { SsoOidcToken cachedDiskToken = getDefaultTokenBuilder() .expiresAt(Instant.now().plusSeconds(120)).registrationExpiresAt(Instant.now().plusSeconds(120)) .build(); @@ -333,7 +330,7 @@ public void refresh_token_does_not_fetch_from_service_when_token_inside_expiry_w } @Test - public void token_is_obtained_from_inmemory_when_token_is_within_inmemory_stale_time() { + void token_is_obtained_from_inmemory_when_token_is_within_inmemory_stale_time() { Instant futureExpiryDate = Instant.now().plus(Duration.ofDays(1)); SsoOidcToken cachedDiskToken = getDefaultTokenBuilder() .expiresAt(futureExpiryDate).registrationExpiresAt(Instant.parse("2022-12-25T11:30:00Z")).build(); @@ -354,7 +351,7 @@ public void token_is_obtained_from_inmemory_when_token_is_within_inmemory_stale_ // Test to make sure cache fetches from Cached values. @Test - public void token_is_obtained_from_refresher_and_then_refresher_cache_if_its_within_stale_time() { + void token_is_obtained_from_refresher_and_then_refresher_cache_if_its_within_stale_time() { Instant closeToExpireTime = Instant.now().plus(Duration.ofMinutes(4)); SsoOidcToken cachedDiskToken = getDefaultTokenBuilder().accessToken("fourMinutesToExpire") .expiresAt(closeToExpireTime) @@ -383,7 +380,7 @@ public void token_is_obtained_from_refresher_and_then_refresher_cache_if_its_wit } @Test - public void token_is_retrieved_from_service_when_service_returns_tokens_with_short_expiration() { + void token_is_retrieved_from_service_when_service_returns_tokens_with_short_expiration() { Instant closeToExpireTime = Instant.now().plus(Duration.ofSeconds(4)); SsoOidcToken cachedDiskToken = getDefaultTokenBuilder().accessToken("fourMinutesToExpire") .expiresAt(closeToExpireTime) @@ -410,7 +407,7 @@ public void token_is_retrieved_from_service_when_service_returns_tokens_with_sho } @Test - public void token_is_retrieved_automatically_when_prefetch_time_is_set() throws InterruptedException { + void token_is_retrieved_automatically_when_prefetch_time_is_set() throws InterruptedException { Instant closeToExpireTime = Instant.now().plus(Duration.ofMillis(3)); SsoOidcToken cachedDiskToken = getDefaultTokenBuilder().accessToken("closeToExpire") @@ -450,13 +447,13 @@ private CreateTokenResponse someOne(CreateTokenResponse.Builder builder, String } @Test - public void tokenProvider_throws_exception_if_client_is_null() { + void tokenProvider_throws_exception_if_client_is_null() { assertThatExceptionOfType(NullPointerException.class).isThrownBy( () -> SsoOidcTokenProvider.builder().sessionName(START_URL).build()).withMessage("ssoOidcClient must not be null."); } @Test - public void tokenProvider_throws_exception_if_start_url_is_null() { + void tokenProvider_throws_exception_if_start_url_is_null() { assertThatExceptionOfType(NullPointerException.class).isThrownBy( () -> SsoOidcTokenProvider.builder().ssoOidcClient(ssoOidcClient).build()).withMessage("sessionName must not be " + "null."); diff --git a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTest.java b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTest.java index 6e1a10b4e93a..b286eb13bac8 100644 --- a/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTest.java +++ b/services/ssooidc/src/test/java/software/amazon/awssdk/services/ssooidc/internal/SsoOidcTokenTest.java @@ -26,6 +26,7 @@ public class SsoOidcTokenTest { @Test public void equalsAndHashCode_workCorrectly() { EqualsVerifier.forClass(SsoOidcToken.class) + .withIgnoredFields("providerName") .usingGetClass() .verify(); } diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java index a435d258c2d5..b7e1e3003c4d 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java @@ -26,7 +26,6 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; -import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -47,6 +46,7 @@ public final class StsAssumeRoleCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { + private static final String PROVIDER_NAME = "StsAssumeRoleCredentialsProvider"; private Supplier assumeRoleRequestSupplier; /** @@ -70,17 +70,17 @@ public static Builder builder() { protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { AssumeRoleRequest assumeRoleRequest = assumeRoleRequestSupplier.get(); Validate.notNull(assumeRoleRequest, "Assume role request must not be null."); - return toAwsSessionCredentials(stsClient.assumeRole(assumeRoleRequest).credentials()); + return toAwsSessionCredentials(stsClient.assumeRole(assumeRoleRequest).credentials(), PROVIDER_NAME); } @Override - public String toString() { - return ToString.create("StsAssumeRoleCredentialsProvider"); + public Builder toBuilder() { + return new Builder(this); } @Override - public Builder toBuilder() { - return new Builder(this); + String providerName() { + return PROVIDER_NAME; } /** diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java index 6365fa488880..761e0dcb044e 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java @@ -26,7 +26,6 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleWithSamlRequest; -import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -47,6 +46,7 @@ public final class StsAssumeRoleWithSamlCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { + private static final String PROVIDER_NAME = "StsAssumeRoleWithSamlCredentialsProvider"; private final Supplier assumeRoleWithSamlRequestSupplier; @@ -71,12 +71,7 @@ public static Builder builder() { protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { AssumeRoleWithSamlRequest assumeRoleWithSamlRequest = assumeRoleWithSamlRequestSupplier.get(); Validate.notNull(assumeRoleWithSamlRequest, "Assume role with saml request must not be null."); - return toAwsSessionCredentials(stsClient.assumeRoleWithSAML(assumeRoleWithSamlRequest).credentials()); - } - - @Override - public String toString() { - return ToString.create("StsAssumeRoleWithSamlCredentialsProvider"); + return toAwsSessionCredentials(stsClient.assumeRoleWithSAML(assumeRoleWithSamlRequest).credentials(), PROVIDER_NAME); } @Override @@ -84,6 +79,11 @@ public Builder toBuilder() { return new Builder(this); } + @Override + String providerName() { + return PROVIDER_NAME; + } + /** * A builder (created by {@link StsAssumeRoleWithSamlCredentialsProvider#builder()}) for creating a * {@link StsAssumeRoleWithSamlCredentialsProvider}. diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java index fcc10e5b2878..d5e5a20f5b97 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java @@ -27,7 +27,6 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; -import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** @@ -48,6 +47,7 @@ public final class StsAssumeRoleWithWebIdentityCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { + private static final String PROVIDER_NAME = "StsAssumeRoleWithWebIdentityCredentialsProvider"; private final Supplier assumeRoleWithWebIdentityRequest; /** @@ -71,12 +71,7 @@ public static Builder builder() { protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { AssumeRoleWithWebIdentityRequest request = assumeRoleWithWebIdentityRequest.get(); notNull(request, "AssumeRoleWithWebIdentityRequest can't be null"); - return toAwsSessionCredentials(stsClient.assumeRoleWithWebIdentity(request).credentials()); - } - - @Override - public String toString() { - return ToString.create("StsAssumeRoleWithWebIdentityCredentialsProvider"); + return toAwsSessionCredentials(stsClient.assumeRoleWithWebIdentity(request).credentials(), PROVIDER_NAME); } @Override @@ -84,6 +79,11 @@ public Builder toBuilder() { return new Builder(this); } + @Override + String providerName() { + return PROVIDER_NAME; + } + /** * A builder (created by {@link StsAssumeRoleWithWebIdentityCredentialsProvider#builder()}) for creating a * {@link StsAssumeRoleWithWebIdentityCredentialsProvider}. diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProvider.java index a1f4573c60ac..e19a6bf4b9f6 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProvider.java @@ -28,6 +28,7 @@ import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.SdkAutoCloseable; +import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -130,11 +131,18 @@ public Duration prefetchTime() { return prefetchTime; } + @Override + public String toString() { + return ToString.create(providerName()); + } + /** * Implemented by a child class to call STS and get a new set of credentials to be used by this provider. */ abstract AwsSessionCredentials getUpdatedCredentials(StsClient stsClient); + abstract String providerName(); + /** * Extended by child class's builders to share configuration across credential providers. */ diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java index 3dd91b501e8b..77c57147aea9 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java @@ -25,7 +25,6 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.GetFederationTokenRequest; -import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -46,6 +45,8 @@ public class StsGetFederationTokenCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { + private static final String PROVIDER_NAME = "StsGetFederationTokenCredentialsProvider"; + private final GetFederationTokenRequest getFederationTokenRequest; /** @@ -67,12 +68,7 @@ public static Builder builder() { @Override protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { - return toAwsSessionCredentials(stsClient.getFederationToken(getFederationTokenRequest).credentials()); - } - - @Override - public String toString() { - return ToString.create("StsGetFederationTokenCredentialsProvider"); + return toAwsSessionCredentials(stsClient.getFederationToken(getFederationTokenRequest).credentials(), PROVIDER_NAME); } @Override @@ -80,6 +76,11 @@ public Builder toBuilder() { return new Builder(this); } + @Override + String providerName() { + return PROVIDER_NAME; + } + /** * A builder (created by {@link StsGetFederationTokenCredentialsProvider#builder()}) for creating a * {@link StsGetFederationTokenCredentialsProvider}. diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java index b4b0717fb9e6..e7942671879c 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java @@ -25,7 +25,6 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.GetSessionTokenRequest; -import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -46,6 +45,8 @@ public class StsGetSessionTokenCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { + private static final String PROVIDER_NAME = "StsGetSessionTokenCredentialsProvider"; + private final GetSessionTokenRequest getSessionTokenRequest; /** @@ -67,12 +68,7 @@ public static Builder builder() { @Override protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { - return toAwsSessionCredentials(stsClient.getSessionToken(getSessionTokenRequest).credentials()); - } - - @Override - public String toString() { - return ToString.create("StsGetSessionTokenCredentialsProvider"); + return toAwsSessionCredentials(stsClient.getSessionToken(getSessionTokenRequest).credentials(), PROVIDER_NAME); } @Override @@ -80,6 +76,11 @@ public Builder toBuilder() { return new Builder(this); } + @Override + String providerName() { + return PROVIDER_NAME; + } + /** * A builder (created by {@link StsGetSessionTokenCredentialsProvider#builder()}) for creating a * {@link StsGetSessionTokenCredentialsProvider}. diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java index 703daaa992e7..c1710327fcb8 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java @@ -32,7 +32,6 @@ import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.internal.AssumeRoleWithWebIdentityRequestSupplier; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; -import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** @@ -55,6 +54,7 @@ public final class StsWebIdentityTokenFileCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { + private static final String PROVIDER_NAME = "StsWebIdentityTokenFileCredentialsProvider"; private final AwsCredentialsProvider credentialsProvider; private final RuntimeException loadException; @@ -133,16 +133,11 @@ public AwsCredentials resolveCredentials() { return credentialsProvider.resolveCredentials(); } - @Override - public String toString() { - return ToString.create("StsWebIdentityTokenFileCredentialsProvider"); - } - @Override protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { AssumeRoleWithWebIdentityRequest request = assumeRoleWithWebIdentityRequest.get(); notNull(request, "AssumeRoleWithWebIdentityRequest can't be null"); - return toAwsSessionCredentials(stsClient.assumeRoleWithWebIdentity(request).credentials()); + return toAwsSessionCredentials(stsClient.assumeRoleWithWebIdentity(request).credentials(), PROVIDER_NAME); } @Override @@ -150,6 +145,11 @@ public Builder toBuilder() { return new Builder(this); } + @Override + String providerName() { + return PROVIDER_NAME; + } + public static final class Builder extends BaseBuilder { private String roleArn; private String roleSessionName; diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsAuthUtils.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsAuthUtils.java index 1f02431e3a64..1de31d7f55ee 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsAuthUtils.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsAuthUtils.java @@ -24,12 +24,13 @@ public final class StsAuthUtils { private StsAuthUtils() { } - public static AwsSessionCredentials toAwsSessionCredentials(Credentials credentials) { + public static AwsSessionCredentials toAwsSessionCredentials(Credentials credentials, String provider) { return AwsSessionCredentials.builder() .accessKeyId(credentials.accessKeyId()) .secretAccessKey(credentials.secretAccessKey()) .sessionToken(credentials.sessionToken()) .expirationTime(credentials.expiration()) + .providerName(provider) .build(); } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java index 03a76ad3d519..2c8a4878b6aa 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java @@ -44,4 +44,9 @@ protected StsAssumeRoleCredentialsProvider.Builder createCredentialsProviderBuil protected AssumeRoleResponse callClient(StsClient client, AssumeRoleRequest request) { return client.assumeRole(request); } + + @Override + protected String providerName() { + return "StsAssumeRoleCredentialsProvider"; + } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java index efae4de54547..624f4380e670 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java @@ -47,4 +47,9 @@ protected Builder createCredentialsProviderBuilder(AssumeRoleWithSamlRequest req protected AssumeRoleWithSamlResponse callClient(StsClient client, AssumeRoleWithSamlRequest request) { return client.assumeRoleWithSAML(request); } + + @Override + protected String providerName() { + return "StsAssumeRoleWithSamlCredentialsProvider"; + } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java index d7f5510976d8..dfdcddd8fd45 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java @@ -46,4 +46,9 @@ protected Builder createCredentialsProviderBuilder(AssumeRoleWithWebIdentityRequ protected AssumeRoleWithWebIdentityResponse callClient(StsClient client, AssumeRoleWithWebIdentityRequest request) { return client.assumeRoleWithWebIdentity(request); } + + @Override + protected String providerName() { + return "StsAssumeRoleWithWebIdentityCredentialsProvider"; + } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProviderTestBase.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProviderTestBase.java index 9fae2499c603..f16f564a0a43 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProviderTestBase.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProviderTestBase.java @@ -20,10 +20,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -98,6 +96,8 @@ public void distantExpiringCredentialsUpdatedInBackground_OverridePrefetchAndSta protected abstract ResponseT callClient(StsClient client, RequestT request); + protected abstract String providerName(); + public void callClientWithCredentialsProvider(Instant credentialsExpirationDate, int numTimesInvokeCredentialsProvider, boolean overrideStaleAndPrefetchTimes) { Credentials credentials = Credentials.builder().accessKeyId("a").secretAccessKey("b").sessionToken("c").expiration(credentialsExpirationDate).build(); RequestT request = getRequest(); @@ -129,6 +129,7 @@ public void callClientWithCredentialsProvider(Instant credentialsExpirationDate, assertThat(providedCredentials.accessKeyId()).isEqualTo("a"); assertThat(providedCredentials.secretAccessKey()).isEqualTo("b"); assertThat(providedCredentials.sessionToken()).isEqualTo("c"); + assertThat(providedCredentials.providerName()).isPresent().contains(providerName()); } } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java index 2663afacf18d..9d04a3c17baf 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java @@ -46,4 +46,9 @@ protected Builder createCredentialsProviderBuilder(GetFederationTokenRequest req protected GetFederationTokenResponse callClient(StsClient client, GetFederationTokenRequest request) { return client.getFederationToken(request); } + + @Override + protected String providerName() { + return "StsGetFederationTokenCredentialsProvider"; + } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java index ee2679ec1bf6..3dd7aaac267f 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java @@ -46,4 +46,9 @@ protected Builder createCredentialsProviderBuilder(GetSessionTokenRequest reques protected GetSessionTokenResponse callClient(StsClient client, GetSessionTokenRequest request) { return client.getSessionToken(request); } + + @Override + protected String providerName() { + return "StsGetSessionTokenCredentialsProvider"; + } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java index f2498c8d3c52..eb08a86f7b6a 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java @@ -77,6 +77,11 @@ protected AssumeRoleWithWebIdentityResponse callClient(StsClient client, AssumeR return client.assumeRoleWithWebIdentity(request); } + @Override + protected String providerName() { + return "StsAssumeRoleWithWebIdentityCredentialsProvider"; + } + private String getToken(Path file) { try (InputStream webIdentityTokenStream = Files.newInputStream(file)) { return IoUtils.toUtf8String(webIdentityTokenStream); diff --git a/test/auth-tests/pom.xml b/test/auth-tests/pom.xml index 20178fad6c47..c03f087795e7 100644 --- a/test/auth-tests/pom.xml +++ b/test/auth-tests/pom.xml @@ -47,6 +47,18 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + identity-spi + ${awsjavasdk.version} + test + + + software.amazon.awssdk + http-client-spi + ${awsjavasdk.version} + test + software.amazon.awssdk sts @@ -98,6 +110,17 @@ junit-jupiter test + + org.mockito + mockito-core + test + + + software.amazon.awssdk + service-test-utils + ${awsjavasdk.version} + test + com.github.tomakehurst wiremock-jre8 diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java new file mode 100644 index 000000000000..00671ed58238 --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java @@ -0,0 +1,102 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.source; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage.HEADER_USER_AGENT; + +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.StringInputStream; + +class UserAgentProviderTest { + private static final AwsCredentials BASIC_IDENTITY = basicCredentialsBuilder().build(); + private static final AwsCredentials SESSION_IDENTITY = sessionCredentialsBuilder().build(); + + private MockSyncHttpClient mockHttpClient; + + @BeforeEach + public void setup() throws UnsupportedEncodingException { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockResponse()); + } + + public static HttpExecuteResponse mockResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + @ParameterizedTest + @MethodSource("credentialProviders") + void userAgentString_containsCredentialProviderNames_IfPresent(IdentityProvider provider, + String expected) throws Exception { + stsClient(provider, mockHttpClient).getCallerIdentity(); + + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get(HEADER_USER_AGENT); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + assertThat(userAgentHeaders.get(0)).contains(expected); + } + + private static Stream credentialProviders() { + return Stream.of( + Arguments.of(StaticCredentialsProvider.create(SESSION_IDENTITY), "stat"), + Arguments.of(StaticCredentialsProvider.create(BASIC_IDENTITY), "stat") + ); + } + + private static StsClient stsClient(IdentityProvider provider, SdkHttpClient httpClient) { + return StsClient.builder() + .credentialsProvider(provider) + .httpClient(httpClient) + .build(); + } + + private static AwsSessionCredentials.Builder sessionCredentialsBuilder() { + return AwsSessionCredentials.builder() + .accessKeyId("akid") + .secretAccessKey("secret") + .sessionToken("token"); + } + + private static AwsBasicCredentials.Builder basicCredentialsBuilder() { + return AwsBasicCredentials.builder() + .accessKeyId("akid") + .secretAccessKey("secret"); + } +}