Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Interecptors, ConnectionProviderFactory, and integration test target #4

Merged
merged 3 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions AppSyncRealTimeClient.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = 'AppSyncRealTimeClient'
s.version = '1.0.1'
s.version = '1.0.2'
s.summary = 'Amazon Web Services AppSync RealTime Client for iOS.'

s.description = 'AppSync RealTime Client provides subscription connections to AppSync websocket endpoints'
Expand All @@ -17,5 +17,4 @@ Pod::Spec.new do |s|

s.source_files = 'AppSyncRealTimeClient/**/*.swift'
s.dependency 'Starscream', '~> 3.0.2'

end
end
452 changes: 449 additions & 3 deletions AppSyncRealTimeClient.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

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>
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>
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 AppSyncRealTimeClient/Interceptor/APIKeyAuthInterceptor.swift
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 AppSyncRealTimeClient/Interceptor/OIDCAuthInterceptor.swift
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)
}
}
Loading