From c444b07920c13d5f9268ebac3d16822b86a41866 Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Fri, 20 Oct 2023 11:37:50 -0700 Subject: [PATCH] fix(datastore): multi auth rule for read subscription --- .../Model/Internal/Schema/AuthRule.swift | 4 ++ .../Auth/AWSAuthModeStrategy.swift | 32 +++++++++-- .../Auth/AuthModeStrategyTests.swift | 53 +++++++++++++------ ...omingAsyncSubscriptionEventPublisher.swift | 6 +-- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/Amplify/Categories/DataStore/Model/Internal/Schema/AuthRule.swift b/Amplify/Categories/DataStore/Model/Internal/Schema/AuthRule.swift index 5c5027aff2..8a5c6b4aeb 100644 --- a/Amplify/Categories/DataStore/Model/Internal/Schema/AuthRule.swift +++ b/Amplify/Categories/DataStore/Model/Internal/Schema/AuthRule.swift @@ -68,3 +68,7 @@ public struct AuthRule { self.operations = operations } } + +extension AuthRule: Hashable { + +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift index a1355ced5a..2667999996 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift @@ -42,6 +42,8 @@ public protocol AuthModeStrategy: AnyObject { init() func authTypesFor(schema: ModelSchema, operation: ModelOperation) async -> AWSAuthorizationTypeIterator + + func authTypesFor(schema: ModelSchema, operations: [ModelOperation]) async -> AWSAuthorizationTypeIterator } /// AuthorizationType iterator with an extra `count` property used @@ -93,6 +95,11 @@ public class AWSDefaultAuthModeStrategy: AuthModeStrategy { operation: ModelOperation) -> AWSAuthorizationTypeIterator { return AWSAuthorizationTypeIterator(withValues: []) } + + public func authTypesFor(schema: ModelSchema, + operations: [ModelOperation]) -> AWSAuthorizationTypeIterator { + return AWSAuthorizationTypeIterator(withValues: []) + } } // MARK: - AWSMultiAuthModeStrategy @@ -188,19 +195,34 @@ public class AWSMultiAuthModeStrategy: AuthModeStrategy { /// - Returns: an iterator for the applicable auth rules public func authTypesFor(schema: ModelSchema, operation: ModelOperation) async -> AWSAuthorizationTypeIterator { - var applicableAuthRules = schema.authRules - .filter(modelOperation: operation) - .sorted(by: AWSMultiAuthModeStrategy.comparator) + return await authTypesFor(schema: schema, operations: [operation]) + } + + /// Returns the union of authorization types for the provided schema for the given list of operations + /// - Parameters: + /// - schema: model schema + /// - operations: model operations + /// - Returns: an iterator for the applicable auth rules + public func authTypesFor(schema: ModelSchema, + operations: [ModelOperation]) async -> AWSAuthorizationTypeIterator { + var applicableAuthRules = Set() + for operation in operations { + let rules = schema.authRules.filter(modelOperation: operation) + applicableAuthRules = applicableAuthRules.union(Set(rules)) + } + + var sortedRules = applicableAuthRules.sorted(by: AWSMultiAuthModeStrategy.comparator) // if there isn't a user signed in, returns only public or custom rules if let authDelegate = authDelegate, await !authDelegate.isUserLoggedIn() { - applicableAuthRules = applicableAuthRules.filter { rule in + sortedRules = sortedRules.filter { rule in return rule.allow == .public || rule.allow == .custom } } - let applicableAuthTypes = applicableAuthRules.map { + let applicableAuthTypes = sortedRules.map { AWSMultiAuthModeStrategy.authTypeFor(authRule: $0) } return AWSAuthorizationTypeIterator(withValues: applicableAuthTypes) } + } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift index 12ecb27a6d..f02a93e3d7 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import AWSPluginsCore class AuthModeStrategyTests: XCTestCase { - + // Given: default strategy and a model schema // When: authTypesFor for .create operation is called // Then: an empty iterator is returned @@ -20,7 +20,7 @@ class AuthModeStrategyTests: XCTestCase { let authTypesIterator = authMode.authTypesFor(schema: AnyModelTester.schema, operation: .create) XCTAssertEqual(authTypesIterator.count, 0) } - + // Given: multi-auth strategy and a model schema // When: authTypesFor for .create operation is called // Then: auth types are returned in order according to priority rules @@ -31,7 +31,7 @@ class AuthModeStrategyTests: XCTestCase { XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) XCTAssertEqual(authTypesIterator.next(), .apiKey) } - + // Given: multi-auth strategy and a model schema without auth provider // When: auth types are requested // Then: default values based on the auth strategy should be returned @@ -42,7 +42,7 @@ class AuthModeStrategyTests: XCTestCase { XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) XCTAssertEqual(authTypesIterator.next(), .apiKey) } - + // Given: multi-auth strategy and a model schema with 4 auth rules // When: authTypesFor for .create operation is called // Then: applicable auth types are ordered according to priority rules @@ -55,7 +55,7 @@ class AuthModeStrategyTests: XCTestCase { XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) XCTAssertEqual(authTypesIterator.next(), .awsIAM) } - + // Given: multi-auth strategy and a model schema multiple public rules // When: authTypesFor for .create operation is called // Then: applicable auth types are ordered according to priority rules @@ -68,7 +68,7 @@ class AuthModeStrategyTests: XCTestCase { XCTAssertEqual(authTypesIterator.next(), .awsIAM) XCTAssertEqual(authTypesIterator.next(), .apiKey) } - + // Given: multi-auth strategy and a model schema // When: authTypesFor for .create operation is called // Then: applicable auth types returned are only the @@ -80,7 +80,7 @@ class AuthModeStrategyTests: XCTestCase { XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) } - + // Given: multi-auth strategy a model schema // When: authTypesFor for .create operation is called for unauthenticated user // Then: applicable auth types returned are only public rules @@ -88,26 +88,26 @@ class AuthModeStrategyTests: XCTestCase { let authMode = AWSMultiAuthModeStrategy() let delegate = UnauthenticatedUserDelegate() authMode.authDelegate = delegate - + var authTypesIterator = await authMode.authTypesFor(schema: ModelWithOwnerAndPublicAuth.schema, - operation: .create) + operation: .create) XCTAssertEqual(authTypesIterator.count, 1) XCTAssertEqual(authTypesIterator.next(), .apiKey) } - + // Given: multi-auth model schema with a custom strategy // When: authTypesFor for .create operation is called // Then: applicable auth types returned respect the priority rules func testMultiAuthPriorityWithCustomStrategy() async { let authMode = AWSMultiAuthModeStrategy() var authTypesIterator = await authMode.authTypesFor(schema: ModelWithCustomStrategy.schema, - operation: .create) + operation: .create) XCTAssertEqual(authTypesIterator.count, 3) XCTAssertEqual(authTypesIterator.next(), .function) XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) XCTAssertEqual(authTypesIterator.next(), .awsIAM) } - + // Given: multi-auth model schema with a custom strategy // When: authTypesFor for .create operation is called for unauthenticated user // Then: applicable auth types returned are public rules or custom @@ -115,14 +115,37 @@ class AuthModeStrategyTests: XCTestCase { let authMode = AWSMultiAuthModeStrategy() let delegate = UnauthenticatedUserDelegate() authMode.authDelegate = delegate - + var authTypesIterator = await authMode.authTypesFor(schema: ModelWithCustomStrategy.schema, - operation: .create) + operation: .create) XCTAssertEqual(authTypesIterator.count, 2) XCTAssertEqual(authTypesIterator.next(), .function) XCTAssertEqual(authTypesIterator.next(), .awsIAM) } - + + // Given: multi-auth strategy and a model schema without auth provider + // When: auth types are requested with multiple operation + // Then: default values based on the auth strategy should be returned + func testMultiAuthShouldReturnDefaultAuthTypesForMultipleOperation() async { + let authMode = AWSMultiAuthModeStrategy() + var authTypesIterator = await authMode.authTypesFor(schema: ModelNoProvider.schema, operations: [.read, .create]) + XCTAssertEqual(authTypesIterator.count, 2) + XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools) + XCTAssertEqual(authTypesIterator.next(), .apiKey) + } + + // Given: multi-auth strategy and a model schema with auth provider + // When: auth types are requested with multiple operation + // Then: auth rule for public access should be returned + func testMultiAuthReturnDefaultAuthTypesForMultipleOperationWithProvider() async { + let authMode = AWSMultiAuthModeStrategy() + let delegate = UnauthenticatedUserDelegate() + authMode.authDelegate = delegate + var authTypesIterator = await authMode.authTypesFor(schema: ModelNoProvider.schema, operations: [.read, .create]) + XCTAssertEqual(authTypesIterator.count, 1) + XCTAssertEqual(authTypesIterator.next(), .apiKey) + } + } // MARK: - Test models diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift index 301e5534b8..c45ba79650 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift @@ -73,7 +73,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable { // onCreate operation let onCreateValueListener = onCreateValueListenerHandler(event:) let onCreateAuthTypeProvider = await authModeStrategy.authTypesFor(schema: modelSchema, - operation: .create) + operations: [.create, .read]) self.onCreateValueListener = onCreateValueListener self.onCreateOperation = RetryableGraphQLSubscriptionOperation( requestFactory: IncomingAsyncSubscriptionEventPublisher.apiRequestFactoryFor( @@ -94,7 +94,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable { // onUpdate operation let onUpdateValueListener = onUpdateValueListenerHandler(event:) let onUpdateAuthTypeProvider = await authModeStrategy.authTypesFor(schema: modelSchema, - operation: .update) + operations: [.update, .read]) self.onUpdateValueListener = onUpdateValueListener self.onUpdateOperation = RetryableGraphQLSubscriptionOperation( requestFactory: IncomingAsyncSubscriptionEventPublisher.apiRequestFactoryFor( @@ -115,7 +115,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable { // onDelete operation let onDeleteValueListener = onDeleteValueListenerHandler(event:) let onDeleteAuthTypeProvider = await authModeStrategy.authTypesFor(schema: modelSchema, - operation: .delete) + operations: [.delete, .read]) self.onDeleteValueListener = onDeleteValueListener self.onDeleteOperation = RetryableGraphQLSubscriptionOperation( requestFactory: IncomingAsyncSubscriptionEventPublisher.apiRequestFactoryFor(