Skip to content

Commit 8153149

Browse files
authored
fix(DataStore): Owner and Group Combined @auth (#817)
1 parent 523499a commit 8153149

File tree

14 files changed

+648
-38
lines changed

14 files changed

+648
-38
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@
144144
6BBECD7123ADA7E100C8DFBE /* AmplifyAWSServiceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BBECD7023ADA7E100C8DFBE /* AmplifyAWSServiceConfiguration.swift */; };
145145
6BBECD7423ADA9D100C8DFBE /* AmplifyAWSServiceConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BBECD7323ADA9D100C8DFBE /* AmplifyAWSServiceConfigurationTests.swift */; };
146146
6BEE0817253114FD00133961 /* AWSAuthServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE0816253114FD00133961 /* AWSAuthServiceTests.swift */; };
147+
6BEE08192533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE08182533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift */; };
148+
6BEE081C2533CCFA00133961 /* OGCScenarioBPost+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE081A2533CCFA00133961 /* OGCScenarioBPost+Schema.swift */; };
149+
6BEE081D2533CCFA00133961 /* OGCScenarioBPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE081B2533CCFA00133961 /* OGCScenarioBPost.swift */; };
150+
6BEE08242533D30800133961 /* OGCScenarioBMGroupPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE08222533D30800133961 /* OGCScenarioBMGroupPost.swift */; };
151+
6BEE08252533D30800133961 /* OGCScenarioBMGroupPost+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE08232533D30800133961 /* OGCScenarioBMGroupPost+Schema.swift */; };
147152
7D5ED6C78E25246DDAF2F2EC /* Pods_Amplify.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F3A76FB68CEFA45F4BB1BB /* Pods_Amplify.framework */; platformFilter = ios; };
148153
7F27B1DCE59C1E674172CCD6 /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 976D972EC2BBCAAD023694EB /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework */; };
149154
881246F5DCC59436DC932469 /* Pods_Amplify_AWSPluginsCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35D92182B8445C8F9B0FAE94 /* Pods_Amplify_AWSPluginsCore.framework */; };
@@ -859,6 +864,11 @@
859864
6BBECD7023ADA7E100C8DFBE /* AmplifyAWSServiceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmplifyAWSServiceConfiguration.swift; sourceTree = "<group>"; };
860865
6BBECD7323ADA9D100C8DFBE /* AmplifyAWSServiceConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmplifyAWSServiceConfigurationTests.swift; sourceTree = "<group>"; };
861866
6BEE0816253114FD00133961 /* AWSAuthServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSAuthServiceTests.swift; sourceTree = "<group>"; };
867+
6BEE08182533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequestOwnerAndGroupTests.swift; sourceTree = "<group>"; };
868+
6BEE081A2533CCFA00133961 /* OGCScenarioBPost+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OGCScenarioBPost+Schema.swift"; sourceTree = "<group>"; };
869+
6BEE081B2533CCFA00133961 /* OGCScenarioBPost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OGCScenarioBPost.swift; sourceTree = "<group>"; };
870+
6BEE08222533D30800133961 /* OGCScenarioBMGroupPost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OGCScenarioBMGroupPost.swift; sourceTree = "<group>"; };
871+
6BEE08232533D30800133961 /* OGCScenarioBMGroupPost+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OGCScenarioBMGroupPost+Schema.swift"; sourceTree = "<group>"; };
862872
6C41D3730B7ED4FD62A43E40 /* Pods-Amplify-AmplifyAWSPlugins-AWSAPICategoryPlugin-AWSAPICategoryPluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Amplify-AmplifyAWSPlugins-AWSAPICategoryPlugin-AWSAPICategoryPluginTests.debug.xcconfig"; path = "Target Support Files/Pods-Amplify-AmplifyAWSPlugins-AWSAPICategoryPlugin-AWSAPICategoryPluginTests/Pods-Amplify-AmplifyAWSPlugins-AWSAPICategoryPlugin-AWSAPICategoryPluginTests.debug.xcconfig"; sourceTree = "<group>"; };
863873
6D51240C78418B733FFA6829 /* Pods-Amplify-AWSPluginsCore-AWSPluginsTestConfigs-AWSDataStoreCategoryPluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Amplify-AWSPluginsCore-AWSPluginsTestConfigs-AWSDataStoreCategoryPluginTests.debug.xcconfig"; path = "Target Support Files/Pods-Amplify-AWSPluginsCore-AWSPluginsTestConfigs-AWSDataStoreCategoryPluginTests/Pods-Amplify-AWSPluginsCore-AWSPluginsTestConfigs-AWSDataStoreCategoryPluginTests.debug.xcconfig"; sourceTree = "<group>"; };
864874
6D62C9C57736C3BEADEB1E30 /* Pods-AWSPinpointAnalyticsPlugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AWSPinpointAnalyticsPlugin.debug.xcconfig"; path = "Target Support Files/Pods-AWSPinpointAnalyticsPlugin/Pods-AWSPinpointAnalyticsPlugin.debug.xcconfig"; sourceTree = "<group>"; };
@@ -1588,6 +1598,7 @@
15881598
2129BE322394828B006363A1 /* GraphQLRequestModelTests.swift */,
15891599
216E45F0248E971E0035E3CE /* GraphQLRequestNonModelTests.swift */,
15901600
214F497D2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift */,
1601+
6BEE08182533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift */,
15911602
6B9F7C5125267E1500F1F71C /* MockAWSAuthUser.swift */,
15921603
);
15931604
path = GraphQLRequest;
@@ -2327,6 +2338,10 @@
23272338
B9521831237E21B900F53237 /* Post+Schema.swift */,
23282339
2129BE002394627B006363A1 /* PostCommentModelRegistration.swift */,
23292340
B9AA09F02473CA29000E6FBB /* PostStatus.swift */,
2341+
6BEE08222533D30800133961 /* OGCScenarioBMGroupPost.swift */,
2342+
6BEE08232533D30800133961 /* OGCScenarioBMGroupPost+Schema.swift */,
2343+
6BEE081B2533CCFA00133961 /* OGCScenarioBPost.swift */,
2344+
6BEE081A2533CCFA00133961 /* OGCScenarioBPost+Schema.swift */,
23302345
B952182F237E21B900F53237 /* schema.graphql */,
23312346
6B9F7C542526864800F1F71C /* ScenarioATest6Post.swift */,
23322347
6B9F7C532526864800F1F71C /* ScenarioATest6Post+Schema.swift */,
@@ -4132,6 +4147,7 @@
41324147
21AD425A249C0D910016FE95 /* AnyModelTester.swift in Sources */,
41334148
2129BE3C2394828B006363A1 /* GraphQLRequestModelTests.swift in Sources */,
41344149
2183A56523EA4A8400232880 /* GraphQLListQueryTests.swift in Sources */,
4150+
6BEE08192533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift in Sources */,
41354151
21AD425B249C0DBE0016FE95 /* AnyModelTests.swift in Sources */,
41364152
D83C5160248964780091548E /* ModelGraphQLTests.swift in Sources */,
41374153
);
@@ -4582,10 +4598,12 @@
45824598
B9521833237E21BA00F53237 /* Comment+Schema.swift in Sources */,
45834599
FA176ED7238503C200C5C5F9 /* HubListenerTestUtilities.swift in Sources */,
45844600
216E45EE248E914F0035E3CE /* Todo.swift in Sources */,
4601+
6BEE081D2533CCFA00133961 /* OGCScenarioBPost.swift in Sources */,
45854602
21F40A3A23A294770074678E /* TestConfigHelper.swift in Sources */,
45864603
B4BD6B3723708C6700A1F0A7 /* MockPredictionsCategoryPlugin.swift in Sources */,
45874604
FACA361C2327FC7D000E74F6 /* MockHubCategoryPlugin.swift in Sources */,
45884605
FAD3937F23820DAE00463F5E /* MockDataStoreCategoryPlugin.swift in Sources */,
4606+
6BEE08252533D30800133961 /* OGCScenarioBMGroupPost+Schema.swift in Sources */,
45894607
FACA361A2327FC69000E74F6 /* MockStorageCategoryPlugin.swift in Sources */,
45904608
B9521837237E21BA00F53237 /* Post.swift in Sources */,
45914609
B9FAA11A23879AC8009414B4 /* BookAuthor.swift in Sources */,
@@ -4610,6 +4628,7 @@
46104628
FACA361D2327FC84000E74F6 /* MockAPICategoryPlugin.swift in Sources */,
46114629
B9FAA11023878C5E009414B4 /* UserProfile.swift in Sources */,
46124630
B9AA09F12473CA29000E6FBB /* PostStatus.swift in Sources */,
4631+
6BEE081C2533CCFA00133961 /* OGCScenarioBPost+Schema.swift in Sources */,
46134632
214F497B2486D8A200DA616C /* User+Schema.swift in Sources */,
46144633
6B9F7C562526864800F1F71C /* ScenarioATest6Post.swift in Sources */,
46154634
B9FAA12023879BD0009414B4 /* BookAuthor+Schema.swift in Sources */,
@@ -4623,6 +4642,7 @@
46234642
B9FAA10E23878BF3009414B4 /* UserAccount.swift in Sources */,
46244643
214F49782486D8A200DA616C /* UserFollowing.swift in Sources */,
46254644
21F40A3C23A2952C0074678E /* AuthHelper.swift in Sources */,
4645+
6BEE08242533D30800133961 /* OGCScenarioBMGroupPost.swift in Sources */,
46264646
B4F3E9FA24314ECC00F23296 /* MockAuthCategoryPlugin.swift in Sources */,
46274647
214F49CE24898E8500DA616C /* Article+Schema.swift in Sources */,
46284648
216E460A249183230035E3CE /* Section.swift in Sources */,

AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/AuthRuleDecorator.swift

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99
import Amplify
1010

11-
public typealias IdentityClaimsDictionary = [String: String]
11+
public typealias IdentityClaimsDictionary = [String: AnyObject]
1212

1313
public enum AuthRuleDecoratorInput {
1414
case subscription(GraphQLSubscriptionType, IdentityClaimsDictionary)
@@ -42,30 +42,39 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator {
4242
return document
4343
}
4444
var decorateDocument = document
45+
if authRules.readRestrictingOwnerRules().count > 1 {
46+
log.error("""
47+
Detected multiple owner type auth rules \
48+
with a READ operation. We currently do not support this use case. Please \
49+
limit your type to just one owner auth rule with a READ operation restriction.
50+
""")
51+
return decorateDocument
52+
}
53+
54+
let readRestrictingStaticGroups = authRules.readRestrictingStaticGroups()
4555
authRules.forEach { authRule in
46-
decorateDocument = decorateIfOwnerAuthStrategy(document: decorateDocument, authRule: authRule)
56+
decorateDocument = decorateAuthStrategy(document: decorateDocument,
57+
authRule: authRule,
58+
readRestrictingStaticGroups: readRestrictingStaticGroups)
4759
}
4860
return decorateDocument
4961
}
5062

51-
func decorateIfOwnerAuthStrategy(document: SingleDirectiveGraphQLDocument,
52-
authRule: AuthRule) -> SingleDirectiveGraphQLDocument {
53-
guard authRule.allow == .owner else {
54-
return document
55-
}
56-
57-
guard var selectionSet = document.selectionSet else {
63+
private func decorateAuthStrategy(document: SingleDirectiveGraphQLDocument,
64+
authRule: AuthRule,
65+
readRestrictingStaticGroups: Set<String>) -> SingleDirectiveGraphQLDocument {
66+
guard authRule.allow == .owner,
67+
var selectionSet = document.selectionSet else {
5868
return document
5969
}
6070

6171
let ownerField = authRule.getOwnerFieldOrDefault()
6272
selectionSet = appendOwnerFieldToSelectionSetIfNeeded(selectionSet: selectionSet, ownerField: ownerField)
6373

64-
guard case let .subscription(_, claims) = input else {
65-
return document.copy(selectionSet: selectionSet)
66-
}
67-
68-
if isOwnerInputRequiredOnSubscription(authRule) {
74+
if case let .subscription(_, claims) = input,
75+
authRule.isReadRestrictingOwner() &&
76+
isNotInReadRestrictingStaticGroup(readRestrictingStaticGroups,
77+
cognitoGroupsFrom(claims: claims)) {
6978
var inputs = document.inputs
7079
let identityClaimValue = resolveIdentityClaimValue(identityClaim: authRule.identityClaimOrDefault(),
7180
claims: claims)
@@ -77,8 +86,22 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator {
7786
return document.copy(selectionSet: selectionSet)
7887
}
7988

80-
private func isOwnerInputRequiredOnSubscription(_ authRule: AuthRule) -> Bool {
81-
return authRule.allow == .owner && authRule.getModelOperationsOrDefault().contains(.read)
89+
private func isNotInReadRestrictingStaticGroup(_ readRestrictingStaticGroups: Set<String>,
90+
_ cognitoGroupsFromClaims: Set<String>) -> Bool {
91+
return (readRestrictingStaticGroups.isEmpty ||
92+
readRestrictingStaticGroups.isDisjoint(with: cognitoGroupsFromClaims))
93+
}
94+
95+
private func cognitoGroupsFrom(claims: IdentityClaimsDictionary) -> Set<String> {
96+
var groupSet = Set<String>()
97+
if let groups = (claims["cognito:groups"] as? NSArray) as Array? {
98+
for group in groups {
99+
if let groupString = group as? String {
100+
groupSet.insert(groupString)
101+
}
102+
}
103+
}
104+
return groupSet
82105
}
83106

84107
private func resolveIdentityClaimValue(identityClaim: String, claims: IdentityClaimsDictionary) -> String? {

AmplifyPlugins/Core/AWSPluginsCore/Model/Support/AuthRule+Extension.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ extension AuthRule {
1515
return ownerField
1616
}
1717

18+
func isReadRestrictingStaticGroup() -> Bool {
19+
return allow == .groups &&
20+
!groups.isEmpty &&
21+
getModelOperationsOrDefault().contains(.read)
22+
}
23+
24+
func isReadRestrictingOwner() -> Bool {
25+
return allow == .owner &&
26+
getModelOperationsOrDefault().contains(.read)
27+
}
28+
1829
func getModelOperationsOrDefault() -> [ModelOperation] {
1930
return operations.isEmpty ? [.create, .update, .delete, .read] : operations
2031
}
@@ -29,3 +40,20 @@ extension AuthRule {
2940
return identityClaim
3041
}
3142
}
43+
44+
extension Array where Element == AuthRule {
45+
func readRestrictingStaticGroups() -> Set<String> {
46+
var readRestrictingStaticGroups = Set<String>()
47+
let readRestrictingGroupRules = filter { $0.isReadRestrictingStaticGroup() }
48+
for groupRules in readRestrictingGroupRules {
49+
groupRules.groups.forEach { group in
50+
readRestrictingStaticGroups.insert(group)
51+
}
52+
}
53+
return readRestrictingStaticGroups
54+
}
55+
56+
func readRestrictingOwnerRules() -> [AuthRule] {
57+
return filter { $0.isReadRestrictingOwner() }
58+
}
59+
}

AmplifyPlugins/Core/AWSPluginsCoreTests/Model/Decorator/AuthRuleDecorator/ModelMultipleOwnerAuthRuleTests.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public struct ModelMultipleOwner: Model {
5454
}
5555
}
5656

57+
/*
58+
* AppSync service currently supports only one owner rule with a single read at this time
59+
* therefore, we will have a single test which test that we do not support this.
60+
*
61+
* When we do support this feature, we will delete test case "testUnsupportedModelMultipleOwner_CreateMutation",
62+
* and uncomment the other tests.
63+
*/
64+
5765
class ModelMultipleOwnerAuthRuleTests: XCTestCase {
5866

5967
override func setUp() {
@@ -63,7 +71,31 @@ class ModelMultipleOwnerAuthRuleTests: XCTestCase {
6371
override func tearDown() {
6472
ModelRegistry.reset()
6573
}
74+
// This is a test case to demostrate if we attempt to use a model with multiple auth rules
75+
// with a read operation, we effectively create a subscription without decorating it with auth.
76+
// We should delete this use case when the AppSync service supports this use case.
77+
func testUnsupportedModelMultipleOwner_CreateMutation() {
78+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: ModelMultipleOwner.self,
79+
operationType: .mutation)
80+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .create))
81+
documentBuilder.add(decorator: AuthRuleDecorator(.mutation))
82+
let document = documentBuilder.build()
83+
let expectedQueryDocument = """
84+
mutation CreateModelMultipleOwner {
85+
createModelMultipleOwner {
86+
id
87+
content
88+
editors
89+
__typename
90+
}
91+
}
92+
"""
93+
XCTAssertEqual(document.name, "createModelMultipleOwner")
94+
XCTAssertEqual(document.stringValue, expectedQueryDocument)
95+
XCTAssertTrue(document.variables.isEmpty)
96+
}
6697

98+
/*
6799
// Ensure that the `owner` field is added to the model fields
68100
func testModelMultipleOwner_CreateMutation() {
69101
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: ModelMultipleOwner.self,
@@ -215,7 +247,7 @@ class ModelMultipleOwnerAuthRuleTests: XCTestCase {
215247
// Only the 'owner' inherently has `.create` operation, requiring the subscription operation to contain the input
216248
func testModelMultipleOwner_OnCreateSubscription() {
217249
let claims = ["username": "user1",
218-
"sub": "123e4567-dead-beef-a456-426614174000"]
250+
"sub": "123e4567-dead-beef-a456-426614174000"] as IdentityClaimsDictionary
219251
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: ModelMultipleOwner.self,
220252
operationType: .subscription)
221253
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate))
@@ -244,7 +276,7 @@ class ModelMultipleOwnerAuthRuleTests: XCTestCase {
244276
// Each owner with `.update` operation requires the ownerField on the corresponding subscription operation
245277
func testModelMultipleOwner_OnUpdateSubscription() {
246278
let claims = ["username": "user1",
247-
"sub": "123e4567-dead-beef-a456-426614174000"]
279+
"sub": "123e4567-dead-beef-a456-426614174000"] as IdentityClaimsDictionary
248280
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: ModelMultipleOwner.self,
249281
operationType: .subscription)
250282
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onUpdate))
@@ -274,7 +306,7 @@ class ModelMultipleOwnerAuthRuleTests: XCTestCase {
274306
// Only the 'owner' inherently has `.delete` operation, requiring the subscription operation to contain the input
275307
func testModelMultipleOwner_OnDeleteSubscription() {
276308
let claims = ["username": "user1",
277-
"sub": "123e4567-dead-beef-a456-426614174000"]
309+
"sub": "123e4567-dead-beef-a456-426614174000"] as IdentityClaimsDictionary
278310
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: ModelMultipleOwner.self,
279311
operationType: .subscription)
280312
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onDelete))
@@ -299,4 +331,5 @@ class ModelMultipleOwnerAuthRuleTests: XCTestCase {
299331
}
300332
XCTAssertEqual(variables["owner"] as? String, "user1")
301333
}
334+
*/
302335
}

0 commit comments

Comments
 (0)