Skip to content

Commit

Permalink
fix(GraphQL): fix @cascade with Pagination for @auth queries. (#7695)
Browse files Browse the repository at this point in the history
Fixes GRAPHQL-1130.

(cherry picked from commit 828e98a)
  • Loading branch information
minhaj-shakeel authored and NamanJain8 committed Apr 9, 2021
1 parent 91c4967 commit ceadb6d
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 16 deletions.
118 changes: 114 additions & 4 deletions graphql/e2e/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Region struct {
type Movie struct {
Id string `json:"id,omitempty"`
Content string `json:"content,omitempty"`
Code string `json:"code,omitempty"`
Hidden bool `json:"hidden,omitempty"`
RegionsAvailable []*Region `json:"regionsAvailable,omitempty"`
}
Expand Down Expand Up @@ -638,11 +639,11 @@ func TestAuthRulesWithMissingJWT(t *testing.T) {
{name: "Query auth field without JWT Token",
query: `
query {
queryMovie {
queryMovie(order: {asc: content}) {
content
}
}`,
result: `{"queryMovie":[{"content":"Movie4"}]}`,
result: `{"queryMovie":[{"content":"Movie3"},{"content":"Movie4"}]}`,
},
{name: "Query empty auth field without JWT Token",
query: `
Expand Down Expand Up @@ -1425,7 +1426,10 @@ func TestNestedFilter(t *testing.T) {
},
{
"name": "Region4"
}
},
{
"name": "Region6"
}
]
},
{
Expand Down Expand Up @@ -1472,7 +1476,10 @@ func TestNestedFilter(t *testing.T) {
},
{
"name": "Region4"
}
},
{
"name": "Region6"
}
]
},
{
Expand Down Expand Up @@ -1514,6 +1521,109 @@ func TestNestedFilter(t *testing.T) {
}
}

func TestAuthPaginationWithCascade(t *testing.T) {
testCases := []TestCase{{
name: "Auth query with @cascade and pagination at top level",
user: "user1",
role: "ADMIN",
query: `
query {
queryMovie (order: {asc: content}, first: 2, offset: 0) @cascade{
content
code
regionsAvailable (order: {asc: name}){
name
}
}
}
`,
result: `
{
"queryMovie": [
{
"content": "Movie3",
"code": "m3",
"regionsAvailable": [
{
"name": "Region1"
},
{
"name": "Region4"
},
{
"name": "Region6"
}
]
},
{
"content": "Movie4",
"code": "m4",
"regionsAvailable": [
{
"name": "Region5"
}
]
}
]
}
`,
}, {
name: "Auth query with @cascade and pagination at deep level",
user: "user1",
role: "ADMIN",
query: `
query {
queryMovie (order: {asc: content}, first: 2, offset: 1) {
content
regionsAvailable (order: {asc: name}, first: 1) @cascade{
name
global
}
}
}
`,
result: `
{
"queryMovie": [
{
"content": "Movie3",
"regionsAvailable": [
{
"name": "Region6",
"global": true
}
]
},
{
"content": "Movie4",
"regionsAvailable": [
{
"name": "Region5",
"global": true
}
]
}
]
}
`,
}}

for _, tcase := range testCases {
t.Run(tcase.name, func(t *testing.T) {
getUserParams := &common.GraphQLParams{
Headers: common.GetJWT(t, tcase.user, tcase.role, metaInfo),
Query: tcase.query,
}

gqlResponse := getUserParams.ExecuteAsPost(t, common.GraphqlURL)
common.RequireNoGQLErrors(t, gqlResponse)

require.JSONEq(t, tcase.result, string(gqlResponse.Data))
})
}

}

func TestDeleteAuthRule(t *testing.T) {
AddDeleteAuthTestData(t)
testCases := []TestCase{
Expand Down
4 changes: 2 additions & 2 deletions graphql/e2e/auth/delete_mutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,11 +569,11 @@ func TestDeleteNestedFilter(t *testing.T) {
testCases := []TestCase{{
user: "user1",
role: "USER",
result: `{"deleteMovie":{"numUids":3,"movie":[{"content":"Movie2","regionsAvailable":[{"name":"Region1","global":null}]},{"content":"Movie3","regionsAvailable":[{"name":"Region1","global":null},{"name":"Region4","global":null}]},{"content":"Movie4","regionsAvailable":[{"name":"Region5","global":true}]}]}}`,
result: `{"deleteMovie":{"numUids":3,"movie":[{"content":"Movie2","regionsAvailable":[{"name":"Region1","global":null}]},{"content":"Movie3","regionsAvailable":[{"name":"Region1","global":null},{"name":"Region4","global":null},{"name":"Region6","global":true}]},{"content":"Movie4","regionsAvailable":[{"name":"Region5","global":true}]}]}}`,
}, {
user: "user2",
role: "USER",
result: `{"deleteMovie":{"numUids":4,"movie":[{"content":"Movie1","regionsAvailable":[{"name":"Region2","global":null},{"name":"Region3","global":null}]},{"content":"Movie2","regionsAvailable":[{"name":"Region1","global":null}]},{"content":"Movie3","regionsAvailable":[{"name":"Region1","global":null},{"name":"Region4","global":null}]},{"content":"Movie4","regionsAvailable":[{"name":"Region5","global":true}]}]}}`,
result: `{"deleteMovie":{"numUids":4,"movie":[{"content":"Movie1","regionsAvailable":[{"name":"Region2","global":null},{"name":"Region3","global":null}]},{"content":"Movie2","regionsAvailable":[{"name":"Region1","global":null}]},{"content":"Movie3","regionsAvailable":[{"name":"Region1","global":null},{"name":"Region4","global":null},{"name":"Region6","global":true}]},{"content":"Movie4","regionsAvailable":[{"name":"Region5","global":true}]}]}}`,
}}

query := `
Expand Down
1 change: 1 addition & 0 deletions graphql/e2e/auth/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ type Movie @auth(
regionsAvailable: [Region]
reviews: [Review]
random: String
code: String
}

type Issue @auth(
Expand Down
12 changes: 10 additions & 2 deletions graphql/e2e/auth/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
"Region.name": "Region5",
"Region.global": true
},
{
"uid": "_:region6",
"dgraph.type": "Region",
"Region.name": "Region6",
"Region.global": true
},
{
"uid": "_:movie1",
"dgraph.type": "Movie",
Expand All @@ -88,13 +94,15 @@
"uid": "_:movie3",
"dgraph.type": "Movie",
"Movie.content": "Movie3",
"Movie.regionsAvailable": [{"uid": "_:region1"}, {"uid": "_:region4"}],
"Movie.code": "m3",
"Movie.regionsAvailable": [{"uid": "_:region1"}, {"uid": "_:region4"}, {"uid": "_:region6"}],
"Movie.disabled": true
},
{
"uid": "_:movie4",
"dgraph.type": "Movie",
"Movie.content": "Movie4",
"Movie.code": "m4",
"Movie.regionsAvailable": [{"uid": "_:region5"}],
"Movie.reviews" : [{"uid": "_:review1"}]
},
Expand Down Expand Up @@ -301,4 +309,4 @@
{"uid":"_:Car1","dgraph.type":"Car"},
{"uid":"_:Car1","Vehicle.owner":"Bob"},
{"uid":"_:Car1","Car.manufacturer":"Tesla"}
]
]
1 change: 1 addition & 0 deletions graphql/e2e/auth/update_mutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ func getAllMovies(t *testing.T, users, roles []string) ([]*Movie, []string) {
queryMovie {
id
content
code
hidden
regionsAvailable {
id
Expand Down
74 changes: 72 additions & 2 deletions graphql/resolve/auth_query_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@
USER: "user1"
dgquery: |-
query {
queryMovie(func: uid(MovieRoot), orderasc: Movie.content) @cascade {
queryMovie(func: uid(MovieRoot), orderasc: Movie.content, first: 10, offset: 10) @cascade {
Movie.content : Movie.content
Movie.regionsAvailable : Movie.regionsAvailable @filter(uid(Region_1)) (orderasc: Region.name, first: 10, offset: 10) {
Region.name : Region.name
Expand All @@ -861,7 +861,7 @@
}
dgraph.uid : uid
}
MovieRoot as var(func: uid(Movie_3), orderasc: Movie.content, first: 10, offset: 10) @filter((NOT (uid(Movie_Auth4)) AND (uid(Movie_Auth5) OR uid(Movie_Auth6))))
MovieRoot as var(func: uid(Movie_3)) @filter((NOT (uid(Movie_Auth4)) AND (uid(Movie_Auth5) OR uid(Movie_Auth6))))
Movie_3 as var(func: type(Movie)) @filter(eq(Movie.content, "MovieXYZ"))
Movie_Auth4 as var(func: uid(Movie_3)) @filter(eq(Movie.hidden, true)) @cascade
Movie_Auth5 as var(func: uid(Movie_3)) @cascade {
Expand Down Expand Up @@ -948,6 +948,76 @@
UserSecret_Auth7 as var(func: uid(UserSecret_6)) @filter(eq(UserSecret.ownedBy, "user1")) @cascade
}
- name: "Auth deep query with @cascade at all the levels - 3 level"
gqlquery: |
query {
queryMovie(filter: { content: { eq: "MovieXYZ" } }, order: { asc: content }, first: 10, offset: 10) @cascade {
content
regionsAvailable(filter: { name: { eq: "Region123" } }, order: { asc: name }, first: 10, offset: 10) @cascade(fields: ["global"]) {
name
global
users(filter: { username: { eq: "User321" } }, order: { asc: username }, first: 10, offset: 10) @cascade {
username
age
isPublic
secrets(filter: { aSecret: { allofterms : "Secret132" } }, order: { asc: aSecret }, first: 10, offset: 10) @cascade(fields: ["ownedBy"]){
aSecret
ownedBy
}
}
}
}
}
jwtvar:
USER: "user1"
dgquery: |-
query {
queryMovie(func: uid(MovieRoot), orderasc: Movie.content, first: 10, offset: 10) @cascade {
Movie.content : Movie.content
Movie.regionsAvailable : Movie.regionsAvailable @filter(uid(Region_1)) (orderasc: Region.name, first: 10, offset: 10) @cascade(Region.global) {
Region.name : Region.name
Region.global : Region.global
Region.users : Region.users @filter(uid(User_3)) (orderasc: User.username, first: 10, offset: 10) @cascade {
User.username : User.username
User.age : User.age
User.isPublic : User.isPublic
User.secrets : User.secrets @filter(uid(UserSecret_5)) (orderasc: UserSecret.aSecret, first: 10, offset: 10) @cascade(UserSecret.ownedBy) {
UserSecret.aSecret : UserSecret.aSecret
UserSecret.ownedBy : UserSecret.ownedBy
dgraph.uid : uid
}
dgraph.uid : uid
}
dgraph.uid : uid
}
dgraph.uid : uid
}
MovieRoot as var(func: uid(Movie_8)) @filter((NOT (uid(Movie_Auth9)) AND (uid(Movie_Auth10) OR uid(Movie_Auth11))))
Movie_8 as var(func: type(Movie)) @filter(eq(Movie.content, "MovieXYZ"))
Movie_Auth9 as var(func: uid(Movie_8)) @filter(eq(Movie.hidden, true)) @cascade
Movie_Auth10 as var(func: uid(Movie_8)) @cascade {
Movie.regionsAvailable : Movie.regionsAvailable {
Region.users : Region.users @filter(eq(User.username, "user1"))
}
}
Movie_Auth11 as var(func: uid(Movie_8)) @cascade {
Movie.regionsAvailable : Movie.regionsAvailable @filter(eq(Region.global, true))
}
var(func: uid(MovieRoot)) {
Region_2 as Movie.regionsAvailable @filter(eq(Region.name, "Region123"))
}
Region_1 as var(func: uid(Region_2))
var(func: uid(Region_1)) {
User_4 as Region.users @filter(eq(User.username, "User321"))
}
User_3 as var(func: uid(User_4))
var(func: uid(User_3)) {
UserSecret_6 as User.secrets @filter(allofterms(UserSecret.aSecret, "Secret132"))
}
UserSecret_5 as var(func: uid(UserSecret_6)) @filter(uid(UserSecret_Auth7))
UserSecret_Auth7 as var(func: uid(UserSecret_6)) @filter(eq(UserSecret.ownedBy, "user1")) @cascade
}
- name: "Auth with complex filter"
gqlquery: |
query {
Expand Down
20 changes: 14 additions & 6 deletions graphql/resolve/query_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,8 @@ func (authRw *authRewriter) addAuthQueries(
// that has the order and pagination params from user query in it and filter set to auth
// queries built for this type. This is then used as the starting point for user query and
// auth queries for children.
// if @cascade directive is present in the user query then pagination and order are applied only
// on the user query and not on root query.
rootQry := &gql.GraphQuery{
Var: authRw.parentVarName,
Attr: "var",
Expand All @@ -839,16 +841,22 @@ func (authRw *authRewriter) addAuthQueries(
Args: []gql.Arg{{Value: authRw.varName}},
},
Filter: filter,
Order: dgQuery[0].Order, // we need the order here for pagination to work correctly
Args: dgQuery[0].Args, // this gets pagination from user query to root query
}

// The user query doesn't need the filter and pagination parameters anymore,
// as they have been taken care of by the var and root queries generated above.
// But, tt still needs the order parameter, even though it is also applied in root query.
// The user query doesn't need the filter parameter anymore,
// as it has been taken care of by the var and root queries generated above.
// But, it still needs the order parameter, even though it is also applied in root query.
// So, not setting order to nil.
dgQuery[0].Filter = nil
dgQuery[0].Args = nil

// if @cascade is not applied on the user query at root then shift pagination arguments
// from user query to root query for optimization and copy the order arguments for paginated
// query to work correctly.
if len(dgQuery[0].Cascade) == 0 {
rootQry.Args = dgQuery[0].Args
dgQuery[0].Args = nil
rootQry.Order = dgQuery[0].Order
}

// The user query starts from the root query generated above and so gets filtered
// input from auth processing, so now we build
Expand Down

0 comments on commit ceadb6d

Please sign in to comment.