Skip to content

Commit 4594e70

Browse files
authored
fix(datastore): fix owner based subscriptions queries w/ multiauth (#1553)
* fix(datastore): fix owner based subscriptions queries w/ multiauth * chore(datastore): update tests with comments * chore(datastore): add OIDC test
1 parent 8dd00b4 commit 4594e70

File tree

3 files changed

+226
-48
lines changed

3 files changed

+226
-48
lines changed

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,18 @@ public enum AuthRuleDecoratorInput {
3030
public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator {
3131

3232
private let input: AuthRuleDecoratorInput
33+
private let authType: AWSAuthorizationType?
3334

34-
public init(_ authRuleDecoratorInput: AuthRuleDecoratorInput) {
35+
36+
/// Initializes a new AuthRuleDecorator
37+
/// - Parameters:
38+
/// - authRuleDecoratorInput: decorator input
39+
/// - authType: authentication type, if provided will be used to filter the auth rules based on the provider field.
40+
/// Only use when multi-auth is enabled.
41+
public init(_ authRuleDecoratorInput: AuthRuleDecoratorInput,
42+
authType: AWSAuthorizationType? = nil) {
3543
self.input = authRuleDecoratorInput
44+
self.authType = authType
3645
}
3746

3847
public func decorate(_ document: SingleDirectiveGraphQLDocument,
@@ -42,7 +51,7 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator {
4251

4352
public func decorate(_ document: SingleDirectiveGraphQLDocument,
4453
modelSchema: ModelSchema) -> SingleDirectiveGraphQLDocument {
45-
let authRules = modelSchema.authRules
54+
let authRules = modelSchema.authRules.filterBy(authType: authType)
4655
guard !authRules.isEmpty else {
4756
return document
4857
}
@@ -171,4 +180,22 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator {
171180
}
172181
}
173182

183+
private extension AuthRules {
184+
func filterBy(authType: AWSAuthorizationType?) -> AuthRules {
185+
guard let authType = authType else {
186+
return self
187+
}
188+
189+
return filter {
190+
guard let provider = $0.provider else {
191+
// if an authType is available but not a provider
192+
// means DataStore is using multi-auth with an outdated
193+
// version of models (prior to codegen v2.26.0).
194+
return true
195+
}
196+
return authType == provider.toAWSAuthorizationType()
197+
}
198+
}
199+
}
200+
174201
extension AuthRuleDecorator: DefaultLogger { }

AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
6565
documentBuilder.add(decorator: DirectiveNameDecorator(type: .get))
6666
documentBuilder.add(decorator: ModelIdDecorator(id: id))
6767
documentBuilder.add(decorator: ConflictResolutionDecorator())
68-
documentBuilder.add(decorator: AuthRuleDecorator(.query))
68+
documentBuilder.add(decorator: AuthRuleDecorator(.query, authType: authType))
6969
let document = documentBuilder.build()
7070

7171
let awsPluginOptions = AWSPluginOptions(authType: authType)
@@ -150,7 +150,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
150150
documentBuilder.add(decorator: FilterDecorator(filter: filter))
151151
}
152152
documentBuilder.add(decorator: ConflictResolutionDecorator(version: version))
153-
documentBuilder.add(decorator: AuthRuleDecorator(.mutation))
153+
documentBuilder.add(decorator: AuthRuleDecorator(.mutation, authType: authType))
154154
let document = documentBuilder.build()
155155

156156
let awsPluginOptions = AWSPluginOptions(authType: authType)
@@ -171,7 +171,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
171171
operationType: .subscription)
172172
documentBuilder.add(decorator: DirectiveNameDecorator(type: subscriptionType))
173173
documentBuilder.add(decorator: ConflictResolutionDecorator())
174-
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(subscriptionType, nil)))
174+
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(subscriptionType, nil), authType: authType))
175175
let document = documentBuilder.build()
176176

177177
let awsPluginOptions = AWSPluginOptions(authType: authType)
@@ -193,7 +193,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
193193
operationType: .subscription)
194194
documentBuilder.add(decorator: DirectiveNameDecorator(type: subscriptionType))
195195
documentBuilder.add(decorator: ConflictResolutionDecorator())
196-
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(subscriptionType, claims)))
196+
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(subscriptionType, claims), authType: authType))
197197
let document = documentBuilder.build()
198198

199199
let awsPluginOptions = AWSPluginOptions(authType: authType)
@@ -220,7 +220,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
220220
}
221221
documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken))
222222
documentBuilder.add(decorator: ConflictResolutionDecorator(lastSync: lastSync))
223-
documentBuilder.add(decorator: AuthRuleDecorator(.query))
223+
documentBuilder.add(decorator: AuthRuleDecorator(.query, authType: authType))
224224
let document = documentBuilder.build()
225225

226226
let awsPluginOptions = AWSPluginOptions(authType: authType)
@@ -249,7 +249,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
249249
documentBuilder.add(decorator: FilterDecorator(filter: filter))
250250
}
251251
documentBuilder.add(decorator: ConflictResolutionDecorator(version: version))
252-
documentBuilder.add(decorator: AuthRuleDecorator(.mutation))
252+
documentBuilder.add(decorator: AuthRuleDecorator(.mutation, authType: authType))
253253
let document = documentBuilder.build()
254254

255255
let awsPluginOptions = AWSPluginOptions(authType: authType)

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

Lines changed: 191 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,7 @@ import XCTest
1111
@testable import AmplifyTestCommon
1212
@testable import AWSPluginsCore
1313

14-
/*
15-
type ModelWithOwnerField
16-
@model
17-
@auth(rules: [ { allow: owner, ownerField: "author" } ])
18-
{
19-
id: ID!
20-
content: String!
21-
author: String
22-
}
23-
*/
24-
public struct ModelWithOwnerField: Model {
25-
public let id: String
26-
public var content: String
27-
public var author: String?
28-
public init(id: String = UUID().uuidString,
29-
content: String,
30-
author: String?) {
31-
self.id = id
32-
self.content = content
33-
self.author = author
34-
}
35-
public enum CodingKeys: String, ModelKey {
36-
case id
37-
case content
38-
case author
39-
}
40-
public static let keys = CodingKeys.self
41-
42-
public static let schema = defineSchema { model in
43-
let modelWithOwnerField = ModelWithOwnerField.keys
44-
model.authRules = [
45-
rule(allow: .owner, ownerField: "author")
46-
]
47-
model.fields(
48-
.id(),
49-
.field(modelWithOwnerField.content, is: .required, ofType: .string),
50-
.field(modelWithOwnerField.author, is: .optional, ofType: .string))
51-
}
52-
}
53-
14+
// swiftlint:disable type_body_length
5415
class ModelWithOwnerFieldAuthRuleTests: XCTestCase {
5516

5617
override func setUp() {
@@ -286,4 +247,194 @@ class ModelWithOwnerFieldAuthRuleTests: XCTestCase {
286247
}
287248
XCTAssertEqual(variables["author"] as? String, "user1")
288249
}
250+
251+
func testModelWithMultipleAuthRules_Subscription() {
252+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: ModelWithMultipleAuthRules.schema,
253+
operationType: .subscription)
254+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate))
255+
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(.onCreate, nil)))
256+
let document = documentBuilder.build()
257+
let expectedQueryDocument = """
258+
subscription OnCreateModelWithMultipleAuthRules($author: String!) {
259+
onCreateModelWithMultipleAuthRules(author: $author) {
260+
id
261+
author
262+
content
263+
__typename
264+
}
265+
}
266+
"""
267+
XCTAssertEqual(document.name, "onCreateModelWithMultipleAuthRules")
268+
XCTAssertEqual(document.stringValue, expectedQueryDocument)
269+
}
270+
271+
func testModelWithMultipleAuthRulesAPIKey_Subscription() {
272+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: ModelWithMultipleAuthRules.schema,
273+
operationType: .subscription)
274+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate))
275+
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(.onCreate, nil),
276+
authType: .apiKey))
277+
let document = documentBuilder.build()
278+
let expectedQueryDocument = """
279+
subscription OnCreateModelWithMultipleAuthRules {
280+
onCreateModelWithMultipleAuthRules {
281+
id
282+
author
283+
content
284+
__typename
285+
}
286+
}
287+
"""
288+
XCTAssertEqual(document.name, "onCreateModelWithMultipleAuthRules")
289+
XCTAssertEqual(document.stringValue, expectedQueryDocument)
290+
}
291+
292+
func testModelWithOIDCOwner_Subscription() {
293+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: ModelWithOIDCOwnerField.schema,
294+
operationType: .subscription)
295+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate))
296+
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(.onCreate, nil)))
297+
let document = documentBuilder.build()
298+
let expectedQueryDocument = """
299+
subscription OnCreateModelWithOIDCOwnerField($author: String!) {
300+
onCreateModelWithOIDCOwnerField(author: $author) {
301+
id
302+
author
303+
content
304+
__typename
305+
}
306+
}
307+
"""
308+
XCTAssertEqual(document.name, "onCreateModelWithOIDCOwnerField")
309+
XCTAssertEqual(document.stringValue, expectedQueryDocument)
310+
}
311+
}
312+
313+
// MARK: Test schemas
314+
315+
/*
316+
type ModelWithOwnerField
317+
@model
318+
@auth(rules: [ { allow: owner, ownerField: "author" } ])
319+
{
320+
id: ID!
321+
content: String!
322+
author: String
323+
}
324+
*/
325+
public struct ModelWithOwnerField: Model {
326+
public let id: String
327+
public var content: String
328+
public var author: String?
329+
public init(id: String = UUID().uuidString,
330+
content: String,
331+
author: String?) {
332+
self.id = id
333+
self.content = content
334+
self.author = author
335+
}
336+
public enum CodingKeys: String, ModelKey {
337+
case id
338+
case content
339+
case author
340+
}
341+
public static let keys = CodingKeys.self
342+
343+
public static let schema = defineSchema { model in
344+
let modelWithOwnerField = ModelWithOwnerField.keys
345+
model.authRules = [
346+
rule(allow: .owner, ownerField: "author")
347+
]
348+
model.fields(
349+
.id(),
350+
.field(modelWithOwnerField.content, is: .required, ofType: .string),
351+
.field(modelWithOwnerField.author, is: .optional, ofType: .string))
352+
}
353+
}
354+
355+
/*
356+
type ModelWithOIDCOwnerField
357+
@model
358+
@auth(rules: [ { allow: owner, ownerField: "author" } ])
359+
{
360+
id: ID!
361+
content: String!
362+
author: String
363+
}
364+
*/
365+
public struct ModelWithOIDCOwnerField: Model {
366+
public let id: String
367+
public var content: String
368+
public var author: String?
369+
public init(id: String = UUID().uuidString,
370+
content: String,
371+
author: String?) {
372+
self.id = id
373+
self.content = content
374+
self.author = author
375+
}
376+
public enum CodingKeys: String, ModelKey {
377+
case id
378+
case content
379+
case author
380+
}
381+
public static let keys = CodingKeys.self
382+
383+
public static let schema = defineSchema { model in
384+
let modelWithOwnerField = ModelWithOwnerField.keys
385+
model.authRules = [
386+
rule(allow: .owner, ownerField: "author", provider: .oidc)
387+
]
388+
model.fields(
389+
.id(),
390+
.field(modelWithOwnerField.content, is: .required, ofType: .string),
391+
.field(modelWithOwnerField.author, is: .optional, ofType: .string))
392+
}
393+
}
394+
395+
/*
396+
Example of model with multiple authorization rules,
397+
and one of them doesn't require an `owner`.
398+
399+
type ModelWithOwnerField
400+
@model
401+
@auth(rules: [
402+
{ allow: owner, ownerField: "author" },
403+
{ allow: public, provider: "apiKey" }
404+
])
405+
{
406+
id: ID!
407+
content: String!
408+
author: String
409+
}
410+
*/
411+
public struct ModelWithMultipleAuthRules: Model {
412+
public let id: String
413+
public var content: String
414+
public var author: String?
415+
public init(id: String = UUID().uuidString,
416+
content: String,
417+
author: String?) {
418+
self.id = id
419+
self.content = content
420+
self.author = author
421+
}
422+
public enum CodingKeys: String, ModelKey {
423+
case id
424+
case content
425+
case author
426+
}
427+
public static let keys = CodingKeys.self
428+
429+
public static let schema = defineSchema { model in
430+
let modelWithMultipleAuthRules = ModelWithMultipleAuthRules.keys
431+
model.authRules = [
432+
rule(allow: .owner, ownerField: "author", provider: .userPools),
433+
rule(allow: .public, provider: .apiKey)
434+
]
435+
model.fields(
436+
.id(),
437+
.field(modelWithMultipleAuthRules.content, is: .required, ofType: .string),
438+
.field(modelWithMultipleAuthRules.author, is: .optional, ofType: .string))
439+
}
289440
}

0 commit comments

Comments
 (0)