diff --git a/AmplifyPlugins/API/APICategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/API/APICategoryPlugin.xcodeproj/project.pbxproj index b2ed54adea..8310037835 100644 --- a/AmplifyPlugins/API/APICategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/API/APICategoryPlugin.xcodeproj/project.pbxproj @@ -147,6 +147,7 @@ B478F6E12374E0CF00C4F92B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B478F6D52374E0CF00C4F92B /* Assets.xcassets */; }; B478F6E22374E0CF00C4F92B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B478F6D62374E0CF00C4F92B /* LaunchScreen.storyboard */; }; B478F6E32374E0CF00C4F92B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B478F6D82374E0CF00C4F92B /* Main.storyboard */; }; + B48D04AF29EE203F000A73BD /* HeaderIAMSigningHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48D04AE29EE203F000A73BD /* HeaderIAMSigningHelper.swift */; }; B4DFA5E0237A611D0013E17B /* MockSessionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DFA5C0237A611D0013E17B /* MockSessionFactory.swift */; }; B4DFA5E1237A611D0013E17B /* MockURLSessionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DFA5C1237A611D0013E17B /* MockURLSessionTask.swift */; }; B4DFA5E2237A611D0013E17B /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DFA5C2237A611D0013E17B /* MockURLSession.swift */; }; @@ -524,6 +525,7 @@ B478F6D72374E0CF00C4F92B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B478F6D92374E0CF00C4F92B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B478F6DA2374E0CF00C4F92B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B48D04AE29EE203F000A73BD /* HeaderIAMSigningHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderIAMSigningHelper.swift; sourceTree = ""; }; B4DFA5C0237A611D0013E17B /* MockSessionFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockSessionFactory.swift; sourceTree = ""; }; B4DFA5C1237A611D0013E17B /* MockURLSessionTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLSessionTask.swift; sourceTree = ""; }; B4DFA5C2237A611D0013E17B /* MockURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; @@ -900,6 +902,7 @@ 21D5286624169E74005186BA /* IAMAuthInterceptor.swift */, 763C857026B0651A005164B2 /* AuthenticationTokenAuthInterceptor.swift */, 6BD4620525380EA200906831 /* OIDCAuthProviderWrapper.swift */, + B48D04AE29EE203F000A73BD /* HeaderIAMSigningHelper.swift */, ); path = SubscriptionInterceptor; sourceTree = ""; @@ -2461,6 +2464,7 @@ buildActionMask = 2147483647; files = ( 21D7A102237B54D90057D00D /* URLSessionBehaviorDelegate.swift in Sources */, + B48D04AF29EE203F000A73BD /* HeaderIAMSigningHelper.swift in Sources */, 21D7A0FD237B54D90057D00D /* URLSession+URLSessionBehavior.swift in Sources */, 212B29212592454400593ED5 /* AppSyncListPayload.swift in Sources */, 21A4F35A25A4F2E800E1047D /* AppSyncListResponse.swift in Sources */, diff --git a/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/HeaderIAMSigningHelper.swift b/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/HeaderIAMSigningHelper.swift new file mode 100644 index 0000000000..8896472fc9 --- /dev/null +++ b/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/HeaderIAMSigningHelper.swift @@ -0,0 +1,196 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import AWSCore +import AppSyncRealTimeClient + +struct HeaderIAMSigningHelper { + + let host: String + let date: String + let payload: String + let awsEndpoint: AWSEndpoint + let region: AWSRegionType + let endpointURL: URL + + private static let defaultLowercasedHeaderKeys: Set = [ + SubscriptionConstants.authorizationkey.lowercased(), + RealtimeProviderConstants.acceptKey.lowercased(), + RealtimeProviderConstants.contentEncodingKey.lowercased(), + RealtimeProviderConstants.contentTypeKey.lowercased(), + RealtimeProviderConstants.amzDate.lowercased(), + RealtimeProviderConstants.iamSecurityTokenKey.lowercased()] + + init?(endpoint: URL, + payload: String, + region: AWSRegionType, + dateString: String? = nil + ) { + guard let host = endpoint.host else { + return nil + } + let amzDate = NSDate.aws_clockSkewFixed() as NSDate + guard let date = dateString ?? amzDate.aws_stringValue(AWSDateISO8601DateFormat2) else { + return nil + } + guard let awsEndpoint = AWSEndpoint( + region: region, + serviceName: SubscriptionConstants.appsyncServiceName, + url: endpoint) else { + return nil + } + self.date = date + self.host = host + self.region = region + self.payload = payload + self.endpointURL = endpoint + self.awsEndpoint = awsEndpoint + } + + func sign(authProvider: AWSCredentialsProvider, + completion: @escaping (IAMAuthenticationHeader) -> Void) { + let signer: AWSSignatureV4Signer = AWSSignatureV4Signer( + credentialsProvider: authProvider, + endpoint: awsEndpoint) + let mutableRequest = NSMutableURLRequest(url: endpointURL) + sign(signer: signer, mutableRequest: mutableRequest, completion: completion) + } + + /// The process of getting the auth header for an IAM based authencation request is as follows: + /// + /// 1. A request is created with the IAM based auth headers (date, accept, content encoding, content type, and + /// additional headers from the `request`. + /// + /// 2. The request is SigV4 signed by using all the available headers on the request. + /// By signing the request, the signature is added to + /// the request headers as authorization and security token. + /// + /// 3. The signed request headers are stored in an `IAMAuthenticationHeader` object, used for further encoding to + /// be added to the request for establishing the subscription connection. + func sign( + signer: AWSSignatureV4Signer, + mutableRequest: NSMutableURLRequest, + completion: @escaping (IAMAuthenticationHeader) -> Void) { + + mutableRequest.httpMethod = "POST" + + mutableRequest.addValue(RealtimeProviderConstants.iamAccept, + forHTTPHeaderField: RealtimeProviderConstants.acceptKey) + mutableRequest.addValue(date, forHTTPHeaderField: RealtimeProviderConstants.amzDate) + mutableRequest.addValue(RealtimeProviderConstants.iamEncoding, + forHTTPHeaderField: RealtimeProviderConstants.contentEncodingKey) + mutableRequest.addValue(RealtimeProviderConstants.iamConentType, + forHTTPHeaderField: RealtimeProviderConstants.contentTypeKey) + mutableRequest.httpBody = payload.data(using: .utf8) + + signer.interceptRequest(mutableRequest).continueWith { _ in + + let authorization = mutableRequest.allHTTPHeaderFields?[SubscriptionConstants.authorizationkey] ?? "" + let securityToken = mutableRequest.allHTTPHeaderFields?[RealtimeProviderConstants.iamSecurityTokenKey] ?? "" + let additionalHeaders = mutableRequest.allHTTPHeaderFields?.filter { + !Self.defaultLowercasedHeaderKeys.contains($0.key.lowercased()) + } + + let header = IAMAuthenticationHeader( + host: host, + authorization: authorization, + securityToken: securityToken, + amzDate: date, + accept: RealtimeProviderConstants.iamAccept, + contentEncoding: RealtimeProviderConstants.iamEncoding, + contentType: RealtimeProviderConstants.iamConentType, + additionalHeaders: additionalHeaders) + completion(header) + return nil + } + } +} + +/// Stores the headers for an IAM based authentication. This object can be serialized to a JSON object and passed as the +/// headers value for establishing subscription connections. This is used as part of the overall interceptor logic +/// which expects a subclass of `AuthenticationHeader` to be returned. +/// See `IAMAuthInterceptor.getAuthHeader` for more details. +class IAMAuthenticationHeader: AuthenticationHeader { + let authorization: String + let securityToken: String + let amzDate: String + let accept: String + let contentEncoding: String + let contentType: String + + /// Additional headers that are not one of the expected headers in the request, but because additional headers are + /// also signed (and added the authorization header), they are required to be stored here to be further encoded. + let additionalHeaders: [String: String]? + + init(host: String, + authorization: String, + securityToken: String, + amzDate: String, + accept: String, + contentEncoding: String, + contentType: String, + additionalHeaders: [String: String]?) { + self.authorization = authorization + self.securityToken = securityToken + self.amzDate = amzDate + self.accept = accept + self.contentEncoding = contentEncoding + self.contentType = contentType + self.additionalHeaders = additionalHeaders + super.init(host: host) + } + + private struct DynamicCodingKeys: CodingKey { + var stringValue: String + init?(stringValue: String) { + self.stringValue = stringValue + } + var intValue: Int? + init?(intValue: Int) { + // We are not using this, thus just return nil. If we don't return nil, then it is expected all of the + // stored properties are initialized, forcing the implementation to have logic that maintains the two + // properties `stringValue` and `intValue`. Since we don't have a string representation of an int value + // and aren't using int values for determining the coding key, then simply return nil since the encoder + // will always pass in the header key string. + self.intValue = intValue + self.stringValue = "" + + } + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DynamicCodingKeys.self) + // Force unwrapping when creating a `DynamicCodingKeys` will always be successful since the + // string constructor will never return nil even though the constructor is optional + // (conformance to CodingKey). + try container.encode( + authorization, + forKey: DynamicCodingKeys(stringValue: SubscriptionConstants.authorizationkey)!) + try container.encode( + securityToken, + forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.iamSecurityTokenKey)!) + try container.encode( + amzDate, + forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.amzDate)!) + try container.encode( + accept, + forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.acceptKey)!) + try container.encode( + contentEncoding, + forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.contentEncodingKey)!) + try container.encode( + contentType, + forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.contentTypeKey)!) + if let headers = additionalHeaders { + for (key, value) in headers { + try container.encode(value, forKey: DynamicCodingKeys(stringValue: key)!) + } + } + try super.encode(to: encoder) + } +} diff --git a/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift b/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift index 92a6213a68..2b1cbc5c36 100644 --- a/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift +++ b/AmplifyPlugins/API/AWSAPICategoryPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift @@ -13,13 +13,6 @@ import AppSyncRealTimeClient class IAMAuthInterceptor: AuthInterceptor { - private static let defaultLowercasedHeaderKeys: Set = [SubscriptionConstants.authorizationkey.lowercased(), - RealtimeProviderConstants.acceptKey.lowercased(), - RealtimeProviderConstants.contentEncodingKey.lowercased(), - RealtimeProviderConstants.contentTypeKey.lowercased(), - RealtimeProviderConstants.amzDate.lowercased(), - RealtimeProviderConstants.iamSecurityTokenKey.lowercased()] - let authProvider: AWSCredentialsProvider let region: AWSRegionType @@ -28,193 +21,137 @@ class IAMAuthInterceptor: AuthInterceptor { self.region = region } - func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage { - switch message.messageType { - case .subscribe: - let authHeader = getAuthHeader(endpoint, with: message.payload?.data ?? "") - var payload = message.payload ?? AppSyncMessage.Payload() - payload.authHeader = authHeader - let signedMessage = AppSyncMessage(id: message.id, - payload: payload, - type: message.messageType) - return signedMessage - default: - Amplify.API.log.verbose("Message type does not need signing - \(message.messageType)") - } - return message - } + func interceptMessage( + _ message: AppSyncMessage, + for endpoint: URL, + completion: @escaping (AppSyncMessage) -> Void) { - func interceptConnection(_ request: AppSyncConnectionRequest, - for endpoint: URL) -> AppSyncConnectionRequest { - let url = endpoint.appendingPathComponent(RealtimeProviderConstants.iamConnectPath) - let payloadString = SubscriptionConstants.emptyPayload - guard let authHeader = getAuthHeader(url, with: payloadString) else { - return request + guard case .subscribe = message.messageType else { + Amplify.API.log.verbose("Message type does not need signing - \(message.messageType)") + completion(message) + return + } + guard let helper = HeaderIAMSigningHelper( + endpoint: endpoint, + payload: message.payload?.data ?? "", + region: region) else { + completion(message) + return + } + helper.sign(authProvider: authProvider) { signedHeader in + var payload = message.payload ?? AppSyncMessage.Payload() + payload.authHeader = signedHeader + let signedMessage = AppSyncMessage( + id: message.id, + payload: payload, + type: message.messageType) + completion(signedMessage) + return + } } - let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader) - let payloadData = payloadString.data(using: .utf8) - let payloadBase64 = payloadData?.base64EncodedString() + func interceptConnection( + _ request: AppSyncConnectionRequest, + for endpoint: URL, + completion: @escaping (AppSyncConnectionRequest) -> Void) { + let url = endpoint.appendingPathComponent(RealtimeProviderConstants.iamConnectPath) + let payloadString = SubscriptionConstants.emptyPayload + + guard let helper = HeaderIAMSigningHelper( + endpoint: url, + payload: payloadString, + region: region) else { + completion(request) + return + } - guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else { - return request - } - let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth) - let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64) - urlComponents.queryItems = [headerQuery, payloadQuery] - guard let signedUrl = urlComponents.url else { - return request + helper.sign(authProvider: authProvider) { signedHeader in + let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(signedHeader) + + let payloadData = payloadString.data(using: .utf8) + let payloadBase64 = payloadData?.base64EncodedString() + + guard var urlComponents = URLComponents( + url: request.url, + resolvingAgainstBaseURL: false) else { + completion(request) + return + } + let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth) + let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64) + urlComponents.queryItems = [headerQuery, payloadQuery] + guard let signedUrl = urlComponents.url else { + completion(request) + return + } + let signedRequest = AppSyncConnectionRequest(url: signedUrl) + completion(signedRequest) + return + } } - let signedRequest = AppSyncConnectionRequest(url: signedUrl) - return signedRequest - } - final private func getAuthHeader(_ endpoint: URL, with payload: String) -> IAMAuthenticationHeader? { - guard let host = endpoint.host else { - return nil - } - let amzDate = NSDate.aws_clockSkewFixed() as NSDate - guard let date = amzDate.aws_stringValue(AWSDateISO8601DateFormat2) else { - return nil - } - guard let awsEndpoint = AWSEndpoint(region: region, - serviceName: SubscriptionConstants.appsyncServiceName, - url: endpoint) else { - return nil - } - let signer: AWSSignatureV4Signer = AWSSignatureV4Signer(credentialsProvider: authProvider, - endpoint: awsEndpoint) - let mutableRequest = NSMutableURLRequest(url: endpoint) - return getAuthHeader(host: host, - mutableRequest: mutableRequest, - signer: signer, - amzDate: date, - payload: payload) - } + func interceptMessage( + _ message: AppSyncMessage, + for endpoint: URL) -> AppSyncMessage { - /// The process of getting the auth header for an IAM based authencation request is as follows: - /// - /// 1. A request is created with the IAM based auth headers (date, accept, content encoding, content type, and - /// additional headers from the `mutableRequest`. - /// - /// 2. The request is SigV4 signed by using all the available headers on the request. - /// By signing the request, the signature is added to - /// the request headers as authorization and security token. - /// - /// 3. The signed request headers are stored in an `IAMAuthenticationHeader` object, used for further encoding to - /// be added to the request for establishing the subscription connection. - func getAuthHeader(host: String, - mutableRequest: NSMutableURLRequest, - signer: AWSSignatureV4Signer, - amzDate: String, - payload: String) -> IAMAuthenticationHeader { - let semaphore = DispatchSemaphore(value: 0) - mutableRequest.httpMethod = "POST" - mutableRequest.addValue(RealtimeProviderConstants.iamAccept, - forHTTPHeaderField: RealtimeProviderConstants.acceptKey) - mutableRequest.addValue(amzDate, forHTTPHeaderField: RealtimeProviderConstants.amzDate) - mutableRequest.addValue(RealtimeProviderConstants.iamEncoding, - forHTTPHeaderField: RealtimeProviderConstants.contentEncodingKey) - mutableRequest.addValue(RealtimeProviderConstants.iamConentType, - forHTTPHeaderField: RealtimeProviderConstants.contentTypeKey) - mutableRequest.httpBody = payload.data(using: .utf8) - - signer.interceptRequest(mutableRequest).continueWith { _ in - semaphore.signal() - return nil + guard case .subscribe = message.messageType else { + Amplify.API.log.verbose("Message type does not need signing - \(message.messageType)") + return message + } + let authHeader = getAuthHeader(endpoint, with: message.payload?.data ?? "") + var payload = message.payload ?? AppSyncMessage.Payload() + payload.authHeader = authHeader + let signedMessage = AppSyncMessage( + id: message.id, + payload: payload, + type: message.messageType) + return signedMessage } - semaphore.wait() - let authorization = mutableRequest.allHTTPHeaderFields?[SubscriptionConstants.authorizationkey] ?? "" - let securityToken = mutableRequest.allHTTPHeaderFields?[RealtimeProviderConstants.iamSecurityTokenKey] ?? "" - let additionalHeaders = mutableRequest.allHTTPHeaderFields?.filter { - !Self.defaultLowercasedHeaderKeys.contains($0.key.lowercased()) - } + func interceptConnection( + _ request: AppSyncConnectionRequest, + for endpoint: URL) -> AppSyncConnectionRequest { - return IAMAuthenticationHeader(host: host, - authorization: authorization, - securityToken: securityToken, - amzDate: amzDate, - accept: RealtimeProviderConstants.iamAccept, - contentEncoding: RealtimeProviderConstants.iamEncoding, - contentType: RealtimeProviderConstants.iamConentType, - additionalHeaders: additionalHeaders) - } -} + let url = endpoint.appendingPathComponent(RealtimeProviderConstants.iamConnectPath) + let payloadString = SubscriptionConstants.emptyPayload + guard let authHeader = getAuthHeader(url, with: payloadString) else { + return request + } + let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader) -/// Stores the headers for an IAM based authentication. This object can be serialized to a JSON object and passed as the -/// headers value for establishing subscription connections. This is used as part of the overall interceptor logic -/// which expects a subclass of `AuthenticationHeader` to be returned. -/// See `IAMAuthInterceptor.getAuthHeader` for more details. -class IAMAuthenticationHeader: AuthenticationHeader { - let authorization: String - let securityToken: String - let amzDate: String - let accept: String - let contentEncoding: String - let contentType: String - - /// Additional headers that are not one of the expected headers in the request, but because additional headers are - /// also signed (and added the authorization header), they are required to be stored here to be further encoded. - let additionalHeaders: [String: String]? - - init(host: String, - authorization: String, - securityToken: String, - amzDate: String, - accept: String, - contentEncoding: String, - contentType: String, - additionalHeaders: [String: String]?) { - self.authorization = authorization - self.securityToken = securityToken - self.amzDate = amzDate - self.accept = accept - self.contentEncoding = contentEncoding - self.contentType = contentType - self.additionalHeaders = additionalHeaders - super.init(host: host) - } + let payloadData = payloadString.data(using: .utf8) + let payloadBase64 = payloadData?.base64EncodedString() - private struct DynamicCodingKeys: CodingKey { - var stringValue: String - init?(stringValue: String) { - self.stringValue = stringValue + guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else { + return request + } + let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth) + let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64) + urlComponents.queryItems = [headerQuery, payloadQuery] + guard let signedUrl = urlComponents.url else { + return request + } + let signedRequest = AppSyncConnectionRequest(url: signedUrl) + return signedRequest } - var intValue: Int? - init?(intValue: Int) { - // We are not using this, thus just return nil. If we don't return nil, then it is expected all of the - // stored properties are initialized, forcing the implementation to have logic that maintains the two - // properties `stringValue` and `intValue`. Since we don't have a string representation of an int value - // and aren't using int values for determining the coding key, then simply return nil since the encoder - // will always pass in the header key string. - self.intValue = intValue - self.stringValue = "" - } - } + func getAuthHeader( + _ endpoint: URL, + with payload: String) -> IAMAuthenticationHeader? { - override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: DynamicCodingKeys.self) - // Force unwrapping when creating a `DynamicCodingKeys` will always be successful since the string constructor - // will never return nil even though the constructor is optional (conformance to CodingKey). - try container.encode(authorization, - forKey: DynamicCodingKeys(stringValue: SubscriptionConstants.authorizationkey)!) - try container.encode(securityToken, - forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.iamSecurityTokenKey)!) - try container.encode(amzDate, - forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.amzDate)!) - try container.encode(accept, - forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.acceptKey)!) - try container.encode(contentEncoding, - forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.contentEncodingKey)!) - try container.encode(contentType, - forKey: DynamicCodingKeys(stringValue: RealtimeProviderConstants.contentTypeKey)!) - if let headers = additionalHeaders { - for (key, value) in headers { - try container.encode(value, forKey: DynamicCodingKeys(stringValue: key)!) + guard let helper = HeaderIAMSigningHelper( + endpoint: endpoint, + payload: payload, + region: region) else { + return nil } + var signedHeader: IAMAuthenticationHeader? + let semaphore = DispatchSemaphore(value: 0) + helper.sign(authProvider: authProvider) { header in + signedHeader = header + semaphore.signal() + } + semaphore.wait() + return signedHeader } - try super.encode(to: encoder) - } } diff --git a/AmplifyPlugins/API/AWSAPICategoryPluginTests/Interceptor/SubscriptionInterceptor/IAMAuthInterceptorTests.swift b/AmplifyPlugins/API/AWSAPICategoryPluginTests/Interceptor/SubscriptionInterceptor/IAMAuthInterceptorTests.swift index 5672e46245..d8dc444a8f 100644 --- a/AmplifyPlugins/API/AWSAPICategoryPluginTests/Interceptor/SubscriptionInterceptor/IAMAuthInterceptorTests.swift +++ b/AmplifyPlugins/API/AWSAPICategoryPluginTests/Interceptor/SubscriptionInterceptor/IAMAuthInterceptorTests.swift @@ -94,24 +94,23 @@ class IAMAuthInterceptorTests: XCTestCase { } func testInterceptConnection() { - let mockAuthService = MockAWSAuthService() - let interceptor = IAMAuthInterceptor(mockAuthService.getCredentialsProvider(), region: .USWest2) let url = URL(string: "https://abc.appsync-api.us-west-2.amazonaws.com/graphql")! let request = NSMutableURLRequest(url: url) request.addValue("headerValue", forHTTPHeaderField: "extra-header") let signer = MockAWSSignatureV4Signer() - let authHeader = interceptor.getAuthHeader(host: "host", - mutableRequest: request, - signer: signer, - amzDate: "date", - payload: "payload") - - XCTAssertNotNil(authHeader.authorization) - XCTAssertNotNil(authHeader.securityToken) - XCTAssertEqual(authHeader.amzDate, "date") - XCTAssertEqual(authHeader.accept, "application/json, text/javascript") - XCTAssertEqual(authHeader.contentEncoding, "amz-1.0") - XCTAssertEqual(authHeader.contentType, "application/json; charset=UTF-8") - XCTAssertEqual(authHeader.additionalHeaders, ["extra-header": "headerValue"]) + let signingHelper = HeaderIAMSigningHelper( + endpoint: url, + payload: "payload", + region: .USWest2, + dateString: "date") + signingHelper?.sign(signer: signer, mutableRequest: request) { authHeader in + XCTAssertNotNil(authHeader.authorization) + XCTAssertNotNil(authHeader.securityToken) + XCTAssertEqual(authHeader.amzDate, "date") + XCTAssertEqual(authHeader.accept, "application/json, text/javascript") + XCTAssertEqual(authHeader.contentEncoding, "amz-1.0") + XCTAssertEqual(authHeader.contentType, "application/json; charset=UTF-8") + XCTAssertEqual(authHeader.additionalHeaders, ["extra-header": "headerValue"]) + } } } diff --git a/AmplifyPlugins/API/Podfile.lock b/AmplifyPlugins/API/Podfile.lock index 6da66ac4d6..bdf6af223a 100644 --- a/AmplifyPlugins/API/Podfile.lock +++ b/AmplifyPlugins/API/Podfile.lock @@ -62,7 +62,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - AppSyncRealTimeClient - AWSAuthCore - AWSCognitoIdentityProvider - AWSCognitoIdentityProviderASF @@ -83,6 +82,9 @@ EXTERNAL SOURCES: :path: "../../" AmplifyTestCommon: :path: "../../" + AppSyncRealTimeClient: + :branch: main + :git: https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git AWSPluginsCore: :path: "../../" CwlPreconditionTesting: @@ -90,6 +92,9 @@ EXTERNAL SOURCES: :tag: 2.1.0 CHECKOUT OPTIONS: + AppSyncRealTimeClient: + :commit: 814d7a42a99371949732fc1e0fd72ad0edfcf6a9 + :git: https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git CwlPreconditionTesting: :git: https://github.com/mattgallagher/CwlPreconditionTesting.git :tag: 2.1.0