Skip to content

Commit 5aaa0da

Browse files
author
Di Wu
authored
fix(datastore): using URLProtocol monitor auth request headers (#3221)
* fix(datastore): using URLProtocol monitor multiAuth request headers * test(datastore): update integration test xcode version * test(datastore): update integration test for auth IAM * remove redundant semicolons
1 parent 514ce12 commit 5aaa0da

9 files changed

+404
-174
lines changed

.github/workflows/integ_test_datastore_auth_iam.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ on:
55

66
permissions:
77
id-token: write
8-
contents: read
8+
contents: read
99

1010
jobs:
1111
datastore-integration-auth-iam-test-iOS:
1212
timeout-minutes: 30
13-
runs-on: macos-12
13+
runs-on: macos-13
1414
environment: IntegrationTest
1515
steps:
1616
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
@@ -33,6 +33,8 @@ jobs:
3333
with:
3434
project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp
3535
scheme: AWSDataStorePluginAuthIAMTests
36+
destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest'
37+
xcode_path: '/Applications/Xcode_14.3.app'
3638

3739
datastore-integration-auth-iam-test-tvOS:
3840
timeout-minutes: 30

.github/workflows/integ_test_datastore_multi_auth.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ permissions:
1010
jobs:
1111
datastore-integration-multi-auth-test-iOS:
1212
timeout-minutes: 30
13-
runs-on: macos-12
13+
runs-on: macos-13
1414
environment: IntegrationTest
1515
steps:
1616
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
@@ -33,6 +33,8 @@ jobs:
3333
with:
3434
project_path: ./AmplifyPlugins/DataStore/Tests/DataStoreHostApp
3535
scheme: AWSDataStorePluginMultiAuthTests
36+
destination: 'platform=iOS Simulator,name=iPhone 14,OS=latest'
37+
xcode_path: '/Applications/Xcode_14.3.app'
3638

3739
datastore-integration-multi-auth-test-tvOS:
3840
timeout-minutes: 30

AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthIAMTests/AWSDataStoreAuthBaseTest.swift

Lines changed: 87 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,46 +23,77 @@ struct TestUser {
2323
let password: String
2424
}
2525

26-
class AuthRecorderInterceptor: URLRequestInterceptor {
27-
let awsAuthService: AWSAuthService = AWSAuthService()
28-
var consumedAuthTypes: Set<AWSAuthorizationType> = []
29-
private let accessQueue = DispatchQueue(label: "com.amazon.AuthRecorderInterceptor.consumedAuthTypes")
30-
31-
private func recordAuthType(_ authType: AWSAuthorizationType) {
32-
accessQueue.async {
33-
self.consumedAuthTypes.insert(authType)
34-
}
35-
}
26+
class DataStoreAuthBaseTestURLSessionFactory: URLSessionBehaviorFactory {
27+
static let testIdHeaderKey = "x-amplify-test"
3628

37-
func intercept(_ request: URLRequest) throws -> URLRequest {
38-
guard let headers = request.allHTTPHeaderFields else {
39-
fatalError("No headers found in request \(request)")
40-
}
29+
static let subject = PassthroughSubject<(String, Set<AWSAuthorizationType>), Never>()
4130

42-
let authHeaderValue = headers["Authorization"]
43-
let apiKeyHeaderValue = headers["x-api-key"]
31+
class Sniffer: URLProtocol {
4432

45-
if apiKeyHeaderValue != nil {
46-
recordAuthType(.apiKey)
47-
}
33+
override class func canInit(with request: URLRequest) -> Bool {
34+
guard let headers = request.allHTTPHeaderFields else {
35+
fatalError("No headers found in request \(request)")
36+
}
37+
38+
guard let testId = headers[DataStoreAuthBaseTestURLSessionFactory.testIdHeaderKey] else {
39+
return false
40+
}
41+
42+
var result: Set<AWSAuthorizationType> = []
43+
let authHeaderValue = headers["Authorization"]
44+
let apiKeyHeaderValue = headers["x-api-key"]
45+
46+
if apiKeyHeaderValue != nil {
47+
result.insert(.apiKey)
48+
}
49+
50+
if let authHeaderValue = authHeaderValue,
51+
case let .success(claims) = AWSAuthService().getTokenClaims(tokenString: authHeaderValue),
52+
let cognitoIss = claims["iss"] as? String, cognitoIss.contains("cognito") {
53+
result.insert(.amazonCognitoUserPools)
54+
}
4855

49-
if let authHeaderValue = authHeaderValue,
50-
case let .success(claims) = awsAuthService.getTokenClaims(tokenString: authHeaderValue),
51-
let cognitoIss = claims["iss"] as? String, cognitoIss.contains("cognito") {
52-
recordAuthType(.amazonCognitoUserPools)
56+
if let authHeaderValue = authHeaderValue,
57+
authHeaderValue.starts(with: "AWS4-HMAC-SHA256") {
58+
result.insert(.awsIAM)
59+
}
60+
61+
DataStoreAuthBaseTestURLSessionFactory.subject.send((testId, result))
62+
return false
5363
}
5464

55-
if let authHeaderValue = authHeaderValue,
56-
authHeaderValue.starts(with: "AWS4-HMAC-SHA256") {
57-
recordAuthType(.awsIAM)
65+
}
66+
67+
class Interceptor: URLRequestInterceptor {
68+
let testId: String?
69+
70+
init(testId: String?) {
71+
self.testId = testId
5872
}
5973

60-
return request
74+
func intercept(_ request: URLRequest) async throws -> URLRequest {
75+
if let testId {
76+
var mutableRequest = request
77+
mutableRequest.setValue(testId, forHTTPHeaderField: DataStoreAuthBaseTestURLSessionFactory.testIdHeaderKey)
78+
return mutableRequest
79+
}
80+
return request
81+
}
6182
}
6283

63-
func reset() {
64-
consumedAuthTypes = []
84+
func makeSession(withDelegate delegate: URLSessionBehaviorDelegate?) -> URLSessionBehavior {
85+
let urlSessionDelegate = delegate?.asURLSessionDelegate
86+
let configuration = URLSessionConfiguration.default
87+
configuration.tlsMinimumSupportedProtocolVersion = .TLSv12
88+
configuration.tlsMaximumSupportedProtocolVersion = .TLSv13
89+
configuration.protocolClasses?.insert(Sniffer.self, at: 0)
90+
91+
let session = URLSession(configuration: configuration,
92+
delegate: urlSessionDelegate,
93+
delegateQueue: nil)
94+
return AmplifyURLSession(session: session)
6595
}
96+
6697
}
6798

6899
class AWSDataStoreAuthBaseTest: XCTestCase {
@@ -71,7 +102,6 @@ class AWSDataStoreAuthBaseTest: XCTestCase {
71102
var amplifyConfig: AmplifyConfiguration!
72103
var user1: TestUser?
73104
var user2: TestUser?
74-
var authRecorderInterceptor: AuthRecorderInterceptor!
75105

76106
override func setUp() {
77107
continueAfterFailure = false
@@ -138,8 +168,6 @@ class AWSDataStoreAuthBaseTest: XCTestCase {
138168
self.user1 = TestUser(username: user1, password: passwordUser1)
139169
self.user2 = TestUser(username: user2, password: passwordUser2)
140170

141-
authRecorderInterceptor = AuthRecorderInterceptor()
142-
143171
amplifyConfig = try TestConfigHelper.retrieveAmplifyConfiguration(forResource: configFile)
144172

145173
} catch {
@@ -161,7 +189,8 @@ class AWSDataStoreAuthBaseTest: XCTestCase {
161189
func setup(
162190
withModels models: AmplifyModelRegistration,
163191
testType: DataStoreAuthTestType,
164-
apiPluginFactory: () -> AWSAPIPlugin = { AWSAPIPlugin(sessionFactory: AmplifyURLSessionFactory()) }
192+
testId: String? = nil,
193+
apiPluginFactory: () -> AWSAPIPlugin = { AWSAPIPlugin(sessionFactory: DataStoreAuthBaseTestURLSessionFactory()) }
165194
) async {
166195
do {
167196
setupCredentials(forAuthStrategy: testType)
@@ -182,7 +211,10 @@ class AWSDataStoreAuthBaseTest: XCTestCase {
182211

183212
// register auth recorder interceptor
184213
let apiName = try apiEndpointName()
185-
try apiPlugin.add(interceptor: authRecorderInterceptor, for: apiName)
214+
try apiPlugin.add(
215+
interceptor: DataStoreAuthBaseTestURLSessionFactory.Interceptor(testId: testId),
216+
for: apiName
217+
)
186218

187219
await signOut()
188220
} catch {
@@ -486,13 +518,27 @@ extension AWSDataStoreAuthBaseTest {
486518
await waitForExpectations([expectations.mutationDelete, expectations.mutationDeleteProcessed], timeout: 60)
487519
}
488520

489-
func assertUsedAuthTypes(_ authTypes: [AWSAuthorizationType],
490-
file: StaticString = #file,
491-
line: UInt = #line) {
492-
XCTAssertEqual(authRecorderInterceptor.consumedAuthTypes,
493-
Set(authTypes),
494-
file: file,
495-
line: line)
521+
func assertUsedAuthTypes(
522+
testId: String,
523+
authTypes: [AWSAuthorizationType],
524+
file: StaticString = #file,
525+
line: UInt = #line
526+
) -> XCTestExpectation {
527+
let expectation = expectation(description: "Should have expected auth types")
528+
expectation.assertForOverFulfill = false
529+
DataStoreAuthBaseTestURLSessionFactory.subject
530+
.filter { $0.0 == testId }
531+
.map { $0.1 }
532+
.collect(.byTime(DispatchQueue.global(), .milliseconds(3500)))
533+
.sink {
534+
let result = $0.reduce(Set<AWSAuthorizationType>()) { partialResult, data in
535+
partialResult.union(data)
536+
}
537+
XCTAssertEqual(result, Set(authTypes), file: file, line: line)
538+
expectation.fulfill()
539+
}
540+
.store(in: &requests)
541+
return expectation
496542
}
497543
}
498544

AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginAuthIAMTests/AWSDataStoreCategoryPluginIAMAuthIntegrationTests.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ class AWSDataStoreCategoryPluginIAMAuthIntegrationTests: AWSDataStoreAuthBaseTes
1616
/// Then: DataStore is successfully initialized, query returns a result,
1717
/// mutation is processed for authenticated users
1818
func testIAMAllowPrivate() async {
19+
let testId = UUID().uuidString
1920
await setup(withModels: IAMPrivateModelRegistration(),
20-
testType: .defaultAuthIAM)
21+
testType: .defaultAuthIAM,
22+
testId: testId)
2123

2224
await signIn(user: user1)
2325

2426
let expectations = makeExpectations()
2527

2628
await assertDataStoreReady(expectations)
2729

30+
let authTypeExpectation = assertUsedAuthTypes(testId: testId, authTypes: [.awsIAM])
31+
2832
// Query
2933
await assertQuerySuccess(modelType: TodoIAMPrivate.self,
3034
expectations) { error in
@@ -38,21 +42,25 @@ class AWSDataStoreCategoryPluginIAMAuthIntegrationTests: AWSDataStoreAuthBaseTes
3842
XCTFail("Error mutation \(error)")
3943
}
4044

41-
assertUsedAuthTypes([.awsIAM])
45+
await fulfillment(of: [authTypeExpectation], timeout: 5)
4246
}
4347

4448
/// Given: a guest user, a model with `allow public` auth rule with IAM as provider
4549
/// When: DataStore query/mutation operations are sent with IAM
4650
/// Then: DataStore is successfully initialized, query returns a result,
4751
/// mutation is processed for unauthenticated users
48-
func testIAMAllowPublic() async{
52+
func testIAMAllowPublic() async {
53+
let testId = UUID().uuidString
4954
await setup(withModels: IAMPublicModelRegistration(),
50-
testType: .defaultAuthIAM)
55+
testType: .defaultAuthIAM,
56+
testId: testId)
5157

5258
let expectations = makeExpectations()
5359

5460
await assertDataStoreReady(expectations)
5561

62+
let authTypeExpectation = assertUsedAuthTypes(testId: testId, authTypes: [.awsIAM])
63+
5664
// Query
5765
await assertQuerySuccess(modelType: TodoIAMPublic.self,
5866
expectations) { error in
@@ -66,7 +74,7 @@ class AWSDataStoreCategoryPluginIAMAuthIntegrationTests: AWSDataStoreAuthBaseTes
6674
XCTFail("Error mutation \(error)")
6775
}
6876

69-
assertUsedAuthTypes([.awsIAM])
77+
await fulfillment(of: [authTypeExpectation], timeout: 5)
7078
}
7179
}
7280

0 commit comments

Comments
 (0)