diff --git a/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.swift b/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.swift index 6592a5e2aa9..4df93ffa54a 100644 --- a/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.swift +++ b/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.swift @@ -135,7 +135,11 @@ func addIntegrationTestTarget(_ name: String) { "Resources/ECSIntegTestApp/" ] case "AWSS3": - additionalDependencies = ["AWSSSOAdmin"] + additionalDependencies = ["AWSSSOAdmin", "AWSS3Control", "AWSSTS"] + case "AWSEventBridge": + additionalDependencies = ["AWSRoute53"] + case "AWSCloudFrontKeyValueStore": + additionalDependencies = ["AWSCloudFront"] default: break } diff --git a/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift b/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift new file mode 100644 index 00000000000..2247a12b261 --- /dev/null +++ b/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift @@ -0,0 +1,89 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import ClientRuntime +import AWSClientRuntime +import AWSCloudFront +import AWSCloudFrontKeyValueStore + +/// Tests SigV4a signing flow using CloudFrontKeyValueStore. +class CloudFrontKeyValueStoreSigV4ATests: XCTestCase { + // The CloudFront client to create / delete key value store (KVS) + private var client: CloudFrontClient! + // The sig4a-only KVS client to call CloudFrontKeyValueStore::listKeys + private var kvsClient: CloudFrontKeyValueStoreClient! + private var kvsConfig: CloudFrontKeyValueStoreClient.CloudFrontKeyValueStoreClientConfiguration! + // Region to use for clients + private let region = "us-east-1" + + // Temporary name of the KVS to use for the test + private let kvsName = "sigv4a-test-kvs-" + UUID().uuidString.split(separator: "-").first!.lowercased() + + // The Etag to use to call CloudFront::deletKeyValueStore + private var cfEtag: String! + // The Etag to use to call CloudFrontKeyValueStore::putKey + private var cfKvsEtag: String! + + // The ARN of the KVS + private var kvsArn: String! + // String status of the KVS while it's being created + private let wipStatus = "PROVISIONING" + + // Key-value pair to pass into CloudFrontKeyValueStore::putKey + private let key = "kvs-sigv4a-integration-test" + private let value = "{}" + + private let NSEC_PER_SEC = 1_000_000_000 + + override func setUp() async throws { + // Initialize CloudFront client + client = try CloudFrontClient(region: region) + // Initiailize KVS client with only SigV4A enabled + kvsConfig = try await CloudFrontKeyValueStoreClient.CloudFrontKeyValueStoreClientConfiguration(region: region) + kvsConfig.authSchemes = [SigV4AAuthScheme()] + kvsClient = CloudFrontKeyValueStoreClient(config: kvsConfig) + + // Create a key value store (KVS) and save its ARN + kvsArn = try await client.createKeyValueStore(input: CreateKeyValueStoreInput(name: kvsName)).keyValueStore?.arn + + // Wait until KVS is provisioned & ready + var status: String? = wipStatus + repeat { + status = try await client.describeKeyValueStore(input: DescribeKeyValueStoreInput(name: kvsName)).keyValueStore?.status + let seconds = 20.0 + try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) + } while status == wipStatus + + // Fetch Etag of the KVS that was just created for both CF and CFKVS clients + cfEtag = try await client.describeKeyValueStore(input: DescribeKeyValueStoreInput(name: kvsName)).eTag + cfKvsEtag = try await kvsClient.describeKeyValueStore(input: DescribeKeyValueStoreInput(kvsARN: kvsArn)).eTag + } + + override func tearDown() async throws { + // Delete the key value store + _ = try await client.deleteKeyValueStore(input: DeleteKeyValueStoreInput( + ifMatch: cfEtag, + name: kvsName + )) + } + + func testKeyValueStoreSigV4A() async throws { + // Put a dummy key onto KVS + _ = try await kvsClient.putKey(input: PutKeyInput( + ifMatch: cfKvsEtag, + key: key, + kvsARN: kvsArn, + value: value + )) + // Confirm that the key was uploaded successfully using SigV4A signing flow + let keys = try await kvsClient.listKeys(input: ListKeysInput(kvsARN: kvsArn)) + let items = try XCTUnwrap(keys.items) + XCTAssertEqual(items[0].key, key) + XCTAssertEqual(items[0].value, value) + } +} diff --git a/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift b/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift new file mode 100644 index 00000000000..dbf5b763634 --- /dev/null +++ b/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift @@ -0,0 +1,121 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import AWSEventBridge +import ClientRuntime +import AWSClientRuntime +import AWSRoute53 + +/// Tests SigV4a signing flow using EventBridge's global endpoint. +class EventBridgeSigV4ATests: XCTestCase { + // The custom event bridge client with only sigv4a auth scheme configured (w/o SigV4) + private var sigv4aEventBridgeClient: EventBridgeClient! + // The primary event bridge client used to create an event bus in primary region + private var primaryRegionEventBridgeClient: EventBridgeClient! + // The secondary event bridge client used to create an event bus in secondary region + private var secondaryRegionEventBridgeClient: EventBridgeClient! + // The Route 53 client used to create a healthcheck, a parameter to EventBridge::createEndpoint + private var route53Client: Route53Client! + + private var eventBridgeConfig: EventBridgeClient.EventBridgeClientConfiguration! + private let primaryRegion = "us-west-2" + private let secondaryRegion = "us-east-1" + + // Name for the EventBridge global endpoint + private let endpointName = "sigv4a-test-global-endpoint" + private let eventBusName = "sigv4a-integ-test-eventbus" + private var endpointId: String! + + private var healthCheckId: String! + private let route53HealthCheckArnPrefix = "arn:aws:route53:::healthcheck/" + + private let NSEC_PER_SEC = 1_000_000_000 + + override func setUp() async throws { + // Create the clients + primaryRegionEventBridgeClient = try EventBridgeClient(region: primaryRegion) + secondaryRegionEventBridgeClient = try EventBridgeClient(region: secondaryRegion) + + eventBridgeConfig = try await EventBridgeClient.EventBridgeClientConfiguration(region: primaryRegion) + eventBridgeConfig.authSchemes = [SigV4AAuthScheme()] + sigv4aEventBridgeClient = EventBridgeClient(config: eventBridgeConfig) + + route53Client = try Route53Client(region: secondaryRegion) + + // Create two event buses with identical names but in two different regions for the global endpoint + let eventBusArn1 = try await primaryRegionEventBridgeClient.createEventBus(input: CreateEventBusInput(name: eventBusName)).eventBusArn + let eventBusArn2 = try await secondaryRegionEventBridgeClient.createEventBus(input: CreateEventBusInput(name: eventBusName)).eventBusArn + + // Create Route 53 Healthcheck + let healthCheckConfig = Route53ClientTypes.HealthCheckConfig( + fullyQualifiedDomainName: "www.amazon.com", + type: .https + ) + let createHealthCheckInput = CreateHealthCheckInput( + callerReference: UUID().uuidString.split(separator: "-").first!.lowercased(), + healthCheckConfig: healthCheckConfig + ) + let healthCheck = try await route53Client.createHealthCheck(input: createHealthCheckInput) + healthCheckId = (healthCheck.healthCheck?.id)! + let healthCheckArn = route53HealthCheckArnPrefix + healthCheckId + + // Construct routingConfig object to use for global endpoint creation + let primary = EventBridgeClientTypes.Primary(healthCheck: healthCheckArn) + let secondary = EventBridgeClientTypes.Secondary(route: secondaryRegion) + let failoverConfig = EventBridgeClientTypes.FailoverConfig(primary: primary, secondary: secondary) + let routingConfig = EventBridgeClientTypes.RoutingConfig(failoverConfig: failoverConfig) + + // Construct replicationConfig object to use for global endpoint creation + let replicationState = EventBridgeClientTypes.ReplicationState.disabled + let replicationConfig = EventBridgeClientTypes.ReplicationConfig(state: replicationState) + + // Create the global endpoint with the two endpoint event buses and the routing config (healthcheck). + let endpointEventBus1 = EventBridgeClientTypes.EndpointEventBus(eventBusArn: eventBusArn1) + let endpointEventBus2 = EventBridgeClientTypes.EndpointEventBus(eventBusArn: eventBusArn2) + _ = try await primaryRegionEventBridgeClient.createEndpoint(input: CreateEndpointInput( + eventBuses: [endpointEventBus1, endpointEventBus2], + name: endpointName, + replicationConfig: replicationConfig, + routingConfig: routingConfig + )) + + // Pause program execution briefly. + // This is needed bc it takes some time for newly created global endpoint to configure itself + let seconds = 20.0 + try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) + + endpointId = try await primaryRegionEventBridgeClient.describeEndpoint(input: DescribeEndpointInput(name: endpointName)).endpointId + } + + override func tearDown() async throws { + // Delete the endpoint + _ = try await primaryRegionEventBridgeClient.deleteEndpoint(input: DeleteEndpointInput(name: endpointName)) + // Delete the event buses + _ = try await primaryRegionEventBridgeClient.deleteEventBus(input: DeleteEventBusInput(name: eventBusName)) + _ = try await secondaryRegionEventBridgeClient.deleteEventBus(input: DeleteEventBusInput(name: eventBusName)) + // Delete the Route 53 Healthcheck + _ = try await route53Client.deleteHealthCheck(input: DeleteHealthCheckInput(healthCheckId: healthCheckId)) + } + + func testEventBridgeSigV4A() async throws { + // Call putEvents with EventBridge client that only has SigV4a auth scheme configured + let event = EventBridgeClientTypes.PutEventsRequestEntry( + detail: "{}", + detailType: "test", + eventBusName: eventBusName, + source: "test" + ) + let response = try await sigv4aEventBridgeClient.putEvents(input: PutEventsInput( + endpointId: endpointId, + entries: [event] + )) + // Confirm that returned response has 0 failed entries + let count = response.failedEntryCount + XCTAssertEqual(count, 0) + } +} diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedRequestTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedRequestTests.swift new file mode 100644 index 00000000000..febc25fe761 --- /dev/null +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedRequestTests.swift @@ -0,0 +1,51 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import AWSS3 +import ClientRuntime +import AWSClientRuntime + +/// Tests presigned request using S3. +class S3PresignedRequestTests: S3XCTestCase { + private var s3Config: S3Client.S3ClientConfiguration! + private let key = "test.txt" + + override func setUp() async throws { + try await super.setUp() + s3Config = try await S3Client.S3ClientConfiguration(region: region) + s3Config.authSchemes = [SigV4AuthScheme()] + } + + func testS3PresignedRequest() async throws { + let putObjectInput = PutObjectInput( + body: .noStream, + bucket: bucketName, + key: key, + metadata: ["filename": key] + ) + + let presignedRequest = try await putObjectInput.presign( + config: s3Config, + expiration: 60 + ) + guard let presignedRequest else { + XCTFail("Presigning PutObjectInput failed.") + // return added for compiler to not complain. + return + } + + _ = try await s3Config.httpClientEngine.send(request: presignedRequest) + + let getObjectInput = GetObjectInput(bucket: bucketName, key: key) + let fetchedObject = try await client.getObject(input: getObjectInput) + + XCTAssertNotNil(fetchedObject.metadata) + let metadata = try XCTUnwrap(fetchedObject.metadata) + XCTAssertEqual(metadata["filename"], key) + } +} diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift new file mode 100644 index 00000000000..c84d44ea15d --- /dev/null +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift @@ -0,0 +1,202 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import XCTest +import AWSS3 +import AWSS3Control +import AWSSTS +import ClientRuntime +import AWSClientRuntime + +/// Tests SigV4A signing flow using S3's Multi-Region Access Point (MRAP). +class S3SigV4ATests: S3XCTestCase { + // The custom S3 client configured with only SigV4A auth scheme + // Used to put object to bucket via MRAP + private var sigv4aClient: S3Client! + private var sigv4aConfig: S3Client.S3ClientConfiguration! + + // MRAP ARN format + // arn:aws:s3::\{account-id}:accesspoint/\{MultiRegionAccessPoint_alias} + private var mrapArnFormat = "arn:aws:s3::%@:accesspoint/%@" + private var mrapArn: String! + private var mrapAlias: String! + private let mrapName = "aws-sdk-s3-integration-test-" + UUID().uuidString.split(separator: "-").first!.lowercased() + + // The S3 control client used to create and delete MRAP + private var s3ControlClient: S3ControlClient! + + // The STS client for fetching AWS account ID + private var stsClient: STSClient! + private var accountId: String! + + // Key string used for putting object in tests + private let key = "text.txt" + + private let NSEC_PER_SEC = 1_000_000_000 + + override func setUp() async throws { + // Create a bucket + try await super.setUp() + + // Create sigv4a-only S3 client + sigv4aConfig = try await S3Client.S3ClientConfiguration(region: region) + sigv4aConfig.authSchemes = [SigV4AAuthScheme()] + sigv4aClient = S3Client(config: sigv4aConfig) + + // Fetch account ID + stsClient = try STSClient(region: region) + accountId = try await stsClient.getCallerIdentity(input: GetCallerIdentityInput()).account + + // Construct MRAP config + let publicAccessBlock = S3ControlClientTypes.PublicAccessBlockConfiguration( + blockPublicAcls: false, + blockPublicPolicy: false, + ignorePublicAcls: false, + restrictPublicBuckets: false + ) + let regions = [S3ControlClientTypes.Region(bucket: bucketName, bucketAccountId: accountId)] + let mrapConfig = S3ControlClientTypes.CreateMultiRegionAccessPointInput( + name: mrapName, + publicAccessBlock: publicAccessBlock, + regions: regions + ) + + // Create S3 Multi-Region Access Point (MRAP) + s3ControlClient = try S3ControlClient(region: region) + let createMRAPInput = CreateMultiRegionAccessPointInput( + accountId: accountId, + clientToken: UUID().uuidString.split(separator: "-").first!.lowercased(), + details: mrapConfig + ) + _ = try await s3ControlClient.createMultiRegionAccessPoint(input: createMRAPInput).requestTokenARN + + // Wait until MRAP creation finishes + var status: S3ControlClientTypes.MultiRegionAccessPointStatus? = .creating + repeat { + let seconds = 20.0 + try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) + + status = try await s3ControlClient.getMultiRegionAccessPoint(input: GetMultiRegionAccessPointInput( + accountId: accountId, + name: mrapName + )).accessPoint?.status + } while status == .creating + + // Fetch MRAP alias then format & save MRAP ARN + // This MRAP ARN is used in place of bucket name in subsequent calls + mrapAlias = try await s3ControlClient.getMultiRegionAccessPoint(input: GetMultiRegionAccessPointInput( + accountId: accountId, + name: mrapName + )).accessPoint?.alias + mrapArn = String(format: mrapArnFormat, accountId, mrapAlias) + } + + override func tearDown() async throws { + // MRAP must be deleted before deleting the linked bucket + try await deleteMRAP() + // Empty & delete the bucket + try await super.tearDown() + } + + private func deleteMRAP() async throws { + // Delete the multi-region access point + _ = try await s3ControlClient.deleteMultiRegionAccessPoint(input: DeleteMultiRegionAccessPointInput( + accountId: accountId, + clientToken: UUID().uuidString.split(separator: "-").first!.lowercased(), + details: S3ControlClientTypes.DeleteMultiRegionAccessPointInput(name: mrapName) + )) + + // Wait until MRAP has been deleted before returning + var mrapExists = true + repeat { + let seconds = 20.0 + try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) + + let mraps = try await s3ControlClient.listMultiRegionAccessPoints(input: ListMultiRegionAccessPointsInput(accountId: accountId)).accessPoints + mrapExists = checkMRAPExists(mraps ?? []) + } while mrapExists + + // Wait some more before returning because + // deleting access point association with buckets takes more time + let seconds = 60.0 + try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) + } + + private func checkMRAPExists(_ mraps: [S3ControlClientTypes.MultiRegionAccessPointReport]) -> Bool { + return mraps.contains { $0.name == mrapName } + } + + func testS3MRAPSigV4A() async throws { + // Put an object to bucket via MRAP using SigV4A-only client + _ = try await sigv4aClient.putObject(input: PutObjectInput( + body: ByteStream.data(Data()), + bucket: mrapArn, + key: key, + metadata: ["filename": key] + )) + + // Confirm object has been successfully uploaded via MRAP + let response = try await sigv4aClient.listObjectsV2( + input: ListObjectsV2Input(bucket: mrapArn) + ) + XCTAssertEqual(response.keyCount, 1) + } + + func testS3MRAPSigV4APresignedRequest() async throws { + // Construct input + let putObjectInput = PutObjectInput( + body: .noStream, + bucket: mrapArn, + key: key, + metadata: ["filename": key] + ) + + // Presign the input with SigV4A (sigv4aConfig has only SigV4A configured) + let presignedRequest = try await putObjectInput.presign( + config: sigv4aConfig, + expiration: 60 + ) + guard let presignedRequest else { + XCTFail("Presigning PutObjectInput failed.") + // return added for compiler to not complain. + return + } + + // Execute request + _ = try await sigv4aConfig.httpClientEngine.send(request: presignedRequest) + + // Confirm object has been uploaded via MRAP + let response = try await sigv4aClient.listObjectsV2( + input: ListObjectsV2Input(bucket: mrapArn) + ) + XCTAssertEqual(response.keyCount, 1) + } + + func testS3MRAPSigV4APresignedURL() async throws { + // Construct input with bucketName set to mrapArn to send request to MRAP + let putObjectInput = PutObjectInput(body: .data(Data()), bucket: mrapArn, key: key, metadata: ["filename": key]) + + // Get presigned URL using SigV4A (sigv4aConfig has only SigV4A configured) + let presignedURLOrNil = try await putObjectInput.presignURL(config: sigv4aConfig, expiration: 30.0) + let presignedURL = try XCTUnwrap(presignedURLOrNil) + + // Construct then send URL request using presigned URL + var urlRequest = URLRequest(url: presignedURL) + urlRequest.httpMethod = "PUT" + urlRequest.httpBody = Data() + _ = try await perform(urlRequest: urlRequest) + + // Confirm object has been uploaded via MRAP + let response = try await sigv4aClient.listObjectsV2( + input: ListObjectsV2Input(bucket: mrapArn) + ) + XCTAssertEqual(response.keyCount, 1) + } +} diff --git a/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift b/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift index 541cb76bac9..62decf5ac05 100644 --- a/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift +++ b/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift @@ -15,9 +15,10 @@ class SQSTests: XCTestCase { private var client: SQSClient! private var queueName: String! private var queueUrl: String? + private let region = "us-west-2" override func setUp() async throws { - client = try await SQSClient() + client = try SQSClient(region: region) queueName = "integration-test-queue-\(UUID().uuidString)" } diff --git a/IntegrationTests/Services/AWSSTSIntegrationTests/STSPresignedRequestTests.swift b/IntegrationTests/Services/AWSSTSIntegrationTests/STSPresignedRequestTests.swift new file mode 100644 index 00000000000..9539dc38cff --- /dev/null +++ b/IntegrationTests/Services/AWSSTSIntegrationTests/STSPresignedRequestTests.swift @@ -0,0 +1,44 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import AWSSTS +import ClientRuntime +import AWSClientRuntime + +/// Tests presigned request using STS::getCallerIdentity. +class STSPresignedRequestTests: XCTestCase { + private var stsConfig: STSClient.STSClientConfiguration! + + override func setUp() async throws { + try await super.setUp() + stsConfig = try await STSClient.STSClientConfiguration(region: "us-east-1") + stsConfig.authSchemes = [SigV4AuthScheme()] + } + + func testGetCallerIdentity() async throws { + let presignedRequest = try await GetCallerIdentityInput().presign( + config: stsConfig, + expiration: 60 + ) + guard let presignedRequest else { + XCTFail("Presigning GetCallerIdentityInput failed.") + // return added for compiler to not complain. + return + } + let httpResponse = try await stsConfig.httpClientEngine.send(request: presignedRequest) + let output = try await GetCallerIdentityOutput(httpResponse: httpResponse, decoder: XMLDecoder()) + + let userID = String(describing: output.userId) + let account = String(describing: output.account) + + XCTAssertNotNil(userID, "User ID should not be nil.") + XCTAssertTrue(!userID.isEmpty) + XCTAssertNotNil(account, "Account ID should not be nil.") + XCTAssertTrue(!account.isEmpty) + } +} diff --git a/Package.swift b/Package.swift index 3eda7f7a255..dfab88c7133 100644 --- a/Package.swift +++ b/Package.swift @@ -231,7 +231,7 @@ func addResolvedTargets() { addDependencies( clientRuntimeVersion: "0.39.0", - crtVersion: "0.22.0" + crtVersion: "0.26.0" ) // Uncomment this line to exclude runtime unit tests @@ -635,3 +635,4 @@ let servicesWithIntegrationTests: [String] = [ // addProtocolTests() addResolvedTargets() + diff --git a/Sources/Core/AWSClientRuntime/Auth/AuthSchemes/SigV4Util.swift b/Sources/Core/AWSClientRuntime/Auth/AuthSchemes/SigV4Util.swift index 1180d5bbb72..36b37d1ce7a 100644 --- a/Sources/Core/AWSClientRuntime/Auth/AuthSchemes/SigV4Util.swift +++ b/Sources/Core/AWSClientRuntime/Auth/AuthSchemes/SigV4Util.swift @@ -9,7 +9,7 @@ import Foundation import ClientRuntime public class SigV4Util { - static let signedBodyHeader = ["S3", "Glacier", "S3 Control"] + static let usesSignedBodyHeader = ["S3", "Glacier", "S3 Control"] static let forceUnsignedBodyForPresigningURL = [ "S3": ["getObject", "putObject"] ] @@ -21,7 +21,7 @@ public class SigV4Util { } static func serviceUsesSignedBodyHeader(serviceName: String) -> Bool { - return signedBodyHeader.contains(serviceName) + return usesSignedBodyHeader.contains(serviceName) } static func setS3SpecificFlags(signingProperties: inout Attributes, serviceName: String) { diff --git a/Sources/Core/AWSClientRuntime/HttpContextBuilder+Extension.swift b/Sources/Core/AWSClientRuntime/HttpContextBuilder+Extension.swift index f2e8bc2127d..80ce4feb622 100644 --- a/Sources/Core/AWSClientRuntime/HttpContextBuilder+Extension.swift +++ b/Sources/Core/AWSClientRuntime/HttpContextBuilder+Extension.swift @@ -97,7 +97,6 @@ extension AttributeKeys { // Keys used to store/retrieve AWSSigningConfig fields in/from signingProperties passed to AWSSigV4Signer public static let unsignedBody = AttributeKey(name: "UnsignedBody") - public static let expiration = AttributeKey(name: "Expiration") public static let signedBodyHeader = AttributeKey(name: "SignedBodyHeader") public static let useDoubleURIEncode = AttributeKey(name: "UseDoubleURIEncode") public static let shouldNormalizeURIPath = AttributeKey(name: "ShouldNormalizeURIPath") diff --git a/Sources/Core/AWSClientRuntime/Middlewares/SigV4Middleware.swift b/Sources/Core/AWSClientRuntime/Middlewares/SigV4Middleware.swift deleted file mode 100644 index 9559b6054b5..00000000000 --- a/Sources/Core/AWSClientRuntime/Middlewares/SigV4Middleware.swift +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -import ClientRuntime -import AwsCommonRuntimeKit - -public struct SigV4Middleware: Middleware { - public let id: String = "Sigv4Signer" - - let config: SigV4Config - - public init(config: SigV4Config) { - self.config = config - } - - public typealias MInput = SdkHttpRequestBuilder - - public typealias MOutput = OperationOutput - - public typealias Context = HttpContext - - public func handle(context: HttpContext, - input: SdkHttpRequestBuilder, - next: H) async throws -> OperationOutput - where H: Handler, - Self.Context == H.Context, - Self.MInput == H.Input, - Self.MOutput == H.Output { - - let originalRequest = input.build() - let crtUnsignedRequest: HTTPRequestBase - if context.isBidirectionalStreamingEnabled() { - crtUnsignedRequest = try originalRequest.toHttp2Request() - } else { - crtUnsignedRequest = try originalRequest.toHttpRequest() - } - - guard let credentialsProvider = context.getCredentialsProvider() else { - throw ClientError.authError("AwsSigv4Signer requires a credentialsProvider") - } - - guard let signingName = context.getSigningName() ?? config.signingService else { - throw ClientError.authError("AwsSigv4Signer requires a signing service") - } - - guard let signingRegion = context.getSigningRegion(), - !signingRegion.isEmpty else { - throw ClientError.authError("AwsSigv4Signer requires a signing region") - } - - // If the context has a signing algorithm, use that. Otherwise, use the operation config's signing algorithm - let signingAlgorithm = context.getSigningAlgorithm() ?? config.signingAlgorithm - - let flags = SigningFlags(useDoubleURIEncode: config.useDoubleURIEncode, - shouldNormalizeURIPath: config.shouldNormalizeURIPath, - omitSessionToken: config.omitSessionToken) - let signedBodyValue: AWSSignedBodyValue = config.unsignedBody ? .unsignedPayload : .empty - - let credentials = try await credentialsProvider.getIdentity(identityProperties: Attributes()) - let signingConfig = AWSSigningConfig( - credentials: credentials, - expiration: config.expiration, - signedBodyHeader: config.signedBodyHeader, - signedBodyValue: signedBodyValue, - flags: flags, - date: Date(), - service: signingName, - region: signingRegion, - signatureType: config.signatureType, - signingAlgorithm: signingAlgorithm - ) - - let crtSignedRequest = try await Signer.signRequest( - request: crtUnsignedRequest, - config: signingConfig.toCRTType() - ) - - context.attributes.set(key: AttributeKeys.requestSignature, value: crtSignedRequest.signature) - - let sdkSignedRequest = input.update(from: crtSignedRequest, originalRequest: originalRequest) - - return try await next.handle(context: context, input: sdkSignedRequest) - } -} diff --git a/Sources/Core/AWSClientRuntime/Signing/AWSSigV4Signer.swift b/Sources/Core/AWSClientRuntime/Signing/AWSSigV4Signer.swift index 3533ad86e18..9c5ffcbd2db 100644 --- a/Sources/Core/AWSClientRuntime/Signing/AWSSigV4Signer.swift +++ b/Sources/Core/AWSClientRuntime/Signing/AWSSigV4Signer.swift @@ -29,7 +29,12 @@ public class AWSSigV4Signer: ClientRuntime.Signer { ) } - let signingConfig = try constructSigningConfig(identity: identity, signingProperties: signingProperties) + var signingConfig = try constructSigningConfig(identity: identity, signingProperties: signingProperties) + + // Used to fix signingConfig.date for testing signRequest(). + if let date = signingProperties.get(key: AttributeKey(name: "SigV4AuthSchemeTests")) { + signingConfig = fixDateForTests(signingConfig, date) + } let unsignedRequest = requestBuilder.build() let crtUnsignedRequest: HTTPRequestBase = isBidirectionalStreamingEnabled ? @@ -194,4 +199,19 @@ public class AWSSigV4Signer: ClientRuntime.Signer { return nil } } + + private func fixDateForTests(_ signingConfig: AWSSigningConfig, _ fixedDate: Date) -> AWSSigningConfig { + return AWSSigningConfig( + credentials: signingConfig.credentials, + expiration: signingConfig.expiration, + signedBodyHeader: signingConfig.signedBodyHeader, + signedBodyValue: signingConfig.signedBodyValue, + flags: signingConfig.flags, + date: fixedDate, + service: signingConfig.service, + region: signingConfig.region, + signatureType: signingConfig.signatureType, + signingAlgorithm: signingConfig.signingAlgorithm + ) + } } diff --git a/Sources/Core/AWSClientRuntime/Signing/SigV4Config.swift b/Sources/Core/AWSClientRuntime/Signing/SigV4Config.swift deleted file mode 100644 index 650f03ccde9..00000000000 --- a/Sources/Core/AWSClientRuntime/Signing/SigV4Config.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation - -public struct SigV4Config { - let credentialsProvider: (any CredentialsProviding)? - let signingService: String? - let signatureType: AWSSignatureType - let useDoubleURIEncode: Bool - let shouldNormalizeURIPath: Bool - let omitSessionToken: Bool - let expiration: TimeInterval - let signedBodyHeader: AWSSignedBodyHeader - let unsignedBody: Bool - let signingAlgorithm: AWSSigningAlgorithm - - public init( - credentialsProvider: (any CredentialsProviding)? = nil, - signingService: String? = nil, - signatureType: AWSSignatureType = .requestHeaders, - useDoubleURIEncode: Bool = true, - shouldNormalizeURIPath: Bool = true, - omitSessionToken: Bool = false, - expiration: TimeInterval = 0, - signedBodyHeader: AWSSignedBodyHeader = .none, - unsignedBody: Bool, - signingAlgorithm: AWSSigningAlgorithm - ) { - self.credentialsProvider = credentialsProvider - self.signingService = signingService - self.signatureType = signatureType - self.useDoubleURIEncode = useDoubleURIEncode - self.shouldNormalizeURIPath = shouldNormalizeURIPath - self.omitSessionToken = omitSessionToken - self.expiration = expiration - self.signedBodyHeader = signedBodyHeader - self.unsignedBody = unsignedBody - self.signingAlgorithm = signingAlgorithm - } -} diff --git a/Tests/Core/AWSClientRuntimeTests/Auth/AuthSchemeTests/SigV4AuthSchemeTests.swift b/Tests/Core/AWSClientRuntimeTests/Auth/AuthSchemeTests/SigV4AuthSchemeTests.swift new file mode 100644 index 00000000000..e4b75552ed3 --- /dev/null +++ b/Tests/Core/AWSClientRuntimeTests/Auth/AuthSchemeTests/SigV4AuthSchemeTests.swift @@ -0,0 +1,285 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import SmithyTestUtil +import ClientRuntime +@testable import AWSClientRuntime + +class SigV4AuthSchemeTests: XCTestCase { + private var contextBuilder: HttpContextBuilder! + private var sigV4AuthScheme: SigV4AuthScheme! + + override func setUp() async throws { + try await super.setUp() + contextBuilder = HttpContextBuilder() + .withSigningName(value: "TestSigningName") + .withSigningRegion(value: "TestSigningRegion") + sigV4AuthScheme = SigV4AuthScheme() + } + + // AttributeKeys.bidirectionalStreaming flag + + func testBidirectionalStreamingIsTrueWhenTrueInContext() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: true) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertTrue(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.bidirectionalStreaming))) + } + + func testBidirectionalStreamingIsFalseWhenFalseInContext() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertFalse(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.bidirectionalStreaming))) + } + + // AttributeKeys.signingName flag + + func testSigningNameIsCopiedCorrectly() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual("TestSigningName", updatedProperties.get(key: AttributeKeys.signingName)) + } + + // AttributeKeys.signingRegion flag + + func testSigningRegionIsCopiedCorrectly() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual("TestSigningRegion", updatedProperties.get(key: AttributeKeys.signingRegion)) + } + + // AttributeKeys.expiration flag + + func testExpirationValueIsZeroWhenNotSetInContext() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(0, updatedProperties.get(key: AttributeKeys.expiration)) + } + + func testExpirationValueIsCopiedCorrectlyFromContext() throws { + let context = contextBuilder + .withExpiration(value: 12345) + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(12345, updatedProperties.get(key: AttributeKeys.expiration)) + } + + // AttributeKeys.signatureType flag + + func testSignatureTypeForPresignURLFlowIsRequestQueryParams() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .PRESIGN_URL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignatureType.requestQueryParams, updatedProperties.get(key: AttributeKeys.signatureType)) + } + + func testSignatureTypeForNormalFlowIsRequestHeaders() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignatureType.requestHeaders, updatedProperties.get(key: AttributeKeys.signatureType)) + } + + func testSignatureTypeForPresignRequestFlowIsRequestHeaders() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .PRESIGN_REQUEST) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignatureType.requestHeaders, updatedProperties.get(key: AttributeKeys.signatureType)) + } + + // AttributeKeys.unsignedBody flag + + func testUnsignedBodyIsFalseWhenUnsignedPayloadTraitFlagAndForceUnsignedBodyFlagAreBothFalse() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertFalse(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.unsignedBody))) + } + + func testUnsignedBodyIsTrueWhenUnsignedPayloadTraitFlagIsFalseAndShouldForceUnsignedBodyIsTrue() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "filler") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: true) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertTrue(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.unsignedBody))) + } + + func testUnsignedBodyIsTrueWhenUnsignedPayloadTraitFlagIsTrueAndShouldForceUnsignedBodyIsFalse() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "S3") + .withFlowType(value: .PRESIGN_URL) + .withOperation(value: "getObject") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertTrue(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.unsignedBody))) + } + + // AttributeKeys.signedBodyHeader flag + + func testUseSignedBodyHeaderWhenServiceIsS3AndUseUnsignedBodyIsFalse() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "S3") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignedBodyHeader.contentSha256, updatedProperties.get(key: AttributeKeys.signedBodyHeader)) + } + + func testUseSignedBodyHeaderWhenServiceIsGlacierAndUseUnsignedBodyIsFalse() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "Glacier") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignedBodyHeader.contentSha256, updatedProperties.get(key: AttributeKeys.signedBodyHeader)) + } + + func testUseSignedBodyHeaderWhenServiceIsS3ControlAndUseUnsignedBodyIsFalse() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "S3 Control") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignedBodyHeader.contentSha256, updatedProperties.get(key: AttributeKeys.signedBodyHeader)) + } + + func testDontUseSignedBodyHeaderWhenUseUnsignedBodyIsTrue() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "S3") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: true) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertEqual(AWSSignedBodyHeader.none, updatedProperties.get(key: AttributeKeys.signedBodyHeader)) + } + + // S3-specific customizations + + // AttributeKeys.useDoubleURIEncode flag + + func testUseDoubleURIEncodeIsFalseWhenServiceIsS3() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "S3") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertFalse(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.useDoubleURIEncode))) + } + + func testUseDoubleURIEncodeIsTrueWhenServiceIsntS3() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "NonS3") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertTrue(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.useDoubleURIEncode))) + } + + // AttributeKeys.shouldNormalizeURIPath flag + + func testShouldNormalizeURIPathIsFalseWhenServiceIsS3() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "S3") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertFalse(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.shouldNormalizeURIPath))) + } + + func testShouldNormalizeURIPathIsTrueWhenServiceIsntS3() throws { + let context = contextBuilder + .with(key: AttributeKeys.bidirectionalStreaming, value: false) + .withServiceName(value: "NonS3") + .withFlowType(value: .NORMAL) + .withOperation(value: "filler") + .withUnsignedPayloadTrait(value: false) + .build() + let updatedProperties = try sigV4AuthScheme.customizeSigningProperties(signingProperties: Attributes(), context: context) + XCTAssertTrue(try XCTUnwrap(updatedProperties.get(key: AttributeKeys.shouldNormalizeURIPath))) + } +} diff --git a/Tests/Core/AWSClientRuntimeTests/Auth/AuthSchemeTests/SigV4UtilTests.swift b/Tests/Core/AWSClientRuntimeTests/Auth/AuthSchemeTests/SigV4UtilTests.swift new file mode 100644 index 00000000000..5a35fc7436a --- /dev/null +++ b/Tests/Core/AWSClientRuntimeTests/Auth/AuthSchemeTests/SigV4UtilTests.swift @@ -0,0 +1,78 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import SmithyTestUtil +import ClientRuntime +@testable import AWSClientRuntime + +class SigV4UtilTests: XCTestCase { + // Test shouldForceUnsignedBody returning true + func testShouldForceUnsignedBodyIsTrueWhenServiceIsS3AndFlowTypeIsPresignURLAndOperationIsGetObject() { + XCTAssertTrue(SigV4Util.shouldForceUnsignedBody(flow: .PRESIGN_URL, serviceName: "S3", opName: "getObject")) + } + + func testShouldForceUnsignedBodyIsTrueWhenServiceIsS3AndFlowTypeIsPresignURLAndOperationIsPutObject() { + XCTAssertTrue(SigV4Util.shouldForceUnsignedBody(flow: .PRESIGN_URL, serviceName: "S3", opName: "putObject")) + } + + // Test shouldForceUnsignedBody returning false due to one failing condition + func testShouldForceUnsignedBodyIsFalseWhenServiceIsntS3() { + XCTAssertFalse(SigV4Util.shouldForceUnsignedBody(flow: .PRESIGN_URL, serviceName: "NonS3", opName: "getObject")) + } + + func testShouldForceUnsignedBodyIsFalseWhenFlowIsntPresignURL() { + XCTAssertFalse(SigV4Util.shouldForceUnsignedBody(flow: .PRESIGN_REQUEST, serviceName: "S3", opName: "getObject")) + } + + func testShouldForceUnsignedBodyIsFalseWhenOperationIsntGetObjectOrPutObject() { + XCTAssertFalse(SigV4Util.shouldForceUnsignedBody(flow: .PRESIGN_URL, serviceName: "S3", opName: "noOp")) + } + + // Test useSignedBodyHeader returning true + func testUseSignedBodyHeaderIsTrueWhenServiceIsS3() { + XCTAssertTrue(SigV4Util.serviceUsesSignedBodyHeader(serviceName: "S3")) + } + + func testUseSignedBodyHeaderIsTrueWhenServiceIsS3Control() { + XCTAssertTrue(SigV4Util.serviceUsesSignedBodyHeader(serviceName: "S3 Control")) + } + + func testUseSignedBodyHeaderIsTrueWhenServiceIsGlacier() { + XCTAssertTrue(SigV4Util.serviceUsesSignedBodyHeader(serviceName: "Glacier")) + } + + // Test useSignedBodyHeader returning false + func testUseSignedBodyHeaderIsTrueWhenServiceIsNotApplicable() { + XCTAssertFalse(SigV4Util.serviceUsesSignedBodyHeader(serviceName: "RandomService")) + } + + // Test setS3SpecificFlags + func testUseDoubleURIEncodeSetToFalseIfS3() { + var config = Attributes() + SigV4Util.setS3SpecificFlags(signingProperties: &config, serviceName: "S3") + XCTAssertFalse(config.get(key: AttributeKeys.useDoubleURIEncode)!) + } + + func testUseDoubleURIEncodeSetToTrueIfNotS3() { + var config = Attributes() + SigV4Util.setS3SpecificFlags(signingProperties: &config, serviceName: "Test") + XCTAssertTrue(config.get(key: AttributeKeys.useDoubleURIEncode)!) + } + + func testShouldNormalizeURIPathSetToFalseIfS3() { + var config = Attributes() + SigV4Util.setS3SpecificFlags(signingProperties: &config, serviceName: "S3") + XCTAssertFalse(config.get(key: AttributeKeys.shouldNormalizeURIPath)!) + } + + func testShouldNormalizeURIPathSetToTrueIfNotS3() { + var config = Attributes() + SigV4Util.setS3SpecificFlags(signingProperties: &config, serviceName: "Test") + XCTAssertTrue(config.get(key: AttributeKeys.shouldNormalizeURIPath)!) + } +} diff --git a/Tests/Core/AWSClientRuntimeTests/Auth/CredentialsProvidersTests/CachedCredentialsProviderTests.swift b/Tests/Core/AWSClientRuntimeTests/Auth/CredentialsProvidersTests/CachedCredentialsProviderTests.swift index 1b8b083e2d6..a145e19eb99 100644 --- a/Tests/Core/AWSClientRuntimeTests/Auth/CredentialsProvidersTests/CachedCredentialsProviderTests.swift +++ b/Tests/Core/AWSClientRuntimeTests/Auth/CredentialsProvidersTests/CachedCredentialsProviderTests.swift @@ -34,6 +34,7 @@ class CachedCredentialsProviderTests: XCTestCase { XCTAssertEqual(counter, 1) try! await Task.sleep(nanoseconds: 1_000_000_000 / 100) // 0.01 seconds + let credentials = try await subject.getIdentity() // Counter is 2 because we slept long enough for cache to expire diff --git a/Tests/Core/AWSClientRuntimeTests/Sigv4/SigV4SigningTests.swift b/Tests/Core/AWSClientRuntimeTests/Sigv4/SigV4SigningTests.swift index 3abc7ac033a..1513a8b702e 100644 --- a/Tests/Core/AWSClientRuntimeTests/Sigv4/SigV4SigningTests.swift +++ b/Tests/Core/AWSClientRuntimeTests/Sigv4/SigV4SigningTests.swift @@ -9,6 +9,7 @@ import AwsCommonRuntimeKit import ClientRuntime import SmithyTimestamps import XCTest +import SmithyTestUtil @testable import AWSClientRuntime @@ -17,6 +18,275 @@ class Sigv4SigningTests: XCTestCase { CommonRuntimeKit.initialize() } + // Test success case + func testSignRequestSuccess() async throws { + let dateString = "2024-01-16T12:36:00Z" + guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { + XCTFail("Unable to parse date") + return + } + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = AWSCredentials(accessKey: "test-access-key", secret: "test-secret-key") + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.bidirectionalStreaming, value: false) + signingProperties.set(key: AttributeKeys.unsignedBody, value: false) + signingProperties.set(key: AttributeKeys.signingName, value: "test") + signingProperties.set(key: AttributeKeys.signingRegion, value: "us-east-1") + signingProperties.set(key: AttributeKeys.signingAlgorithm, value: .sigv4) + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + let signedRequest = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + + let expectedSignature = "68a60ecd39081e56bc2413c3397669c72ba61642c5d283aa15ad2f7e155c3e04" + XCTAssertEqual(expectedSignature, signedRequest.signature) + } + + // Test exception cases + func testSignRequestMissingBidirecitonalSteamingFlag() async throws { + let dateString = "2024-01-16T12:36:00Z" + guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { + XCTFail("Unable to parse date") + return + } + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = AWSCredentials(accessKey: "test-access-key", secret: "test-secret-key") + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.unsignedBody, value: false) + signingProperties.set(key: AttributeKeys.signingName, value: "test") + signingProperties.set(key: AttributeKeys.signingRegion, value: "us-east-1") + signingProperties.set(key: AttributeKeys.signingAlgorithm, value: .sigv4) + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + do { + _ = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + XCTFail("The code failed to throw at expected point.") + } catch ClientError.authError(let message) { + let expectedMessage = "Signing properties passed to the AWSSigV4Signer must contain T/F flag for bidirectional streaming." + XCTAssertEqual(expectedMessage, message) + } catch { + XCTFail("Unexpected error thrown: \(error.localizedDescription)") + } + } + + func testSignRequestWrongTypeOfIdentity() async throws { + let dateString = "2024-01-16T12:36:00Z" + guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { + XCTFail("Unable to parse date") + return + } + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = MockIdentity() + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.bidirectionalStreaming, value: false) + signingProperties.set(key: AttributeKeys.unsignedBody, value: false) + signingProperties.set(key: AttributeKeys.signingName, value: "test") + signingProperties.set(key: AttributeKeys.signingRegion, value: "us-east-1") + signingProperties.set(key: AttributeKeys.signingAlgorithm, value: .sigv4) + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + do { + _ = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + XCTFail("The code failed to throw at expected point.") + } catch ClientError.authError(let message) { + let expectedMessage = "Identity passed to the AWSSigV4Signer must be of type Credentials." + XCTAssertEqual(expectedMessage, message) + } catch { + XCTFail("Unexpected error thrown: \(error.localizedDescription)") + } + } + + func testSignRequestMissingUnsignedBodyFlag() async throws { + let dateString = "2024-01-16T12:36:00Z" + guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { + XCTFail("Unable to parse date") + return + } + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = AWSCredentials(accessKey: "test-access-key", secret: "test-secret-key") + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.bidirectionalStreaming, value: false) + signingProperties.set(key: AttributeKeys.signingName, value: "test") + signingProperties.set(key: AttributeKeys.signingRegion, value: "us-east-1") + signingProperties.set(key: AttributeKeys.signingAlgorithm, value: .sigv4) + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + do { + _ = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + XCTFail("The code failed to throw at expected point.") + } catch ClientError.authError(let message) { + let expectedMessage = "Signing properties passed to the AWSSigV4Signer must contain T/F flag for unsigned body." + XCTAssertEqual(expectedMessage, message) + } catch { + XCTFail("Unexpected error thrown: \(error.localizedDescription)") + } + } + + func testSignRequestMissingSigningName() async throws { + let dateString = "2024-01-16T12:36:00Z" + guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { + XCTFail("Unable to parse date") + return + } + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = AWSCredentials(accessKey: "test-access-key", secret: "test-secret-key") + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.bidirectionalStreaming, value: false) + signingProperties.set(key: AttributeKeys.unsignedBody, value: false) + signingProperties.set(key: AttributeKeys.signingRegion, value: "us-east-1") + signingProperties.set(key: AttributeKeys.signingAlgorithm, value: .sigv4) + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + do { + _ = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + XCTFail("The code failed to throw at expected point.") + } catch ClientError.authError(let message) { + let expectedMessage = "Signing properties passed to the AWSSigV4Signer must contain signing name." + XCTAssertEqual(expectedMessage, message) + } catch { + XCTFail("Unexpected error thrown: \(error.localizedDescription)") + } + } + + func testSignRequestMissingSigningRegion() async throws { + let dateString = "2024-01-16T12:36:00Z" + guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { + XCTFail("Unable to parse date") + return + } + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = AWSCredentials(accessKey: "test-access-key", secret: "test-secret-key") + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.bidirectionalStreaming, value: false) + signingProperties.set(key: AttributeKeys.unsignedBody, value: false) + signingProperties.set(key: AttributeKeys.signingName, value: "test") + signingProperties.set(key: AttributeKeys.signingAlgorithm, value: .sigv4) + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + do { + _ = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + XCTFail("The code failed to throw at expected point.") + } catch ClientError.authError(let message) { + let expectedMessage = "Signing properties passed to the AWSSigV4Signer must contain signing region." + XCTAssertEqual(expectedMessage, message) + } catch { + XCTFail("Unexpected error thrown: \(error.localizedDescription)") + } + } + + func testSignRequestMissingSigningAlgorithm() async throws { + let dateString = "2024-01-16T12:36:00Z" + let date = try XCTUnwrap(TimestampFormatter(format: .dateTime).date(from: dateString)) + + let requestBuilder = SdkHttpRequestBuilder() + .withHost("example.amazonaws.com") + .withPath("/") + .withMethod(.get) + .withPort(443) + .withProtocol(.http) + .withHeader(name: "host", value: "example.amazonaws.com") + + let credentials = AWSCredentials(accessKey: "test-access-key", secret: "test-secret-key") + + var signingProperties = Attributes() + signingProperties.set(key: AttributeKeys.bidirectionalStreaming, value: false) + signingProperties.set(key: AttributeKeys.unsignedBody, value: false) + signingProperties.set(key: AttributeKeys.signingName, value: "test") + signingProperties.set(key: AttributeKeys.signingRegion, value: "us-east-1") + signingProperties.set(key: AttributeKey(name: "SigV4AuthSchemeTests"), value: date) + + do { + _ = try await AWSSigV4Signer().signRequest( + requestBuilder: requestBuilder, + identity: credentials, + signingProperties: signingProperties + ) + XCTFail("The code failed to throw at expected point.") + } catch ClientError.authError(let message) { + let expectedMessage = "Signing properties passed to the AWSSigV4Signer must contain signing algorithm." + XCTAssertEqual(expectedMessage, message) + } catch { + XCTFail("Unexpected error thrown: \(error.localizedDescription)") + } + } + func testPresigner() async throws { let dateString = "2015-08-30T12:36:00Z" guard let date = TimestampFormatter(format: .dateTime).date(from: dateString) else { diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolCustomizations.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolCustomizations.kt index 4088c262fa6..a3d30b82068 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolCustomizations.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolCustomizations.kt @@ -6,7 +6,6 @@ package software.amazon.smithy.aws.swift.codegen import software.amazon.smithy.aws.swift.codegen.customization.RulesBasedAuthSchemeResolverGenerator -import software.amazon.smithy.aws.swift.codegen.middleware.AWSSigningMiddleware import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.swift.codegen.AuthSchemeResolverGenerator @@ -28,8 +27,8 @@ abstract class AWSHttpProtocolCustomizations : DefaultHttpProtocolCustomizations writer.write(" .withCredentialsProvider(value: config.credentialsProvider)") writer.write(" .withIdentityResolver(value: config.credentialsProvider, type: IdentityKind.aws)") writer.write(" .withRegion(value: config.region)") - if (AWSSigningMiddleware.hasSigV4AuthScheme(ctx.model, ctx.service, op)) { - val signingName = AWSSigningMiddleware.signingServiceName(serviceShape) + if (SigV4Utils.hasSigV4AuthScheme(ctx.model, ctx.service, op)) { + val signingName = SigV4Utils.signingServiceName(serviceShape) writer.write(" .withSigningName(value: \$S)", signingName) writer.write(" .withSigningRegion(value: config.signingRegion)") } diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSigningParams.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSigningParams.kt deleted file mode 100644 index 5cff2430048..00000000000 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSigningParams.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -package software.amazon.smithy.aws.swift.codegen - -import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait -import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.swift.codegen.model.hasTrait -import java.util.Locale - -data class AWSSigningParams( - // The service that the signature is being prepared for. - val service: ServiceShape, - // The operation that the signature is being prepared for. - val operation: OperationShape, - // When set to true, forces the AWSv4 signature to be placed in the query string. - val useSignatureTypeQueryString: Boolean, - // When set to true, the AWSv4 signature does not include the body of the request. - val forceUnsignedBody: Boolean, - // When set to true, the expiration is included in the params string so it may be set to a custom - // value named `expiration`. - val useExpiration: Boolean, - // The signing algorithm to use which is determined by the service's SigV4Trait. - val signingAlgorithm: SigningAlgorithm -) - -enum class SigningAlgorithm { - SigV4 -} - -private fun SigningAlgorithm.toSwiftLiteral(): String { - return when (this) { - SigningAlgorithm.SigV4 -> ".sigv4" - } -} - -class SigV4Configurator( - val useSignatureTypeQueryString: Boolean, - val useExpiration: Boolean, - val useDoubleURIEncode: Boolean, - val useURLPathNormalization: Boolean, - val useUnsignedPayload: Boolean, - val useSignedBodyHeader: Boolean, - val signingAlgorithm: SigningAlgorithm -) { - constructor(params: AWSSigningParams) : this( - useSignatureTypeQueryString = params.useSignatureTypeQueryString, - useExpiration = params.useExpiration, - useDoubleURIEncode = params.service.sdkId.lowercase(Locale.US) != "s3", - useURLPathNormalization = params.service.sdkId.lowercase(Locale.US) != "s3", - useUnsignedPayload = params.operation.hasTrait() || params.forceUnsignedBody, - useSignedBodyHeader = listOf("s3", "glacier", "s3 control").contains(params.service.sdkId.lowercase(Locale.US)) && - !params.operation.hasTrait() && !params.forceUnsignedBody, - signingAlgorithm = params.signingAlgorithm - ) - - val swiftParamsString: String - get() { - // Create param strings for each setting, or null for default param - val params = listOf( - "signatureType: .requestQueryParams".takeIf { useSignatureTypeQueryString }, - "useDoubleURIEncode: false".takeIf { !useDoubleURIEncode }, - "shouldNormalizeURIPath: false".takeIf { !useURLPathNormalization }, - "expiration: expiration".takeIf { useExpiration }, - "signedBodyHeader: .contentSha256".takeIf { useSignedBodyHeader }, - "unsignedBody: " + ("true".takeIf { useUnsignedPayload } ?: "false"), - "signingAlgorithm: ${signingAlgorithm.toSwiftLiteral()}" - ) - // Join the strings for use in initializing the Swift SigV4Config - return params.mapNotNull { it }.joinToString(", ") - } -} diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGenerator.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGenerator.kt index e87927ceaa8..d957ebecca2 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGenerator.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGenerator.kt @@ -6,7 +6,6 @@ package software.amazon.smithy.aws.swift.codegen import software.amazon.smithy.aws.swift.codegen.AWSClientRuntimeTypes.Core.AWSClientConfiguration -import software.amazon.smithy.aws.swift.codegen.middleware.AWSSigningMiddleware import software.amazon.smithy.aws.swift.codegen.model.traits.Presignable import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape @@ -33,12 +32,12 @@ class PresignerGenerator : SwiftIntegration { override fun writeAdditionalFiles(ctx: CodegenContext, protoCtx: ProtocolGenerator.GenerationContext, delegator: SwiftDelegator) { val service = ctx.model.expectShape(ctx.settings.service) - if (!AWSSigningMiddleware.isSupportedAuthentication(ctx.model, service)) return + if (!SigV4Utils.isSupportedAuthentication(ctx.model, service)) return val presignOperations = service.allOperations .map { ctx.model.expectShape(it) } .filter { operationShape -> operationShape.hasTrait(Presignable.ID) } .map { operationShape -> - check(AWSSigningMiddleware.hasSigV4AuthScheme(ctx.model, service, operationShape)) { "Operation does not have valid auth trait" } + check(SigV4Utils.hasSigV4AuthScheme(ctx.model, service, operationShape)) { "Operation does not have valid auth trait" } PresignableOperation(service.id.toString(), operationShape.id.toString()) } presignOperations.forEach { presignableOperation -> diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/AWSSigningMiddleware.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4Utils.kt similarity index 51% rename from codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/AWSSigningMiddleware.kt rename to codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4Utils.kt index dcc3d33f6ea..87d406c4d77 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/AWSSigningMiddleware.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4Utils.kt @@ -3,62 +3,18 @@ * SPDX-License-Identifier: Apache-2.0. */ -package software.amazon.smithy.aws.swift.codegen.middleware +package software.amazon.smithy.aws.swift.codegen -import software.amazon.smithy.aws.swift.codegen.AWSClientRuntimeTypes -import software.amazon.smithy.aws.swift.codegen.AWSClientRuntimeTypes.Signing.SigV4Config -import software.amazon.smithy.aws.swift.codegen.AWSSigningParams -import software.amazon.smithy.aws.swift.codegen.SigV4Configurator import software.amazon.smithy.aws.traits.auth.SigV4Trait -import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.ServiceIndex import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.traits.OptionalAuthTrait -import software.amazon.smithy.swift.codegen.SwiftWriter -import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator -import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.MiddlewareShapeUtils -import software.amazon.smithy.swift.codegen.middleware.MiddlewarePosition -import software.amazon.smithy.swift.codegen.middleware.MiddlewareRenderable -import software.amazon.smithy.swift.codegen.middleware.MiddlewareStep import software.amazon.smithy.swift.codegen.model.expectTrait import software.amazon.smithy.swift.codegen.model.hasTrait -open class AWSSigningMiddleware( - val model: Model, - val symbolProvider: SymbolProvider, - val params: AWSSigningParams -) : MiddlewareRenderable { - - override val name = "AWSSigningMiddleware" - - override val middlewareStep = MiddlewareStep.FINALIZESTEP - - override val position = MiddlewarePosition.BEFORE - - override fun render( - ctx: ProtocolGenerator.GenerationContext, - writer: SwiftWriter, - op: OperationShape, - operationStackName: String, - ) { - renderConfigDeclaration(writer) - val output = MiddlewareShapeUtils.outputSymbol(symbolProvider, model, op) - val outputError = MiddlewareShapeUtils.outputErrorSymbol(op) - writer.write( - "$operationStackName.${middlewareStep.stringValue()}.intercept(position: ${position.stringValue()}, middleware: \$N<\$N>(config: sigv4Config))", - AWSClientRuntimeTypes.Signing.SigV4Middleware, - output - ) - } - - private fun renderConfigDeclaration(writer: SwiftWriter) { - writer.addImport(SigV4Config) - val paramString = SigV4Configurator(params).swiftParamsString - writer.write("let sigv4Config = \$N(\$L)", SigV4Config, paramString) - } - +open class SigV4Utils() { companion object { /** * Returns if the SigV4Trait is a auth scheme supported by the service. diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/RulesBasedAuthSchemeResolverGenerator.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/RulesBasedAuthSchemeResolverGenerator.kt index 5ba7311392d..8d0326629e1 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/RulesBasedAuthSchemeResolverGenerator.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/RulesBasedAuthSchemeResolverGenerator.kt @@ -1,5 +1,6 @@ package software.amazon.smithy.aws.swift.codegen.customization +import software.amazon.smithy.aws.traits.auth.SigV4ATrait import software.amazon.smithy.aws.traits.auth.SigV4Trait import software.amazon.smithy.rulesengine.language.EndpointRuleSet import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait @@ -85,7 +86,7 @@ class RulesBasedAuthSchemeResolverGenerator { // SigV4 case write("case .sigV4(let param):") indent() - write("var sigV4Option = AuthOption(schemeID: \"${SigV4Trait.ID}\")") + write("var sigV4Option = AuthOption(schemeID: \$S)", SigV4Trait.ID) write("sigV4Option.signingProperties.set(key: AttributeKeys.signingName, value: param.signingName)") write("sigV4Option.signingProperties.set(key: AttributeKeys.signingRegion, value: param.signingRegion)") write("validAuthOptions.append(sigV4Option)") @@ -93,9 +94,7 @@ class RulesBasedAuthSchemeResolverGenerator { // SigV4A case write("case .sigV4A(let param):") indent() - // sigv4a trait is not yet implemented by Smithy - // This is a SDK-level customization until the trait is added - write("var sigV4Option = AuthOption(schemeID: \"${SigV4Trait.ID}a\")") + write("var sigV4Option = AuthOption(schemeID: \$S)", SigV4ATrait.ID) write("sigV4Option.signingProperties.set(key: AttributeKeys.signingName, value: param.signingName)") write("sigV4Option.signingProperties.set(key: AttributeKeys.signingRegion, value: param.signingRegionSet?[0])") write("validAuthOptions.append(sigV4Option)") diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/presignable/PresignableUrlIntegration.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/presignable/PresignableUrlIntegration.kt index 8b114336dd5..9446a1e68e6 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/presignable/PresignableUrlIntegration.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/presignable/PresignableUrlIntegration.kt @@ -3,9 +3,9 @@ package software.amazon.smithy.aws.swift.codegen.customization.presignable import software.amazon.smithy.aws.swift.codegen.AWSClientRuntimeTypes import software.amazon.smithy.aws.swift.codegen.AWSServiceConfig import software.amazon.smithy.aws.swift.codegen.PresignableOperation +import software.amazon.smithy.aws.swift.codegen.SigV4Utils import software.amazon.smithy.aws.swift.codegen.customization.InputTypeGETQueryItemMiddleware import software.amazon.smithy.aws.swift.codegen.customization.PutObjectPresignedURLMiddleware -import software.amazon.smithy.aws.swift.codegen.middleware.AWSSigningMiddleware import software.amazon.smithy.aws.swift.codegen.middleware.InputTypeGETQueryItemMiddlewareRenderable import software.amazon.smithy.aws.swift.codegen.middleware.PutObjectPresignedURLMiddlewareRenderable import software.amazon.smithy.codegen.core.Symbol @@ -52,7 +52,7 @@ class PresignableUrlIntegration(private val presignedOperations: Map(ctx.settings.service) - if (!AWSSigningMiddleware.isSupportedAuthentication(ctx.model, service)) return + if (!SigV4Utils.isSupportedAuthentication(ctx.model, service)) return val operationsToGenerate = presignedOperations.getOrDefault(service.id.toString(), setOf()) @@ -60,7 +60,7 @@ class PresignableUrlIntegration(private val presignedOperations: Map(it) } .filter { operationShape -> operationsToGenerate.contains(operationShape.id.toString()) } .map { operationShape -> - check(AWSSigningMiddleware.hasSigV4AuthScheme(ctx.model, service, operationShape)) { "Operation does not have valid auth trait" } + check(SigV4Utils.hasSigV4AuthScheme(ctx.model, service, operationShape)) { "Operation does not have valid auth trait" } PresignableOperation(service.id.toString(), operationShape.id.toString()) } presignOperations.forEach { presignableOperation -> diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSigningMiddlewareTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSigningMiddlewareTests.kt deleted file mode 100644 index 181200f25ef..00000000000 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSigningMiddlewareTests.kt +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -package software.amazon.smithy.aws.swift.codegen - -import io.kotest.matchers.string.shouldContainOnlyOnce -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import software.amazon.smithy.aws.swift.codegen.awsjson.AwsJson1_0_ProtocolGenerator -import software.amazon.smithy.aws.swift.codegen.middleware.AWSSigningMiddleware -import software.amazon.smithy.aws.traits.ServiceTrait -import software.amazon.smithy.aws.traits.auth.SigV4Trait -import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait -import software.amazon.smithy.model.Model -import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.model.shapes.ShapeId -import software.amazon.smithy.model.shapes.StructureShape -import software.amazon.smithy.model.traits.AuthTrait -import software.amazon.smithy.model.traits.HttpBasicAuthTrait -import software.amazon.smithy.swift.codegen.SwiftWriter -import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator -import software.amazon.smithy.swift.codegen.model.AddOperationShapes - -class AWSSigningMiddlewareTests { - @Test - fun `service has SigV4Trait and operation has auth trait`() { - val sigV4Trait = SigV4Trait.builder().name("ExampleService").build() - val authList = listOf(HttpBasicAuthTrait().toShapeId(), sigV4Trait.toShapeId()) - - val serviceShape = ServiceShape.builder() - .id("com.test#Example") - .version("1.0") - .addTrait(sigV4Trait) - .build() - val operationShape = OperationShape.builder() - .id("com.test#ExampleOperation") - .addTrait(UnsignedPayloadTrait()) - .addTrait(AuthTrait(authList)) - .build() - val model = Model.builder() - .addShape(serviceShape) - .addShape(operationShape) - .build() - - val hasAuthScheme = AWSSigningMiddleware.hasSigV4AuthScheme(model, serviceShape, operationShape) - assertTrue(hasAuthScheme) - } - - @Test - fun `service has SigV4trait but operation does not have auth`() { - val serviceShape = ServiceShape.builder() - .id("com.test#Example") - .version("1.0") - .addTrait(SigV4Trait.builder().name("ExampleService").build()) - .build() - val outputShape = StructureShape.builder() - .id("com.test#ExampleOutput") - .build() - val operationShape = OperationShape.builder() - .id("com.test#ExampleOperation") - .output { ShapeId.from("com.test#ExampleOutput") } - .build() - val model = Model.builder() - .addShape(serviceShape) - .addShape(operationShape) - .addShape(outputShape) - .build() - - val hasAuthScheme = AWSSigningMiddleware.hasSigV4AuthScheme(model, serviceShape, operationShape) - assertFalse(hasAuthScheme) - } - - @Test - fun `render unsignedBody true`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(unsignedBody: true, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) -""" - val writer = SwiftWriter("testName") - val context = contextForSDK("ExampleService") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = AWSSigningMiddleware(context.model, context.symbolProvider, params) - subject.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `render unsignedBody false`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(unsignedBody: false, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) -""" - val writer = SwiftWriter("testName") - val context = contextForSDK("ExampleService") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = AWSSigningMiddleware(context.model, context.symbolProvider, params) - subject.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `render unsignedBody true, presigner`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(expiration: expiration, unsignedBody: true, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) -""" - val writer = SwiftWriter("testName") - val context = contextForSDK("ExampleService") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = true, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = AWSSigningMiddleware(context.model, context.symbolProvider, params) - subject.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `render unsignedBody false, presigner`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(expiration: expiration, unsignedBody: false, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) -""" - val writer = SwiftWriter("testName") - val context = contextForSDK("ExampleService") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = true, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val sut = AWSSigningMiddleware(context.model, context.symbolProvider, params) - sut.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `render s3 with query string sig`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(signatureType: .requestQueryParams, useDoubleURIEncode: false, shouldNormalizeURIPath: false, expiration: expiration, unsignedBody: true, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) -""" - val writer = SwiftWriter("testName") - val context = contextForSDK("S3") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = true, - forceUnsignedBody = true, - useExpiration = true, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = AWSSigningMiddleware(context.model, context.symbolProvider, params) - subject.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `render s3 with signed body & normal sig`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(useDoubleURIEncode: false, shouldNormalizeURIPath: false, expiration: expiration, signedBodyHeader: .contentSha256, unsignedBody: false, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) - """.trimIndent() - val writer = SwiftWriter("testName") - val context = contextForSDK("S3") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = true, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = AWSSigningMiddleware(context.model, context.symbolProvider, params) - subject.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `render glacier with signed body & normal sig`() { - val expectedContents = """ -let sigv4Config = AWSClientRuntime.SigV4Config(signedBodyHeader: .contentSha256, unsignedBody: false, signingAlgorithm: .sigv4) -stack.finalizeStep.intercept(position: .before, middleware: AWSClientRuntime.SigV4Middleware(config: sigv4Config)) -""" - val writer = SwiftWriter("testName") - val context = contextForSDK("Glacier") - val operation = context.model.operationShapes.first() - - val params = AWSSigningParams( - context.service, - operation, - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = AWSSigningMiddleware(context.model, context.symbolProvider, params) - subject.render(context, writer, operation, stack) - val contents = writer.toString() - - contents.shouldContainOnlyOnce(expectedContents) - } - - private fun contextForSDK(sdkID: String): ProtocolGenerator.GenerationContext { - val serviceShape = ServiceShape.builder() - .id("com.test#Example") - .version("1.0") - .addTrait(serviceTraitWithID(sdkID)) - .addTrait(SigV4Trait.builder().name("ExampleService").build()) - .build() - val outputShape = StructureShape.builder() - .id("com.test#ExampleOutput") - .build() - val operationShape = OperationShape.builder() - .id("com.test#ExampleOperation") - .output { ShapeId.from("com.test#ExampleOutput") } - .build() - val opStackName = "stack" - var model = Model.builder() - .addShape(serviceShape) - .addShape(operationShape) - .addShape(outputShape) - .build() - model = AddOperationShapes.execute(model, serviceShape, "Example") - return model.newTestContext("com.test#Example", AwsJson1_0_ProtocolGenerator()).ctx - } - - private fun serviceTraitWithID(sdkID: String): ServiceTrait { - return ServiceTrait.builder().sdkId(sdkID).arnNamespace("aws::example").cloudFormationName("ExampleService").cloudTrailEventSource("ExampleService").build() - } - - private val stack = "stack" -} diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4ConfiguratorTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4ConfiguratorTests.kt deleted file mode 100644 index 479675591f6..00000000000 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4ConfiguratorTests.kt +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -package software.amazon.smithy.aws.swift.codegen - -import io.kotest.matchers.booleans.shouldBeFalse -import io.kotest.matchers.booleans.shouldBeTrue -import io.kotest.matchers.collections.shouldContainExactly -import io.kotest.matchers.string.shouldContain -import io.kotest.matchers.string.shouldNotContain -import org.junit.jupiter.api.Test -import software.amazon.smithy.aws.traits.ServiceTrait -import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait -import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ServiceShape - -class SigV4ConfiguratorTests { - @Test - fun `useDoubleURIEncode is false when service is S3`() { - val params = AWSSigningParams( - service = s3Service, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useDoubleURIEncode.shouldBeFalse() - } - - @Test - fun `useDoubleURIEncode is true when service is not S3`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useDoubleURIEncode.shouldBeTrue() - } - - @Test - fun `useURLPathNormalization is false when service is S3`() { - val params = AWSSigningParams( - service = s3Service, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useDoubleURIEncode.shouldBeFalse() - } - - @Test - fun `useURLPathNormalization is true when service is not S3`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useDoubleURIEncode.shouldBeTrue() - } - - @Test - fun `useUnsignedPayload is false when operation has no presigned trait and forceUnsignedBody is false`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useUnsignedPayload.shouldBeFalse() - } - - @Test - fun `useUnsignedPayload is true when operation has presigned trait and forceUnsignedBody is false`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(true), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useUnsignedPayload.shouldBeTrue() - } - - @Test - fun `useUnsignedPayload is true when operation has no presigned trait and forceUnsignedBody is true`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useUnsignedPayload.shouldBeTrue() - } - - @Test - fun `useUnsignedPayload is true when operation has presigned trait and forceUnsignedBody is true`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(true), - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useUnsignedPayload.shouldBeTrue() - } - - @Test - fun `useSignedBodyHeader is false when service is not S3 or Glacier or S3 Control and forceUnsignedBody is false`() { - val params = AWSSigningParams( - service = otherService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeFalse() - } - - @Test - fun `useSignedBodyHeader is false when service is S3 and forceUnsignedBody is true`() { - val params = AWSSigningParams( - service = s3Service, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeFalse() - } - - @Test - fun `useSignedBodyHeader is true when service is S3 and forceUnsignedBody is false`() { - val params = AWSSigningParams( - service = s3Service, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeTrue() - } - - @Test - fun `useSignedBodyHeader is false when service is Glacier and forceUnsignedBody is true`() { - val params = AWSSigningParams( - service = glacierService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeFalse() - } - - @Test - fun `useSignedBodyHeader is true when service is Glacier and forceUnsignedBody is false`() { - val params = AWSSigningParams( - service = glacierService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeTrue() - } - - @Test - fun `useSignedBodyHeader is false when service is S3 Control and forceUnsignedBody is true`() { - val params = AWSSigningParams( - service = s3ControlService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = true, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeFalse() - } - - @Test - fun `useSignedBodyHeader is true when service is S3 Control and forceUnsignedBody is false`() { - val params = AWSSigningParams( - service = s3ControlService, - operation = operation(), - useSignatureTypeQueryString = false, - forceUnsignedBody = false, - useExpiration = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - val subject = SigV4Configurator(params) - subject.useSignedBodyHeader.shouldBeTrue() - } - - @Test - fun `swiftParamsString includes signatureType when useSignatureTypeQueryString is true`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = true, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("signatureType: .requestQueryParams") - } - - @Test - fun `swiftParamsString omits signatureType when useSignatureTypeQueryString is false`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldNotContain("signatureType:") - } - - @Test - fun `swiftParamsString includes useDoubleURIEncode false when useDoubleURLEncode is false`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("useDoubleURIEncode: false") - } - - @Test - fun `swiftParamsString omits useDoubleURIEncode false when when useDoubleURLEncode is true`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = true, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldNotContain("useDoubleURIEncode:") - } - - @Test - fun `swiftParamsString includes shouldNormalizeURIPath false when useURLPathNormalization is false`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("shouldNormalizeURIPath: false") - } - - @Test - fun `swiftParamsString omits shouldNormalizeURIPath when useURLPathNormalization is true`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = true, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldNotContain("shouldNormalizeURIPath:") - } - - @Test - fun `swiftParamsString includes expiration when useExpiration is true`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = true, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("expiration: expiration") - } - - @Test - fun `swiftParamsString omits expiration when useExpiration is false`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldNotContain("expiration:") - } - - @Test - fun `swiftParamsString includes signedBodyHeader when useSignedBodyHeader is true`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = true, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("signedBodyHeader: .contentSha256") - } - - @Test - fun `swiftParamsString omits signedBodyHeader when useSignedBodyHeader is false`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldNotContain("signedBodyHeader:") - } - - @Test - fun `swiftParamsString includes unsignedBody true when useUnsignedPayload is true`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = true, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("unsignedBody: true") - } - - @Test - fun `swiftParamsString includes unsignedBody false when useUnsignedPayload is false`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = false, - useExpiration = false, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = false, - useSignedBodyHeader = false, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.shouldContain("unsignedBody: false") - } - - @Test - fun `swiftParamsString joins individual params with comma plus space`() { - val subject = SigV4Configurator( - useSignatureTypeQueryString = true, - useExpiration = true, - useDoubleURIEncode = false, - useURLPathNormalization = false, - useUnsignedPayload = true, - useSignedBodyHeader = true, - signingAlgorithm = SigningAlgorithm.SigV4 - ) - subject.swiftParamsString.split(", ").shouldContainExactly( - "signatureType: .requestQueryParams", - "useDoubleURIEncode: false", - "shouldNormalizeURIPath: false", - "expiration: expiration", - "signedBodyHeader: .contentSha256", - "unsignedBody: true", - "signingAlgorithm: .sigv4" - ) - } - - private val s3Service: ServiceShape = - ServiceShape.builder() - .id("com.test#Example") - .addTrait(serviceTraitWithId("S3")) - .version("1.0") - .build() - - private val glacierService: ServiceShape = - ServiceShape.builder() - .id("com.test#Example") - .addTrait(serviceTraitWithId("Glacier")) - .version("1.0") - .build() - - private val s3ControlService: ServiceShape = - ServiceShape.builder() - .id("com.test#Example") - .addTrait(serviceTraitWithId("S3 Control")) - .version("1.0") - .build() - - private val otherService: ServiceShape = - ServiceShape.builder() - .id("com.test#Example") - .addTrait(serviceTraitWithId("DefinitelyNotS3OrGlacier")) - .version("1.0") - .build() - - private fun operation(presignedTrait: Boolean = false): OperationShape { - val builder = OperationShape.builder() - .id("com.test#Operation") - if (presignedTrait) builder.addTrait(UnsignedPayloadTrait()) - return builder.build() - } - private fun serviceTraitWithId(sdkID: String): ServiceTrait { - return ServiceTrait.builder().sdkId(sdkID).arnNamespace("aws::example").cloudFormationName("ExampleService").cloudTrailEventSource("ExampleService").build() - } -} diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4UtilsTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4UtilsTests.kt new file mode 100644 index 00000000000..63effa1649a --- /dev/null +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/SigV4UtilsTests.kt @@ -0,0 +1,64 @@ +package software.amazon.smithy.aws.swift.codegen + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import software.amazon.smithy.aws.traits.auth.SigV4Trait +import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.traits.AuthTrait +import software.amazon.smithy.model.traits.HttpBasicAuthTrait + +class SigV4UtilsTests { + @Test + fun `service has SigV4Trait and operation has auth trait`() { + val sigV4Trait = SigV4Trait.builder().name("ExampleService").build() + val authList = listOf(HttpBasicAuthTrait().toShapeId(), sigV4Trait.toShapeId()) + + val serviceShape = ServiceShape.builder() + .id("com.test#Example") + .version("1.0") + .addTrait(sigV4Trait) + .build() + val operationShape = OperationShape.builder() + .id("com.test#ExampleOperation") + .addTrait(UnsignedPayloadTrait()) + .addTrait(AuthTrait(authList)) + .build() + val model = Model.builder() + .addShape(serviceShape) + .addShape(operationShape) + .build() + + val hasAuthScheme = SigV4Utils.hasSigV4AuthScheme(model, serviceShape, operationShape) + assertTrue(hasAuthScheme) + } + + @Test + fun `service has SigV4trait but operation does not have auth`() { + val serviceShape = ServiceShape.builder() + .id("com.test#Example") + .version("1.0") + .addTrait(SigV4Trait.builder().name("ExampleService").build()) + .build() + val outputShape = StructureShape.builder() + .id("com.test#ExampleOutput") + .build() + val operationShape = OperationShape.builder() + .id("com.test#ExampleOperation") + .output { ShapeId.from("com.test#ExampleOutput") } + .build() + val model = Model.builder() + .addShape(serviceShape) + .addShape(operationShape) + .addShape(outputShape) + .build() + + val hasAuthScheme = SigV4Utils.hasSigV4AuthScheme(model, serviceShape, operationShape) + assertFalse(hasAuthScheme) + } +} diff --git a/packageDependencies.plist b/packageDependencies.plist index 5dddfa9c485..e86d5a60bc4 100644 --- a/packageDependencies.plist +++ b/packageDependencies.plist @@ -5,7 +5,7 @@ awsCRTSwiftBranch main awsCRTSwiftVersion - 0.22.0 + 0.26.0 clientRuntimeBranch main clientRuntimeVersion diff --git a/scripts/integration-test-sdk.properties b/scripts/integration-test-sdk.properties index cf01c8bd508..9ca16b9ea2b 100644 --- a/scripts/integration-test-sdk.properties +++ b/scripts/integration-test-sdk.properties @@ -1,2 +1,2 @@ # Only include services needed for running integration tests -onlyIncludeModels=kinesis.2013-12-02,s3.2006-03-01,sso-admin.2020-07-20,translate.2017-07-01,sqs.2012-11-05,ec2.2016-11-15,mediaconvert.2017-08-29,ecs.2014-11-13,cloudwatch-logs.2014-03-28,iam.2010-05-08,sts.2011-06-15 +onlyIncludeModels=kinesis.2013-12-02,s3.2006-03-01,sso-admin.2020-07-20,translate.2017-07-01,sqs.2012-11-05,ec2.2016-11-15,mediaconvert.2017-08-29,ecs.2014-11-13,cloudwatch-logs.2014-03-28,iam.2010-05-08,sts.2011-06-15,s3-control.2018-08-20,sts.2011-06-15,eventbridge.2015-10-07,cloudfront.2020-05-31,cloudfront-keyvaluestore.2022-07-26,route-53.2013-04-01