diff --git a/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtS3V4aSigner.java b/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtS3V4aSigner.java index c6ee258c064e..794245dea8d9 100644 --- a/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtS3V4aSigner.java +++ b/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtS3V4aSigner.java @@ -38,10 +38,13 @@ *

* See * Amazon S3 Sigv4 documentation for more detailed information. + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner} from the 'http-auth-aws' module. */ @SdkPublicApi @Immutable @ThreadSafe +@Deprecated public interface AwsCrtS3V4aSigner extends Signer, Presigner { /** diff --git a/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtV4aSigner.java b/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtV4aSigner.java index 692ecec5047f..e43425d83db6 100644 --- a/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtV4aSigner.java +++ b/core/auth-crt/src/main/java/software/amazon/awssdk/authcrt/signer/AwsCrtV4aSigner.java @@ -28,10 +28,13 @@ * (Common RunTime) library. *

* In CRT signing, payload signing is the default unless an override value is specified. + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner} from the 'http-auth-aws' module. */ @SdkPublicApi @Immutable @ThreadSafe +@Deprecated public interface AwsCrtV4aSigner extends Signer, Presigner { /** diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AsyncAws4Signer.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AsyncAws4Signer.java index 5a175bcaf4b4..eb5a6b4339db 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AsyncAws4Signer.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AsyncAws4Signer.java @@ -34,7 +34,10 @@ /** * AWS Signature Version 4 signer that can include contents of an asynchronous request body into the signature * calculation. + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner} from the 'http-auth-aws' module. */ +@Deprecated @SdkPublicApi public final class AsyncAws4Signer extends BaseAws4Signer implements AsyncSigner { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4Signer.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4Signer.java index bad5428c3a8c..7c37904ecfe1 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4Signer.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4Signer.java @@ -20,7 +20,10 @@ /** * Signer implementation that signs requests with the AWS4 signing protocol. + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner} from the 'http-auth-aws' module. */ +@Deprecated @SdkPublicApi public final class Aws4Signer extends BaseAws4Signer { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4UnsignedPayloadSigner.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4UnsignedPayloadSigner.java index 5b49c694dc3a..9773d81d7645 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4UnsignedPayloadSigner.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/Aws4UnsignedPayloadSigner.java @@ -32,8 +32,11 @@ *

* Payloads are still signed for requests over HTTP to preserve the request * integrity over a non-secure transport. + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner} from the 'http-auth-aws' module. */ @SdkPublicApi +@Deprecated public final class Aws4UnsignedPayloadSigner extends BaseAws4Signer { public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsS3V4Signer.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsS3V4Signer.java index eb0a93c123f4..9df0b58b9302 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsS3V4Signer.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/AwsS3V4Signer.java @@ -20,7 +20,10 @@ /** * AWS4 signer implementation for AWS S3 + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner} from the 'http-auth-aws' module. */ +@Deprecated @SdkPublicApi public final class AwsS3V4Signer extends AbstractAwsS3V4Signer { private AwsS3V4Signer() { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/EventStreamAws4Signer.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/EventStreamAws4Signer.java index 474046cf2202..8776f4b5dc0b 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/EventStreamAws4Signer.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/EventStreamAws4Signer.java @@ -18,6 +18,7 @@ import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.auth.signer.internal.BaseEventStreamAsyncAws4Signer; +@Deprecated @SdkProtectedApi public final class EventStreamAws4Signer extends BaseEventStreamAsyncAws4Signer { private EventStreamAws4Signer() { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/SignerLoader.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/SignerLoader.java index add5a3e83208..94781f69b90f 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/SignerLoader.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/SignerLoader.java @@ -26,6 +26,7 @@ /** * Utility class for instantiating signers only if they're available on the class path. */ +@Deprecated @SdkProtectedApi public final class SignerLoader { diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/BaseAws4Signer.java b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/BaseAws4Signer.java index 6ae237487578..08652ec79134 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/BaseAws4Signer.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/BaseAws4Signer.java @@ -27,6 +27,7 @@ * Abstract base class for concrete implementations of Aws4 signers. */ @SdkInternalApi +@Deprecated public abstract class BaseAws4Signer extends AbstractAws4Signer { @Override diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java b/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java index f150ac057fe1..9d8e6040ccdd 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/token/signer/aws/BearerTokenSigner.java @@ -27,8 +27,11 @@ /** * A {@link Signer} that will sign a request with Bearer token authorization. + * + * @deprecated Use {@code software.amazon.awssdk.http.auth.signer.BearerHttpSigner} from the 'http-auth' module. */ @SdkPublicApi +@Deprecated public final class BearerTokenSigner implements Signer { private static final String BEARER_LABEL = "Bearer"; diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java index dd6ebdf72760..5041047cb509 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4FamilyHttpSigner.java @@ -29,7 +29,8 @@ @SdkPublicApi public interface AwsV4FamilyHttpSigner extends HttpSigner { /** - * The name of the AWS service. This property is required. + * The name of the AWS service. This property is required. This value can be found in the service documentation or + * on the service client itself (e.g. {@code S3Client.SERVICE_NAME}). */ SignerProperty SERVICE_SIGNING_NAME = SignerProperty.create(AwsV4FamilyHttpSigner.class, "ServiceSigningName"); @@ -37,6 +38,8 @@ public interface AwsV4FamilyHttpSigner extends HttpSigner /** * A boolean to indicate whether to double url-encode the resource path when constructing the canonical request. This property * defaults to true. + *

+ * Note: S3 requires this value to be set to 'false' to prevent signature mismatch errors for certain paths. */ SignerProperty DOUBLE_URL_ENCODE = SignerProperty.create(AwsV4FamilyHttpSigner.class, "DoubleUrlEncode"); @@ -44,6 +47,8 @@ public interface AwsV4FamilyHttpSigner extends HttpSigner /** * A boolean to indicate whether the resource path should be "normalized" according to RFC3986 when constructing the canonical * request. This property defaults to true. + *

+ * Note: S3 requires this value to be set to 'false' to prevent signature mismatch errors for certain paths. */ SignerProperty NORMALIZE_PATH = SignerProperty.create(AwsV4FamilyHttpSigner.class, "NormalizePath"); @@ -64,13 +69,21 @@ public interface AwsV4FamilyHttpSigner extends HttpSigner /** * Whether to indicate that a payload is signed or not. This property defaults to true. This can be set false to disable * payload signing. + *

+ * When this value is true and {@link #CHUNK_ENCODING_ENABLED} is false, the whole payload must be read to generate + * the payload signature. For very large payloads, this could impact memory usage and call latency. Some services + * support this value being disabled, especially over HTTPS where SSL provides some of its own protections against + * payload tampering. */ SignerProperty PAYLOAD_SIGNING_ENABLED = SignerProperty.create(AwsV4FamilyHttpSigner.class, "PayloadSigningEnabled"); /** * Whether to indicate that a payload is chunk-encoded or not. This property defaults to false. This can be set true to - * enable the `aws-chunk` content-encoding + * enable the `aws-chunked` content-encoding. + *

+ * Only some services support this value being set to true, but for those services it can prevent the need to read + * the whole payload before writing when {@link #PAYLOAD_SIGNING_ENABLED} is true. */ SignerProperty CHUNK_ENCODING_ENABLED = SignerProperty.create(AwsV4FamilyHttpSigner.class, "ChunkEncodingEnabled"); diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java index 4b85c017c0f5..ee3656f946e4 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4HttpSigner.java @@ -22,10 +22,67 @@ import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; /** - * An {@link HttpSigner} that will sign a request using an AWS credentials {@link AwsCredentialsIdentity}). + * An {@link HttpSigner} that will use the AWS V4 signing algorithm to sign a request using an + * {@link AwsCredentialsIdentity}). + * *

- * The process for signing requests to send to AWS services is documented + * The steps performed by this signer are documented * here. + * + *

Using the AwsV4HttpSigner

+ *

+ * Sign an HTTP request and send it to a service. + *

+ * {@snippet : + * AwsV4HttpSigner signer = AwsV4HttpSigner.create(); + * + * // Specify AWS credentials. Credential providers that are used by the SDK by default are + * // available in the module "auth" (e.g. DefaultCredentialsProvider). + * AwsCredentialsIdentity credentials = + * AwsSessionCredentialsIdentity.create("skid", "akid", "stok"); + * + * // Create the HTTP request to be signed + * SdkHttpRequest httpRequest = + * SdkHttpRequest.builder() + * .uri("https://s3.us-west-2.amazonaws.com/bucket/object") + * .method(SdkHttpMethod.PUT) + * .putHeader("Content-Type", "text/plain") + * .build(); + * + * // Create the request payload to be signed + * ContentStreamProvider requestPayload = + * ContentStreamProvider.fromUtf8String("Hello, World!"); + * + * // Sign the request. Some services require custom signing configuration properties (e.g. S3). + * // See AwsV4HttpSigner and AwsV4FamilyHttpSigner for the available signing options. + * // Note: The S3Client class below requires a dependency on the 's3' module. Alternatively, the + * // signing name can be hard-coded because it is guaranteed to not change. + * SignedRequest signedRequest = + * signer.sign(r -> r.identity(credentials) + * .request(httpRequest) + * .payload(requestPayload) + * .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, S3Client.SERVICE_NAME) + * .putProperty(AwsV4HttpSigner.REGION_NAME, "us-west-2") + * .putProperty(AwsV4HttpSigner.DOUBLE_URL_ENCODE, false) // Required for S3 only + * .putProperty(AwsV4HttpSigner.NORMALIZE_PATH, false)); // Required for S3 only + * + * // Create and HTTP client and send the request. ApacheHttpClient requires the 'apache-client' module. + * try (SdkHttpClient httpClient = ApacheHttpClient.create()) { + * HttpExecuteRequest httpExecuteRequest = + * HttpExecuteRequest.builder() + * .request(signedRequest.request()) + * .contentStreamProvider(signedRequest.payload().orElse(null)) + * .build(); + * + * HttpExecuteResponse httpResponse = + * httpClient.prepareRequest(httpExecuteRequest).call(); + * + * System.out.println("HTTP Status Code: " + httpResponse.httpResponse().statusCode()); + * } catch (IOException e) { + * System.err.println("HTTP Request Failed."); + * e.printStackTrace(); + * } + * } */ @SdkPublicApi public interface AwsV4HttpSigner extends AwsV4FamilyHttpSigner { diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java index 686ac70645aa..503d2de7e0ef 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/signer/AwsV4aHttpSigner.java @@ -23,10 +23,67 @@ import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; /** - * An {@link HttpSigner} that will sign a request using an AWS credentials {@link AwsCredentialsIdentity}). + * An {@link HttpSigner} that will use the AWS V4a signing algorithm to sign a request using an + * {@link AwsCredentialsIdentity}). + * *

- * The process for signing requests to send to AWS services is documented + * AWS request signing is described * here. + * + *

Using the AwsV4aHttpSigner

+ *

+ * Sign an HTTP request and send it to a service. + *

+ * {@snippet : + * AwsV4aHttpSigner signer = AwsV4aHttpSigner.create(); + * + * // Specify AWS credentials. Credential providers that are used by the SDK by default are + * // available in the module "auth" (e.g. DefaultCredentialsProvider). + * AwsCredentialsIdentity credentials = + * AwsSessionCredentialsIdentity.create("skid", "akid", "stok"); + * + * // Create the HTTP request to be signed + * SdkHttpRequest httpRequest = + * SdkHttpRequest.builder() + * .uri("https://s3.us-west-2.amazonaws.com/bucket/object") + * .method(SdkHttpMethod.PUT) + * .putHeader("Content-Type", "text/plain") + * .build(); + * + * // Create the request payload to be signed + * ContentStreamProvider requestPayload = + * ContentStreamProvider.fromUtf8String("Hello, World!"); + * + * // Sign the request. Some services require custom signing configuration properties (e.g. S3). + * // See AwsV4aHttpSigner and AwsV4FamilyHttpSigner for the available signing options. + * // Note: The S3Client class below requires a dependency on the 's3' module. Alternatively, the + * // signing name can be hard-coded because it is guaranteed to not change. + * SignedRequest signedRequest = + * signer.sign(r -> r.identity(credentials) + * .request(httpRequest) + * .payload(requestPayload) + * .putProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, S3Client.SERVICE_NAME) + * .putProperty(AwsV4aHttpSigner.REGION_SET, RegionSet.create("us-west-2")) + * .putProperty(AwsV4aHttpSigner.DOUBLE_URL_ENCODE, false) // Required for S3 only + * .putProperty(AwsV4aHttpSigner.NORMALIZE_PATH, false)); // Required for S3 only + * + * // Create and HTTP client and send the request. ApacheHttpClient requires the 'apache-client' module. + * try (SdkHttpClient httpClient = ApacheHttpClient.create()) { + * HttpExecuteRequest httpExecuteRequest = + * HttpExecuteRequest.builder() + * .request(signedRequest.request()) + * .contentStreamProvider(signedRequest.payload().orElse(null)) + * .build(); + * + * HttpExecuteResponse httpResponse = + * httpClient.prepareRequest(httpExecuteRequest).call(); + * + * System.out.println("HTTP Status Code: " + httpResponse.httpResponse().statusCode()); + * } catch (IOException e) { + * System.err.println("HTTP Request Failed."); + * e.printStackTrace(); + * } + * } */ @SdkPublicApi public interface AwsV4aHttpSigner extends AwsV4FamilyHttpSigner { @@ -41,5 +98,4 @@ public interface AwsV4aHttpSigner extends AwsV4FamilyHttpSigner implements HttpSigner { + @Override + public SignedRequest sign(SignRequest request) { + return SignedRequest.builder() + .request(request.request()) + .payload(request.payload().orElse(null)) + .build(); + } + + @Override + public CompletableFuture signAsync(AsyncSignRequest request) { + return CompletableFuture.completedFuture(AsyncSignedRequest.builder() + .request(request.request()) + .payload(request.payload().orElse(null)) + .build()); + } +} diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java index 5aafbe51e116..0f60bae1d7e0 100644 --- a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java +++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/HttpSigner.java @@ -21,6 +21,7 @@ import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultAsyncSignRequest; import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultSignRequest; +import software.amazon.awssdk.http.auth.spi.internal.signer.NoOpHttpSigner; import software.amazon.awssdk.identity.spi.Identity; /** @@ -31,7 +32,6 @@ */ @SdkPublicApi public interface HttpSigner { - /** * A {@link Clock} to be used to derive the signing time. This property defaults to the system clock. * @@ -39,6 +39,13 @@ public interface HttpSigner { */ SignerProperty SIGNING_CLOCK = SignerProperty.create(HttpSigner.class, "SigningClock"); + /** + * Retrieve a signer that returns the input message, without signing. + */ + default HttpSigner doNotSign() { + return new NoOpHttpSigner<>(); + } + /** * Method that takes in inputs to sign a request with sync payload and returns a signed version of the request. * @@ -84,4 +91,5 @@ default SignedRequest sign(Consumer> consumer) { default CompletableFuture signAsync(Consumer> consumer) { return signAsync(DefaultAsyncSignRequest.builder().applyMutation(consumer).build()); } + } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncRequestBodySigner.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncRequestBodySigner.java index fca744c562de..e0ef03e70b03 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncRequestBodySigner.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncRequestBodySigner.java @@ -22,9 +22,12 @@ /** * Interface for the signer used for signing the async requests. + * + * @deprecated Replaced by {@code software.amazon.awssdk.http.auth.spi.signer.HttpSigner} in 'http-auth-spi'. */ @SdkPublicApi @FunctionalInterface +@Deprecated public interface AsyncRequestBodySigner { /** * Method that takes in an signed request and async request body provider, diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncSigner.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncSigner.java index ee31dfffbe91..e901242db74f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncSigner.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/AsyncSigner.java @@ -23,8 +23,11 @@ /** * A signer capable of including the contents of the asynchronous body into the request calculation. + * + * @deprecated Replaced by {@code software.amazon.awssdk.http.auth.spi.signer.HttpSigner} in 'http-auth-spi'. */ @SdkPublicApi +@Deprecated public interface AsyncSigner { /** * Sign the request, including the contents of the body into the signature calculation. diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/NoOpSigner.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/NoOpSigner.java index e6f2f96dd1d6..ae0576920998 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/NoOpSigner.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/NoOpSigner.java @@ -22,8 +22,12 @@ /** * A No op implementation of Signer and Presigner interfaces that returns the * input {@link SdkHttpFullRequest} without modifications. + * + * @deprecated Replaced by {@code software.amazon.awssdk.http.auth.spi.signer.HttpSigner#doNotSign()} in + * 'http-auth-spi'. */ @SdkPublicApi +@Deprecated public final class NoOpSigner implements Signer, Presigner { @Override diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Presigner.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Presigner.java index f3eeda10be93..025e86dcf4d5 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Presigner.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Presigner.java @@ -22,9 +22,12 @@ /** * Interface for the signer used for pre-signing the requests. All SDK signer implementations that support pre-signing * will implement this interface. + * + * @deprecated Replaced by {@code software.amazon.awssdk.http.auth.spi.signer.HttpSigner} in 'http-auth-spi'. */ @SdkPublicApi @FunctionalInterface +@Deprecated public interface Presigner { /** * Method that takes in an request and returns a pre signed version of the request. diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Signer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Signer.java index c44adaa37089..e43b420d908c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Signer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/signer/Signer.java @@ -22,9 +22,12 @@ /** * Interface for the signer used for signing the requests. All SDK signer implementations will implement this interface. + * + * @deprecated Replaced by {@code software.amazon.awssdk.http.auth.spi.signer.HttpSigner} in 'http-auth-spi'. */ @SdkPublicApi @FunctionalInterface +@Deprecated public interface Signer { /** * Method that takes in an request and returns a signed version of the request. diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java index cbb63dc2faf2..59493fd33e4d 100644 --- a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java +++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java @@ -15,21 +15,119 @@ package software.amazon.awssdk.http; +import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; + +import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.StringInputStream; +import software.amazon.awssdk.utils.Validate; /** * Provides the content stream of a request. *

- * Each call to to the {@link #newStream()} method must result in a stream whose position is at the beginning of the content. + * Each call to the {@link #newStream()} method must result in a stream whose position is at the beginning of the content. * Implementations may return a new stream or the same stream for each call. If returning a new stream, the implementation * must ensure to {@code close()} and free any resources acquired by the previous stream. The last stream returned by {@link * #newStream()}} will be closed by the SDK. - * */ @SdkPublicApi @FunctionalInterface public interface ContentStreamProvider { + /** + * Create {@link ContentStreamProvider} from a byte array. This will copy the contents of the byte array. + */ + static ContentStreamProvider fromByteArray(byte[] bytes) { + Validate.paramNotNull(bytes, "bytes"); + byte[] copy = Arrays.copyOf(bytes, bytes.length); + return () -> new ByteArrayInputStream(copy); + } + + /** + * Create {@link ContentStreamProvider} from a byte array without copying the contents of the byte array. + * This introduces concurrency risks, allowing the caller to modify the byte array stored in this + * {@code ContentStreamProvider} implementation. + * + *

As the method name implies, this is unsafe. Use {@link #fromByteArray(byte[])} unless you're sure you know + * the risks. + */ + static ContentStreamProvider fromByteArrayUnsafe(byte[] bytes) { + Validate.paramNotNull(bytes, "bytes"); + return () -> new ByteArrayInputStream(bytes); + } + + /** + * Create {@link ContentStreamProvider} from a string, using the provided charset. + */ + static ContentStreamProvider fromString(String string, Charset charset) { + Validate.paramNotNull(string, "string"); + Validate.paramNotNull(charset, "charset"); + return () -> new StringInputStream(string, charset); + } + + /** + * Create {@link ContentStreamProvider} from a string, using the UTF-8 charset. + */ + static ContentStreamProvider fromUtf8String(String string) { + return fromString(string, StandardCharsets.UTF_8); + } + + /** + * Create a {@link ContentStreamProvider} from an input stream. + *

+ * If the provided input stream supports mark/reset, the stream will be marked with a 128Kb read limit and reset + * each time {@link #newStream()} is invoked. If the provided input stream does not support mark/reset, + * {@link #newStream()} will return the provided stream once, but fail subsequent calls. To create new streams when + * needed instead of using mark/reset, see {@link #fromInputStreamSupplier(Supplier)}. + */ + static ContentStreamProvider fromInputStream(InputStream inputStream) { + Validate.paramNotNull(inputStream, "inputStream"); + IoUtils.markStreamWithMaxReadLimit(inputStream); + return new ContentStreamProvider() { + private boolean first = true; + @Override + public InputStream newStream() { + if (first) { + first = false; + return inputStream; + } + + if (inputStream.markSupported()) { + invokeSafely(inputStream::reset); + return inputStream; + } + + throw new IllegalStateException("Content input stream does not support mark/reset, " + + "and was already read once."); + } + }; + } + + /** + * Create {@link ContentStreamProvider} from an input stream supplier. Each time a new stream is retrieved from + * this content stream provider, the last one returned will be closed. + */ + static ContentStreamProvider fromInputStreamSupplier(Supplier inputStreamSupplier) { + Validate.paramNotNull(inputStreamSupplier, "inputStreamSupplier"); + return new ContentStreamProvider() { + private InputStream lastStream; + + @Override + public InputStream newStream() { + if (lastStream != null) { + invokeSafely(lastStream::close); + } + lastStream = inputStreamSupplier.get(); + return lastStream; + } + }; + } + /** * @return The content stream. */ diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpRequest.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpRequest.java index d011f9d7b19a..bb34909b5f36 100644 --- a/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpRequest.java +++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpRequest.java @@ -191,6 +191,17 @@ default Builder uri(URI uri) { return builder; } + /** + * Convenience method to set the {@link #protocol()}, {@link #host()}, {@link #port()}, + * {@link #encodedPath()} and extracts query parameters from a URI string. + * + * @param uri URI containing protocol, host, port and path. + * @return This builder for method chaining. + */ + default Builder uri(String uri) { + return uri(URI.create(uri)); + } + /** * The protocol, exactly as it was configured with {@link #protocol(String)}. */ diff --git a/http-client-spi/src/test/java/software/amazon/awssdk/http/ContentStreamProviderTest.java b/http-client-spi/src/test/java/software/amazon/awssdk/http/ContentStreamProviderTest.java new file mode 100644 index 000000000000..1298360f753b --- /dev/null +++ b/http-client-spi/src/test/java/software/amazon/awssdk/http/ContentStreamProviderTest.java @@ -0,0 +1,188 @@ +/* + * 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.http; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.StringInputStream; + +class ContentStreamProviderTest { + @Test + void fromMethods_failOnNull() { + assertThatThrownBy(() -> ContentStreamProvider.fromByteArray(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ContentStreamProvider.fromByteArrayUnsafe(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ContentStreamProvider.fromString("foo", null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ContentStreamProvider.fromString(null, StandardCharsets.UTF_8)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ContentStreamProvider.fromUtf8String(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ContentStreamProvider.fromInputStream(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ContentStreamProvider.fromInputStreamSupplier(null)).isInstanceOf(NullPointerException.class); + } + + @Test + void fromByteArray_containsInputBytes() throws IOException { + byte[] bytes = "foo".getBytes(); + ContentStreamProvider provider = ContentStreamProvider.fromByteArray(bytes); + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + } + + @Test + void fromByteArray_doesNotAllowModifyingInputBytes() throws IOException { + byte[] bytes = "foo".getBytes(); + byte[] bytesCopy = Arrays.copyOf(bytes, bytes.length); + + ContentStreamProvider provider = ContentStreamProvider.fromByteArray(bytes); + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + bytes[0] = 'b'; + assertArrayEquals(bytesCopy, IoUtils.toByteArray(provider.newStream())); + } + + @Test + void fromByteArrayUnsafe_containsInputBytes() throws IOException { + byte[] bytes = "foo".getBytes(); + ContentStreamProvider provider = ContentStreamProvider.fromByteArray(bytes); + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + } + + @Test + void fromByteArrayUnsafe_doesNotProtectAgainstModifyingInputBytes() throws IOException { + byte[] bytes = "foo".getBytes(); + + ContentStreamProvider provider = ContentStreamProvider.fromByteArrayUnsafe(bytes); + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + bytes[0] = 'b'; + assertArrayEquals(bytes, IoUtils.toByteArray(provider.newStream())); + } + + @Test + void fromString_containsInputBytes() throws IOException { + String str = "foo"; + ContentStreamProvider provider = ContentStreamProvider.fromString(str, StandardCharsets.UTF_8); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + } + + @Test + void fromString_honorsEncoding() throws IOException { + String str = "\uD83D\uDE0A"; + ContentStreamProvider asciiProvider = ContentStreamProvider.fromString(str, StandardCharsets.US_ASCII); + ContentStreamProvider utf8Provider = ContentStreamProvider.fromString(str, StandardCharsets.UTF_8); + assertArrayEquals("?".getBytes(StandardCharsets.US_ASCII), IoUtils.toByteArray(asciiProvider.newStream())); + assertArrayEquals("\uD83D\uDE0A".getBytes(StandardCharsets.UTF_8), IoUtils.toByteArray(utf8Provider.newStream())); + } + + @Test + void fromUtf8String_containsInputBytes() throws IOException { + String str = "foo"; + ContentStreamProvider provider = ContentStreamProvider.fromUtf8String(str); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + } + + @Test + void fromInputStream_containsInputBytes() throws IOException { + String str = "foo"; + ContentStreamProvider provider = ContentStreamProvider.fromInputStream(new StringInputStream(str)); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + } + + @Test + void fromInputStream_failsOnSecondNewStream_ifInputStreamDoesNotSupportMarkAndReset() { + InputStream stream = new InputStream() { + @Override + public int read() { + return 0; + } + }; + + ContentStreamProvider provider = ContentStreamProvider.fromInputStream(stream); + provider.newStream(); + assertThatThrownBy(provider::newStream).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("reset"); + } + + @Test + void fromInputStream_marksStream() throws IOException { + InputStream stream = Mockito.mock(InputStream.class); + Mockito.when(stream.markSupported()).thenReturn(true); + + ContentStreamProvider provider = ContentStreamProvider.fromInputStream(stream); + provider.newStream().read(); + Mockito.verify(stream, Mockito.atLeastOnce()).mark(Mockito.anyInt()); + } + + @Test + void fromInputStream_doesNotCloseProvidedResettableStream() throws IOException { + InputStream stream = Mockito.mock(InputStream.class); + Mockito.when(stream.markSupported()).thenReturn(true); + + ContentStreamProvider provider = ContentStreamProvider.fromInputStream(stream); + provider.newStream().read(); + provider.newStream().read(); + Mockito.verify(stream, Mockito.never()).close(); + } + + @Test + void fromInputStream_doesNotCloseProvidedSingleUseStream() throws IOException { + InputStream stream = Mockito.mock(InputStream.class); + Mockito.when(stream.markSupported()).thenReturn(false); + + ContentStreamProvider provider = ContentStreamProvider.fromInputStream(stream); + provider.newStream().read(); + Mockito.verify(stream, Mockito.never()).close(); + } + + @Test + void fromInputStreamSupplier_containsInputBytes() throws IOException { + String str = "foo"; + ContentStreamProvider provider = ContentStreamProvider.fromInputStreamSupplier(() -> new StringInputStream(str)); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + assertEquals(str, IoUtils.toUtf8String(provider.newStream())); + } + + @Test + void fromInputStreamSupplier_closesStreams() throws IOException { + InputStream stream1 = Mockito.mock(InputStream.class); + InputStream stream2 = Mockito.mock(InputStream.class); + Supplier streamSupplier = Mockito.mock(Supplier.class); + + Mockito.when(streamSupplier.get()).thenReturn(stream1, stream2); + + ContentStreamProvider provider = ContentStreamProvider.fromInputStreamSupplier(streamSupplier); + + provider.newStream(); + + Mockito.verify(stream1, Mockito.never()).close(); + Mockito.verify(stream2, Mockito.never()).close(); + + provider.newStream(); + + Mockito.verify(stream1, Mockito.times(1)).close(); + Mockito.verify(stream2, Mockito.never()).close(); + } +} \ No newline at end of file diff --git a/utils/src/main/java/software/amazon/awssdk/utils/StringInputStream.java b/utils/src/main/java/software/amazon/awssdk/utils/StringInputStream.java index f0d595c681e3..36da50fbce5d 100644 --- a/utils/src/main/java/software/amazon/awssdk/utils/StringInputStream.java +++ b/utils/src/main/java/software/amazon/awssdk/utils/StringInputStream.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.utils; import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import software.amazon.awssdk.annotations.SdkProtectedApi; @@ -29,7 +30,11 @@ public class StringInputStream extends ByteArrayInputStream { private final String string; public StringInputStream(String s) { - super(s.getBytes(StandardCharsets.UTF_8)); + this(s, StandardCharsets.UTF_8); + } + + public StringInputStream(String s, Charset charset) { + super(s.getBytes(charset)); this.string = s; }