From d6d5722bf29a8fe677e81c0869ad646ebc584a99 Mon Sep 17 00:00:00 2001 From: Diego Costantino Date: Fri, 21 Jan 2022 17:37:56 -0800 Subject: [PATCH] fix(datastore): filter authrules with invalid ownerfield --- .../Model/Decorator/AuthRuleDecorator.swift | 38 ++++++++++---- .../Common/AWSDataStoreAuthBaseTest.swift | 2 +- ...egoryPluginAuthOwnerIntegrationTests.swift | 38 ++++++++++++++ .../PostDraftCognitoMultiOwner+Schema.swift | 47 ++++++++++++++++++ .../Models/PostDraftCognitoMultiOwner.swift | 49 +++++++++++++++++++ .../singleauth-cognito-schema.graphql | 15 ++++++ .../project.pbxproj | 8 +++ 7 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner+Schema.swift create mode 100644 AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner.swift diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/AuthRuleDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/AuthRuleDecorator.swift index 6404ec23c5..2ac4b81e22 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/AuthRuleDecorator.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/AuthRuleDecorator.swift @@ -51,20 +51,14 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator { public func decorate(_ document: SingleDirectiveGraphQLDocument, modelSchema: ModelSchema) -> SingleDirectiveGraphQLDocument { - let authRules = modelSchema.authRules.filterBy(authType: authType) + let authRules = modelSchema.authRules + .filterBy(authType: authType) + .filterBy(ownerFieldType: .string, modelSchema: modelSchema) + guard !authRules.isEmpty else { return document } var decorateDocument = document - if authRules.readRestrictingOwnerRules().count > 1 { - log.error(""" - Detected multiple owner type auth rules \ - with a READ operation. We currently do not support this use case. Please \ - limit your type to just one owner auth rule with a READ operation restriction. - """) - return decorateDocument - } - let readRestrictingStaticGroups = authRules.groupClaimsToReadRestrictingStaticGroups() authRules.forEach { authRule in decorateDocument = decorateAuthStrategy(document: decorateDocument, @@ -180,6 +174,15 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator { } } +private extension AuthRule { + func ownerField(inSchema schema: ModelSchema) -> ModelField? { + guard let fieldName = self.ownerField else { + return nil + } + return schema.field(withName: fieldName) + } +} + private extension AuthRules { func filterBy(authType: AWSAuthorizationType?) -> AuthRules { guard let authType = authType else { @@ -196,6 +199,21 @@ private extension AuthRules { return authType == provider.toAWSAuthorizationType() } } + + func filterBy(ownerFieldType: ModelFieldType, + modelSchema: ModelSchema) -> AuthRules { + return filter { + guard let modelField = $0.ownerField(inSchema: modelSchema) else { + // if we couldn't find the owner field means it has been implicitly + // declared in the model schema, therefore has the correct type "string" + return true + } + if case .string = modelField.type { + return true + } + return false + } + } } extension AuthRuleDecorator: DefaultLogger { } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/Common/AWSDataStoreAuthBaseTest.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/Common/AWSDataStoreAuthBaseTest.swift index 4f658a5127..1bc8ca68b0 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/Common/AWSDataStoreAuthBaseTest.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/Common/AWSDataStoreAuthBaseTest.swift @@ -94,7 +94,7 @@ class AWSDataStoreAuthBaseTest: XCTestCase { amplifyConfig = try TestConfigHelper.retrieveAmplifyConfiguration(forResource: configFile) } catch { - XCTFail("Error during setup: \(error)") + fatalError("Error during setup: \(error)") } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/AWSDataStoreCategoryPluginAuthOwnerIntegrationTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/AWSDataStoreCategoryPluginAuthOwnerIntegrationTests.swift index 53730339d0..bd42041059 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/AWSDataStoreCategoryPluginAuthOwnerIntegrationTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/AWSDataStoreCategoryPluginAuthOwnerIntegrationTests.swift @@ -103,6 +103,37 @@ class AWSDataStoreCategoryPluginAuthOwnerIntegrationTests: AWSDataStoreAuthBaseT assertUsedAuthTypes([.amazonCognitoUserPools]) } + /// Given: a user signed in with CognitoUserPools, a model with multiple rules with + /// explicit owner field + /// When: DataStore query/mutation operations are sent with CognitoUserPools + /// Then: DataStore is successfully initialized, query returns a result, + /// mutation is processed for an authenticated users + func testExplicitMultipleOwner() { + setup(withModels: ExplicitMultipleOwnerModelRegistration(), + testType: .defaultAuthCognito) + + signIn(user: user1) + + let expectations = makeExpectations() + + assertDataStoreReady(expectations) + + // Query + assertQuerySuccess(modelType: PostDraftCognitoMultiOwner.self, + expectations) { error in + XCTFail("Error query \(error)") + } + + let post = PostDraftCognitoMultiOwner(title: "title") + + // Mutations + assertMutations(model: post, expectations) { error in + XCTFail("Error mutation \(error)") + } + + assertUsedAuthTypes([.amazonCognitoUserPools]) + } + /// Given: a user signed in with CognitoUserPools, a model with an implicit owner field /// When: DataStore query/mutation operations are sent with CognitoUserPools /// Then: DataStore is successfully initialized, query returns a result, @@ -189,6 +220,13 @@ extension AWSDataStoreCategoryPluginAuthOwnerIntegrationTests { } } + struct ExplicitMultipleOwnerModelRegistration: AmplifyModelRegistration { + public let version: String = "version" + func registerModels(registry: ModelRegistry.Type) { + ModelRegistry.register(modelType: PostDraftCognitoMultiOwner.self) + } + } + struct ImplicitOwnerModelRegistration: AmplifyModelRegistration { public let version: String = "version" func registerModels(registry: ModelRegistry.Type) { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner+Schema.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner+Schema.swift new file mode 100644 index 0000000000..ce51f8cde2 --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner+Schema.swift @@ -0,0 +1,47 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// swiftlint:disable all +import Amplify +import Foundation + +extension PostDraftCognitoMultiOwner { + // MARK: - CodingKeys + public enum CodingKeys: String, ModelKey { + case id + case title + case content + case owner + case editors + case createdAt + case updatedAt + } + + public static let keys = CodingKeys.self + // MARK: - ModelSchema + + public static let schema = defineSchema { model in + let postDraftCognitoMultiOwner = PostDraftCognitoMultiOwner.keys + + model.authRules = [ + rule(allow: .owner, ownerField: "owner", identityClaim: "cognito:username", provider: .userPools, operations: [.create, .update, .delete, .read]), + rule(allow: .owner, ownerField: "editors", identityClaim: "cognito:username", provider: .userPools, operations: [.update, .read]) + ] + + model.pluralName = "PostDraftCognitoMultiOwners" + + model.fields( + .id(), + .field(postDraftCognitoMultiOwner.title, is: .required, ofType: .string), + .field(postDraftCognitoMultiOwner.content, is: .optional, ofType: .string), + .field(postDraftCognitoMultiOwner.owner, is: .optional, ofType: .string), + .field(postDraftCognitoMultiOwner.editors, is: .optional, ofType: .embeddedCollection(of: String.self)), + .field(postDraftCognitoMultiOwner.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime), + .field(postDraftCognitoMultiOwner.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime) + ) + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner.swift new file mode 100644 index 0000000000..156da9e407 --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/Models/PostDraftCognitoMultiOwner.swift @@ -0,0 +1,49 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// swiftlint:disable all +import Amplify +import Foundation + +public struct PostDraftCognitoMultiOwner: Model { + public let id: String + public var title: String + public var content: String? + public var owner: String? + public var editors: [String?]? + public var createdAt: Temporal.DateTime? + public var updatedAt: Temporal.DateTime? + + public init(id: String = UUID().uuidString, + title: String, + content: String? = nil, + owner: String? = nil, + editors: [String?]? = nil) { + self.init(id: id, + title: title, + content: content, + owner: owner, + editors: editors, + createdAt: nil, + updatedAt: nil) + } + internal init(id: String = UUID().uuidString, + title: String, + content: String? = nil, + owner: String? = nil, + editors: [String?]? = nil, + createdAt: Temporal.DateTime? = nil, + updatedAt: Temporal.DateTime? = nil) { + self.id = id + self.title = title + self.content = content + self.owner = owner + self.editors = editors + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/singleauth-cognito-schema.graphql b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/singleauth-cognito-schema.graphql index 191c38d167..1a6e42f4e9 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/singleauth-cognito-schema.graphql +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginAuthIntegrationTests/DefaultAuthCognito/singleauth-cognito-schema.graphql @@ -34,3 +34,18 @@ type TodoCognitoPrivate @model @auth(rules: [{ allow: private }]) { id: ID! title: String! } + +type PostDraftCognitoMultiOwner @model + @auth(rules: [ + # Defaults to use the "owner" field. + { allow: owner }, + + # Authorize both the update mutation and queries. + { allow: owner, ownerField: "editors", operations: [update, read] } + ]) { + id: ID! + title: String! + content: String + owner: String + editors: [String] +} diff --git a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj index 882aa97031..c9e57b84fb 100644 --- a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj @@ -140,6 +140,8 @@ 6BE9D6F125A6643100AB5C9A /* StorageEngineTestsHasOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE9D6F025A6643100AB5C9A /* StorageEngineTestsHasOne.swift */; }; 6BE9D6F325A665EA00AB5C9A /* StorageEngineTestsBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE9D6F225A665EA00AB5C9A /* StorageEngineTestsBase.swift */; }; 6BE9D73E25A6800100AB5C9A /* StorageEngineTestsManyToMany.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE9D73D25A6800100AB5C9A /* StorageEngineTestsManyToMany.swift */; }; + 7613F9E3279B7D32009B9007 /* PostDraftCognitoMultiOwner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7613F9E1279B7D31009B9007 /* PostDraftCognitoMultiOwner.swift */; }; + 7613F9E4279B7D32009B9007 /* PostDraftCognitoMultiOwner+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7613F9E2279B7D31009B9007 /* PostDraftCognitoMultiOwner+Schema.swift */; }; 7617928827558BFA001EABD6 /* TodoCustomOwnerExplicit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7617928627558BFA001EABD6 /* TodoCustomOwnerExplicit.swift */; }; 7617928927558BFA001EABD6 /* TodoCustomOwnerExplicit+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7617928727558BFA001EABD6 /* TodoCustomOwnerExplicit+Schema.swift */; }; 7617928B275594B8001EABD6 /* AWSDataStoreCategoryPluginIAMAuthIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7617928A275594B8001EABD6 /* AWSDataStoreCategoryPluginIAMAuthIntegrationTests.swift */; }; @@ -560,6 +562,8 @@ 6BE9D6F025A6643100AB5C9A /* StorageEngineTestsHasOne.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageEngineTestsHasOne.swift; sourceTree = ""; }; 6BE9D6F225A665EA00AB5C9A /* StorageEngineTestsBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageEngineTestsBase.swift; sourceTree = ""; }; 6BE9D73D25A6800100AB5C9A /* StorageEngineTestsManyToMany.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageEngineTestsManyToMany.swift; sourceTree = ""; }; + 7613F9E1279B7D31009B9007 /* PostDraftCognitoMultiOwner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostDraftCognitoMultiOwner.swift; sourceTree = ""; }; + 7613F9E2279B7D31009B9007 /* PostDraftCognitoMultiOwner+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PostDraftCognitoMultiOwner+Schema.swift"; sourceTree = ""; }; 7617928627558BFA001EABD6 /* TodoCustomOwnerExplicit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoCustomOwnerExplicit.swift; sourceTree = ""; }; 7617928727558BFA001EABD6 /* TodoCustomOwnerExplicit+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TodoCustomOwnerExplicit+Schema.swift"; sourceTree = ""; }; 7617928A275594B8001EABD6 /* AWSDataStoreCategoryPluginIAMAuthIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSDataStoreCategoryPluginIAMAuthIntegrationTests.swift; sourceTree = ""; }; @@ -1299,6 +1303,8 @@ 76C0090C2736061200ADA120 /* Models */ = { isa = PBXGroup; children = ( + 7613F9E1279B7D31009B9007 /* PostDraftCognitoMultiOwner.swift */, + 7613F9E2279B7D31009B9007 /* PostDraftCognitoMultiOwner+Schema.swift */, 761792A227594339001EABD6 /* TodoCognitoPrivate.swift */, 761792A127594339001EABD6 /* TodoCognitoPrivate+Schema.swift */, 7617928627558BFA001EABD6 /* TodoCustomOwnerExplicit.swift */, @@ -2321,6 +2327,7 @@ 76C009112736061200ADA120 /* TodoExplicitOwnerField.swift in Sources */, 762547F42735FD0C00E5F6A3 /* GroupPublicUPIAMPost+Schema.swift in Sources */, 769CF2C9266D7F04007843A0 /* AWSDataStoreMultiAuthTwoRulesTests.swift in Sources */, + 7613F9E4279B7D32009B9007 /* PostDraftCognitoMultiOwner+Schema.swift in Sources */, 762547E82735FD0C00E5F6A3 /* PrivatePublicComboUPPost.swift in Sources */, 762547D52735FD0C00E5F6A3 /* PrivateIAMPost+Schema.swift in Sources */, 762547CF2735FD0C00E5F6A3 /* OwnerPrivatePublicUPIAMAPIPost+Schema.swift in Sources */, @@ -2348,6 +2355,7 @@ 762547F02735FD0C00E5F6A3 /* OwnerOIDCPost.swift in Sources */, 762547C52735FD0C00E5F6A3 /* OwnerUPPost+Schema.swift in Sources */, 762547E72735FD0C00E5F6A3 /* PrivatePrivatePublicUPIAMIAMPost+Schema.swift in Sources */, + 7613F9E3279B7D32009B9007 /* PostDraftCognitoMultiOwner.swift in Sources */, 762547D12735FD0C00E5F6A3 /* OwnerPublicOIDAPIPost.swift in Sources */, 762547DD2735FD0C00E5F6A3 /* PrivatePrivateUPIAMPost+Schema.swift in Sources */, 761792A327594339001EABD6 /* TodoCognitoPrivate+Schema.swift in Sources */,