-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Interecptors, ConnectionProviderFactory, and integration test tar…
…get (#4) * - Moved over the three common interceptor classes: APIKey, IAM, OIDC. Refactored the initialization methods to allow consumers to use without taking on a dependency on the auth provider classes vended by AppSync client. The reason for this is to prevent a breaking change, we cannot move the auth provider classes over to AppSyncRealTimeClient or AppSyncClient consumers will have to take on AppSyncRealTimeClient as a dependency. - Add ConnectionProviderFactory class for creating connection providers with auth interceptors. This is the entry point for consumers to pass (url, authInterceptor) to get a connection provider back. - Add integration test target with AppSync backend provisioned * Moved out IAMAuthInterceptor to remove dependency on AWSCore * removed AWSCore from podspec
- Loading branch information
Showing
31 changed files
with
1,500 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
52 changes: 52 additions & 0 deletions
52
...imeClient.xcodeproj/xcshareddata/xcschemes/AppSyncRealTimeClientIntegrationTests.xcscheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<Scheme | ||
LastUpgradeVersion = "1130" | ||
version = "1.3"> | ||
<BuildAction | ||
parallelizeBuildables = "YES" | ||
buildImplicitDependencies = "YES"> | ||
</BuildAction> | ||
<TestAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
shouldUseLaunchSchemeArgsEnv = "YES"> | ||
<Testables> | ||
<TestableReference | ||
skipped = "NO"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "21D38B3D2409AFBD00EC2A8D" | ||
BuildableName = "AppSyncRealTimeClientIntegrationTests.xctest" | ||
BlueprintName = "AppSyncRealTimeClientIntegrationTests" | ||
ReferencedContainer = "container:AppSyncRealTimeClient.xcodeproj"> | ||
</BuildableReference> | ||
</TestableReference> | ||
</Testables> | ||
</TestAction> | ||
<LaunchAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
launchStyle = "0" | ||
useCustomWorkingDirectory = "NO" | ||
ignoresPersistentStateOnLaunch = "NO" | ||
debugDocumentVersioning = "YES" | ||
debugServiceExtension = "internal" | ||
allowLocationSimulation = "YES"> | ||
</LaunchAction> | ||
<ProfileAction | ||
buildConfiguration = "Release" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
savedToolIdentifier = "" | ||
useCustomWorkingDirectory = "NO" | ||
debugDocumentVersioning = "YES"> | ||
</ProfileAction> | ||
<AnalyzeAction | ||
buildConfiguration = "Debug"> | ||
</AnalyzeAction> | ||
<ArchiveAction | ||
buildConfiguration = "Release" | ||
revealArchiveInOrganizer = "YES"> | ||
</ArchiveAction> | ||
</Scheme> |
88 changes: 88 additions & 0 deletions
88
AppSyncRealTimeClient.xcodeproj/xcshareddata/xcschemes/HostApp.xcscheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<Scheme | ||
LastUpgradeVersion = "1130" | ||
version = "1.3"> | ||
<BuildAction | ||
parallelizeBuildables = "YES" | ||
buildImplicitDependencies = "YES"> | ||
<BuildActionEntries> | ||
<BuildActionEntry | ||
buildForTesting = "YES" | ||
buildForRunning = "YES" | ||
buildForProfiling = "YES" | ||
buildForArchiving = "YES" | ||
buildForAnalyzing = "YES"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "21D38B522409B93F00EC2A8D" | ||
BuildableName = "HostApp.app" | ||
BlueprintName = "HostApp" | ||
ReferencedContainer = "container:AppSyncRealTimeClient.xcodeproj"> | ||
</BuildableReference> | ||
</BuildActionEntry> | ||
</BuildActionEntries> | ||
</BuildAction> | ||
<TestAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
shouldUseLaunchSchemeArgsEnv = "YES"> | ||
<Testables> | ||
<TestableReference | ||
skipped = "NO"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "21D38B3D2409AFBD00EC2A8D" | ||
BuildableName = "AppSyncRealTimeClientIntegrationTests.xctest" | ||
BlueprintName = "AppSyncRealTimeClientIntegrationTests" | ||
ReferencedContainer = "container:AppSyncRealTimeClient.xcodeproj"> | ||
</BuildableReference> | ||
</TestableReference> | ||
</Testables> | ||
</TestAction> | ||
<LaunchAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
launchStyle = "0" | ||
useCustomWorkingDirectory = "NO" | ||
ignoresPersistentStateOnLaunch = "NO" | ||
debugDocumentVersioning = "YES" | ||
debugServiceExtension = "internal" | ||
allowLocationSimulation = "YES"> | ||
<BuildableProductRunnable | ||
runnableDebuggingMode = "0"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "21D38B522409B93F00EC2A8D" | ||
BuildableName = "HostApp.app" | ||
BlueprintName = "HostApp" | ||
ReferencedContainer = "container:AppSyncRealTimeClient.xcodeproj"> | ||
</BuildableReference> | ||
</BuildableProductRunnable> | ||
</LaunchAction> | ||
<ProfileAction | ||
buildConfiguration = "Release" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
savedToolIdentifier = "" | ||
useCustomWorkingDirectory = "NO" | ||
debugDocumentVersioning = "YES"> | ||
<BuildableProductRunnable | ||
runnableDebuggingMode = "0"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "21D38B522409B93F00EC2A8D" | ||
BuildableName = "HostApp.app" | ||
BlueprintName = "HostApp" | ||
ReferencedContainer = "container:AppSyncRealTimeClient.xcodeproj"> | ||
</BuildableReference> | ||
</BuildableProductRunnable> | ||
</ProfileAction> | ||
<AnalyzeAction | ||
buildConfiguration = "Debug"> | ||
</AnalyzeAction> | ||
<ArchiveAction | ||
buildConfiguration = "Release" | ||
revealArchiveInOrganizer = "YES"> | ||
</ArchiveAction> | ||
</Scheme> |
37 changes: 37 additions & 0 deletions
37
AppSyncRealTimeClient/ConnectionProvider/ConnectionProviderFactory.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// | ||
// Copyright 2018-2020 Amazon.com, | ||
// Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Foundation | ||
|
||
/// Create connection providers to connect to the websocket endpoint of the AppSync endpoint. | ||
public struct ConnectionProviderFactory { | ||
|
||
public static func createConnectionProvider(for url: URL, | ||
authInterceptor: AuthInterceptor, | ||
connectionType: SubscriptionConnectionType) -> ConnectionProvider { | ||
let provider = ConnectionProviderFactory.createConnectionProvider(for: url, connectionType: connectionType) | ||
|
||
if let messageInterceptable = provider as? MessageInterceptable { | ||
messageInterceptable.addInterceptor(authInterceptor) | ||
} | ||
if let connectionInterceptable = provider as? ConnectionInterceptable { | ||
connectionInterceptable.addInterceptor(RealtimeGatewayURLInterceptor()) | ||
connectionInterceptable.addInterceptor(authInterceptor) | ||
} | ||
|
||
return provider | ||
} | ||
|
||
static func createConnectionProvider(for url: URL, connectionType: SubscriptionConnectionType) -> ConnectionProvider { | ||
switch connectionType { | ||
case .appSyncRealtime: | ||
let websocketProvider = StarscreamAdapter() | ||
let connectionProvider = RealtimeConnectionProvider(for: url, websocket: websocketProvider) | ||
return connectionProvider | ||
} | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
AppSyncRealTimeClient/Interceptor/APIKeyAuthInterceptor.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// | ||
// Copyright 2018-2020 Amazon.com, | ||
// Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Foundation | ||
|
||
/// Auth interceptor for API Key based authentication | ||
public class APIKeyAuthInterceptor: AuthInterceptor { | ||
|
||
let apiKey: String | ||
|
||
public init(_ apiKey: String) { | ||
self.apiKey = apiKey | ||
} | ||
|
||
/// Intercept the connection and adds header, payload query to the request url. | ||
/// | ||
/// The value of header should be the base64 string of the following: | ||
/// * "host": <string> : this is the host for the AppSync endpoint | ||
/// * "x-amz-date": <string> : UTC timestamp in the following ISO 8601 format: YYYYMMDD'T'HHMMSS'Z' | ||
/// * "x-api-key": <string> : Api key configured for AppSync API | ||
/// The value of payload is {} | ||
/// - Parameter request: Signed request | ||
public func interceptConnection(_ request: AppSyncConnectionRequest, | ||
for endpoint: URL) -> AppSyncConnectionRequest { | ||
let host = endpoint.host! | ||
let authHeader = APIKeyAuthenticationHeader(apiKey: apiKey, host: host) | ||
let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader) | ||
|
||
let payloadData = SubscriptionConstants.emptyPayload.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 url = urlComponents.url else { | ||
return request | ||
} | ||
let signedRequest = AppSyncConnectionRequest(url: url) | ||
return signedRequest | ||
} | ||
|
||
public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage { | ||
let host = endpoint.host! | ||
switch message.messageType { | ||
case .subscribe: | ||
let authHeader = APIKeyAuthenticationHeader(apiKey: apiKey, host: host) | ||
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 | ||
} | ||
} | ||
|
||
/// 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) { | ||
self.date = formatter.string(from: Date()) | ||
self.apiKey = apiKey | ||
super.init(host: host) | ||
} | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case date = "x-amz-date" | ||
case apiKey = "x-api-key" | ||
} | ||
|
||
override func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(date, forKey: .date) | ||
try container.encode(apiKey, forKey: .apiKey) | ||
try super.encode(to: encoder) | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
AppSyncRealTimeClient/Interceptor/OIDCAuthInterceptor.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// | ||
// Copyright 2018-2020 Amazon.com, | ||
// Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Foundation | ||
|
||
public class OIDCAuthInterceptor: AuthInterceptor { | ||
|
||
let authProvider: OIDCAuthProvider | ||
|
||
public init (_ authProvider: OIDCAuthProvider) { | ||
self.authProvider = authProvider | ||
} | ||
|
||
public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage { | ||
let host = endpoint.host! | ||
let jwtToken: String | ||
switch authProvider.getLatestAuthToken() { | ||
case .success(let token): | ||
jwtToken = token | ||
case .failure: | ||
return message | ||
} | ||
switch message.messageType { | ||
case .subscribe: | ||
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host) | ||
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 host = endpoint.host! | ||
let jwtToken: String | ||
switch authProvider.getLatestAuthToken() { | ||
case .success(let token): | ||
jwtToken = token | ||
case .failure: | ||
return request | ||
} | ||
|
||
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host) | ||
let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader) | ||
|
||
let payloadData = SubscriptionConstants.emptyPayload.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 url = urlComponents.url else { | ||
return request | ||
} | ||
let signedRequest = AppSyncConnectionRequest(url: url) | ||
return signedRequest | ||
} | ||
} | ||
|
||
/// Authentication header for user pool based auth | ||
private class UserPoolsAuthenticationHeader: AuthenticationHeader { | ||
let authorization: String | ||
|
||
init(token: String, host: String) { | ||
self.authorization = token | ||
super.init(host: host) | ||
} | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case authorization = "Authorization" | ||
} | ||
|
||
override func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(authorization, forKey: .authorization) | ||
try super.encode(to: encoder) | ||
} | ||
} |
Oops, something went wrong.