From a05da67f57d55c809dbaee00e8520dbb8a9af5c1 Mon Sep 17 00:00:00 2001 From: Michael law <1365977+lawmicha@users.noreply.github.com> Date: Mon, 9 Mar 2020 09:17:29 -0700 Subject: [PATCH] Moved out IAMAuthInterceptor to remove dependency on AWSCore --- .../project.pbxproj | 8 - .../Interceptor/APIKeyAuthInterceptor.swift | 13 +- .../Interceptor/IAMAuthInterceptor.swift | 143 ------------------ .../Interceptor/OIDCAuthInterceptor.swift | 22 +-- .../Support/OIDCAuthProvider.swift | 2 +- .../Interceptor/IAMAuthInterceptorTests.swift | 57 ------- .../OIDCAuthInterceptorTests.swift | 5 +- Podfile | 2 - 8 files changed, 24 insertions(+), 228 deletions(-) delete mode 100644 AppSyncRealTimeClient/Interceptor/IAMAuthInterceptor.swift delete mode 100644 AppSyncRealTimeClientTests/Interceptor/IAMAuthInterceptorTests.swift diff --git a/AppSyncRealTimeClient.xcodeproj/project.pbxproj b/AppSyncRealTimeClient.xcodeproj/project.pbxproj index 937e1d51..e51809cb 100644 --- a/AppSyncRealTimeClient.xcodeproj/project.pbxproj +++ b/AppSyncRealTimeClient.xcodeproj/project.pbxproj @@ -52,11 +52,9 @@ 21D38B622409B94100EC2A8D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 21D38B602409B94100EC2A8D /* LaunchScreen.storyboard */; }; 21D38B692409B95B00EC2A8D /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21D38B4B2409B6C000EC2A8D /* amplifyconfiguration.json */; }; 21D38B6D240A262800EC2A8D /* AppSyncJSONHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B6C240A262800EC2A8D /* AppSyncJSONHelper.swift */; }; - 21D38B7B240A2A1300EC2A8D /* IAMAuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B77240A2A1300EC2A8D /* IAMAuthInterceptor.swift */; }; 21D38B7C240A2A1300EC2A8D /* OIDCAuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B78240A2A1300EC2A8D /* OIDCAuthInterceptor.swift */; }; 21D38B83240A392B00EC2A8D /* APIKeyAuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B82240A392B00EC2A8D /* APIKeyAuthInterceptor.swift */; }; 21D38B89240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B85240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift */; }; - 21D38B8A240A39E400EC2A8D /* IAMAuthInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B86240A39E400EC2A8D /* IAMAuthInterceptorTests.swift */; }; 21D38B8B240A39E400EC2A8D /* AppSyncJSONHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B87240A39E400EC2A8D /* AppSyncJSONHelperTests.swift */; }; 21D38B8C240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B88240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift */; }; 21D38B8E240A3C2300EC2A8D /* ConnectionProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B8D240A3C2300EC2A8D /* ConnectionProviderFactory.swift */; }; @@ -146,11 +144,9 @@ 21D38B612409B94100EC2A8D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 21D38B632409B94100EC2A8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 21D38B6C240A262800EC2A8D /* AppSyncJSONHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSyncJSONHelper.swift; sourceTree = ""; }; - 21D38B77240A2A1300EC2A8D /* IAMAuthInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IAMAuthInterceptor.swift; sourceTree = ""; }; 21D38B78240A2A1300EC2A8D /* OIDCAuthInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDCAuthInterceptor.swift; sourceTree = ""; }; 21D38B82240A392B00EC2A8D /* APIKeyAuthInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIKeyAuthInterceptor.swift; sourceTree = ""; }; 21D38B85240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDCAuthInterceptorTests.swift; sourceTree = ""; }; - 21D38B86240A39E400EC2A8D /* IAMAuthInterceptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IAMAuthInterceptorTests.swift; sourceTree = ""; }; 21D38B87240A39E400EC2A8D /* AppSyncJSONHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSyncJSONHelperTests.swift; sourceTree = ""; }; 21D38B88240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIKeyAuthInterceptorTests.swift; sourceTree = ""; }; 21D38B8D240A3C2300EC2A8D /* ConnectionProviderFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionProviderFactory.swift; sourceTree = ""; }; @@ -411,7 +407,6 @@ isa = PBXGroup; children = ( 21D38B82240A392B00EC2A8D /* APIKeyAuthInterceptor.swift */, - 21D38B77240A2A1300EC2A8D /* IAMAuthInterceptor.swift */, 21D38B78240A2A1300EC2A8D /* OIDCAuthInterceptor.swift */, 217F39C82406E98400F1A0B3 /* RealtimeGatewayURLInterceptor.swift */, ); @@ -422,7 +417,6 @@ isa = PBXGroup; children = ( 21D38B85240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift */, - 21D38B86240A39E400EC2A8D /* IAMAuthInterceptorTests.swift */, 21D38B87240A39E400EC2A8D /* AppSyncJSONHelperTests.swift */, 21D38B88240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift */, ); @@ -797,7 +791,6 @@ 217F39D42406E98400F1A0B3 /* RealtimeConnectionProvider+Websocket.swift in Sources */, 217F39DC2406E98400F1A0B3 /* AppSyncSubscriptionConnection.swift in Sources */, 21D38B6D240A262800EC2A8D /* AppSyncJSONHelper.swift in Sources */, - 21D38B7B240A2A1300EC2A8D /* IAMAuthInterceptor.swift in Sources */, 217F39CE2406E98400F1A0B3 /* ConnectionProviderError.swift in Sources */, 217F39E12406E98400F1A0B3 /* StarscreamAdapter.swift in Sources */, 217F39D72406E98400F1A0B3 /* RealtimeConnectionProvider+ConnectionInterceptable.swift in Sources */, @@ -811,7 +804,6 @@ buildActionMask = 2147483647; files = ( 21D38B89240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift in Sources */, - 21D38B8A240A39E400EC2A8D /* IAMAuthInterceptorTests.swift in Sources */, 217F39F22406EA4000F1A0B3 /* RealtimeConnectionProviderTests.swift in Sources */, 217F39F02406EA4000F1A0B3 /* AppSyncSubscriptionConnectionTests.swift in Sources */, 21D38B8C240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift in Sources */, diff --git a/AppSyncRealTimeClient/Interceptor/APIKeyAuthInterceptor.swift b/AppSyncRealTimeClient/Interceptor/APIKeyAuthInterceptor.swift index 0294f0f1..8e6de564 100644 --- a/AppSyncRealTimeClient/Interceptor/APIKeyAuthInterceptor.swift +++ b/AppSyncRealTimeClient/Interceptor/APIKeyAuthInterceptor.swift @@ -6,7 +6,6 @@ // import Foundation -import AWSCore /// Auth interceptor for API Key based authentication public class APIKeyAuthInterceptor: AuthInterceptor { @@ -68,12 +67,20 @@ public class APIKeyAuthInterceptor: AuthInterceptor { /// Authentication header for API key based auth private class APIKeyAuthenticationHeader: AuthenticationHeader { + static let ISO8601DateFormat: String = "yyyyMMdd'T'HHmmss'Z'" let date: String? let apiKey: String + var formatter: DateFormatter = { + var formatter = DateFormatter() + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = ISO8601DateFormat + return formatter + }() + init(apiKey: String, host: String) { - let amzDate = NSDate.aws_clockSkewFixed() as NSDate - self.date = amzDate.aws_stringValue(AWSDateISO8601DateFormat2) + self.date = formatter.string(from: Date()) self.apiKey = apiKey super.init(host: host) } diff --git a/AppSyncRealTimeClient/Interceptor/IAMAuthInterceptor.swift b/AppSyncRealTimeClient/Interceptor/IAMAuthInterceptor.swift deleted file mode 100644 index aac85abb..00000000 --- a/AppSyncRealTimeClient/Interceptor/IAMAuthInterceptor.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import AWSCore - -public class IAMAuthInterceptor: AuthInterceptor { - - let authProvider: AWSCredentialsProvider - let region: AWSRegionType - - public init(_ authProvider: AWSCredentialsProvider, region: AWSRegionType) { - self.authProvider = authProvider - self.region = region - } - - public 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: - AppSyncLogger.debug("Message type does not need signing - \(message.messageType)") - } - return message - } - - public 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 - } - let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader) - - let payloadData = payloadString.data(using: .utf8) - let payloadBase64 = payloadData?.base64EncodedString() - - 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 - } - - 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 - } - let awsEndpoint = AWSEndpoint(region: region, serviceName: SubscriptionConstants.appsyncServiceName, url: endpoint) - let signer: AWSSignatureV4Signer = AWSSignatureV4Signer(credentialsProvider: authProvider, - endpoint: awsEndpoint) - let semaphore = DispatchSemaphore(value: 0) - let mutableRequest = NSMutableURLRequest(url: endpoint) - 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 { task in - semaphore.signal() - return nil - } - semaphore.wait() - let authorization = mutableRequest.allHTTPHeaderFields?[SubscriptionConstants.authorizationkey] ?? "" - let securityToken = mutableRequest.allHTTPHeaderFields?[RealtimeProviderConstants.iamSecurityTokenKey] ?? "" - let authHeader = IAMAuthenticationHeader(authorization: authorization, - host: host, - token: securityToken, - date: date, - accept: RealtimeProviderConstants.iamAccept, - contentEncoding: RealtimeProviderConstants.iamEncoding, - contentType: RealtimeProviderConstants.iamConentType) - return authHeader - } -} - -/// Authentication header for IAM based auth -private class IAMAuthenticationHeader: AuthenticationHeader { - let authorization: String - let securityToken: String - let date: String - let accept: String - let contentEncoding: String - let contentType: String - - init(authorization: String, - host: String, - token: String, - date: String, - accept: String, - contentEncoding: String, - contentType: String) { - self.date = date - self.authorization = authorization - self.securityToken = token - self.accept = accept - self.contentEncoding = contentEncoding - self.contentType = contentType - super.init(host: host) - } - - private enum CodingKeys: String, CodingKey { - case authorization = "Authorization" - case accept - case contentEncoding = "content-encoding" - case contentType = "content-type" - case date = "x-amz-date" - case securityToken = "x-amz-security-token" - } - - override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(authorization, forKey: .authorization) - try container.encode(accept, forKey: .accept) - try container.encode(contentEncoding, forKey: .contentEncoding) - try container.encode(contentType, forKey: .contentType) - try container.encode(date, forKey: .date) - try container.encode(securityToken, forKey: .securityToken) - try super.encode(to: encoder) - } -} diff --git a/AppSyncRealTimeClient/Interceptor/OIDCAuthInterceptor.swift b/AppSyncRealTimeClient/Interceptor/OIDCAuthInterceptor.swift index 645986eb..245e4830 100644 --- a/AppSyncRealTimeClient/Interceptor/OIDCAuthInterceptor.swift +++ b/AppSyncRealTimeClient/Interceptor/OIDCAuthInterceptor.swift @@ -6,7 +6,6 @@ // import Foundation -import AWSCore public class OIDCAuthInterceptor: AuthInterceptor { @@ -18,16 +17,16 @@ public class OIDCAuthInterceptor: AuthInterceptor { public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage { let host = endpoint.host! - var jwtToken: String? - authProvider.getLatestAuthToken { (token, error) in + let jwtToken: String + switch authProvider.getLatestAuthToken() { + case .success(let token): jwtToken = token - } - guard let token = jwtToken else { + case .failure: return message } switch message.messageType { case .subscribe: - let authHeader = UserPoolsAuthenticationHeader(token: token, host: host) + let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host) var payload = message.payload ?? AppSyncMessage.Payload() payload.authHeader = authHeader @@ -43,14 +42,15 @@ public class OIDCAuthInterceptor: AuthInterceptor { public func interceptConnection(_ request: AppSyncConnectionRequest, for endpoint: URL) -> AppSyncConnectionRequest { let host = endpoint.host! - var jwtToken: String? - authProvider.getLatestAuthToken { (token, error) in + let jwtToken: String + switch authProvider.getLatestAuthToken() { + case .success(let token): jwtToken = token - } - guard let token = jwtToken else { + case .failure: return request } - let authHeader = UserPoolsAuthenticationHeader(token: token, host: host) + + let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host) let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader) let payloadData = SubscriptionConstants.emptyPayload.data(using: .utf8) diff --git a/AppSyncRealTimeClient/Support/OIDCAuthProvider.swift b/AppSyncRealTimeClient/Support/OIDCAuthProvider.swift index a99ce8c3..6eda03c6 100644 --- a/AppSyncRealTimeClient/Support/OIDCAuthProvider.swift +++ b/AppSyncRealTimeClient/Support/OIDCAuthProvider.swift @@ -6,5 +6,5 @@ // public protocol OIDCAuthProvider { - func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) + func getLatestAuthToken() -> Result } diff --git a/AppSyncRealTimeClientTests/Interceptor/IAMAuthInterceptorTests.swift b/AppSyncRealTimeClientTests/Interceptor/IAMAuthInterceptorTests.swift deleted file mode 100644 index 642dfa22..00000000 --- a/AppSyncRealTimeClientTests/Interceptor/IAMAuthInterceptorTests.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright 2018-2020 Amazon.com, -// Inc. or its affiliates. All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import XCTest -import AppSyncRealTimeClient -import AWSCore - -class IAMAuthInterceptorTests: XCTestCase { - - var authInterceptor: IAMAuthInterceptor! - - override func setUp() { - authInterceptor = IAMAuthInterceptor(MockIAMAuthProvider(), region: .USWest2) - } - - func testInterceptRequest() { - let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")! - let request = AppSyncConnectionRequest(url: url) - let signedRequest = authInterceptor.interceptConnection(request, for: url) - - guard let queries = URLComponents(url: signedRequest.url, resolvingAgainstBaseURL: true)?.queryItems else { - assertionFailure("Query parameters should not be nil") - return - } - XCTAssertTrue(queries.contains{ $0.name == "header"}, "Should contain the header query") - XCTAssertTrue(queries.contains{ $0.name == "payload"}, "Should contain the payload query") - } - - func testInterceptMessage() { - let message = AppSyncMessage(type: .subscribe("start")) - let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")! - let signedMessage = authInterceptor.interceptMessage(message, for: url) - XCTAssertNotNil(signedMessage.payload?.authHeader) - - } -} - -class MockIAMAuthProvider: NSObject, AWSCredentialsProvider { - - func credentials() -> AWSTask { - let credentials = AWSCredentials(accessKey: "accessKey", - secretKey: "secretKey", - sessionKey: "sessionKey", - expiration: Date()) - return AWSTask(result: credentials) - } - - func invalidateCachedTemporaryCredentials() { - - } - - -} diff --git a/AppSyncRealTimeClientTests/Interceptor/OIDCAuthInterceptorTests.swift b/AppSyncRealTimeClientTests/Interceptor/OIDCAuthInterceptorTests.swift index 45781074..fa7e2275 100644 --- a/AppSyncRealTimeClientTests/Interceptor/OIDCAuthInterceptorTests.swift +++ b/AppSyncRealTimeClientTests/Interceptor/OIDCAuthInterceptorTests.swift @@ -39,8 +39,7 @@ class OIDCAuthInterceptorTests: XCTestCase { } class MockUserPoolsAuthProvider: OIDCAuthProvider { - - func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) { - callback("jwtToken", nil) + func getLatestAuthToken() -> Result { + return .success("jwtToken") } } diff --git a/Podfile b/Podfile index d8160994..42c99084 100644 --- a/Podfile +++ b/Podfile @@ -7,11 +7,9 @@ target 'AppSyncRealTimeClient' do # Pods for AppSyncRealTimeClient pod "Starscream", "~> 3.0.2" - pod "AWSCore", "~> 2.12.7" target 'AppSyncRealTimeClientTests' do # Pods for testing - pod "AWSCore", "~> 2.12.7" end end