Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(GraphQL): Allow Query with Auth rules on Interfaces #6776

Merged
merged 5 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions graphql/e2e/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ type Issue struct {
Owner *common.User `json:"owner,omitempty"`
}

type Author struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Posts []*Question `json:"posts,omitempty"`
}

type Question struct {
Id string `json:"id,omitempty"`
Text string `json:"text,omitempty"`
Author *Author `json:"author,omitempty"`
}

type Log struct {
Id string `json:"id,omitempty"`
Logs string `json:"logs,omitempty"`
Expand Down Expand Up @@ -118,6 +130,7 @@ type TaskOccurrence struct {
type TestCase struct {
user string
role string
ans bool
result string
name string
filter map[string]interface{}
Expand Down Expand Up @@ -348,6 +361,112 @@ func TestAuthWithDgraphDirective(t *testing.T) {
}
}

func TestAuthOnInterfaces(t *testing.T) {
TestCases := []TestCase{
{
name: "Types inherit Interface's auth rules and its own rules",
query: `
query{
queryQuestion{
text
}
}
`,
user: "[email protected]",
ans: true,
result: `{"queryQuestion":[{"text": "A Question"}]}`,
},
{
name: "Query Should return empty for non-existent user",
query: `
query{
queryQuestion{
text
}
}
`,
user: "[email protected]",
ans: true,
result: `{"queryQuestion":[]}`,
},
{
name: "Types inherit Only Interface's auth rules if it doesn't have its own auth rules",
query: `
query{
queryAnswer{
text
}
}
`,
user: "[email protected]",
result: `{"queryAnswer": [{"text": "A Answer"}]}`,
},
{
name: "Types inherit auth rules from all the different Interfaces",
query: `
query{
queryFbPost{
text
}
}
`,
user: "[email protected]",
role: "ADMIN",
result: `{"queryFbPost": [{"text": "B FbPost"}]}`,
},
{
name: "Query Interface should interhit auth rules from all the interfaces",
query: `
query{
queryPost(order: {asc: text}){
text
}
}
`,
user: "[email protected]",
ans: true,
role: "ADMIN",
result: `{"queryPost":[{"text": "A Answer"},{"text": "A FbPost"},{"text": "A Question"}]}`,
},
{
name: "Query Interface should return those implementing type whose auth rules are satisfied",
query: `
query{
queryPost(order: {asc: text}){
text
}
}
`,
user: "[email protected]",
ans: true,
result: `{"queryPost":[{"text": "A Answer"},{"text": "A Question"}]}`,
},
{
name: "Query Interface should return empty if the Auth rules of interface are not satisfied",
query: `
query{
queryPost(order: {asc: text}){
text
}
}
`,
ans: true,
result: `{"queryPost":[]}`,
},
}

for _, tcase := range TestCases {
t.Run(tcase.name, func(t *testing.T) {
getUserParams := &common.GraphQLParams{
Headers: common.GetJWTForInterfaceAuth(t, tcase.user, tcase.role, tcase.ans, metaInfo),
Query: tcase.query,
}
gqlResponse := getUserParams.ExecuteAsPost(t, graphqlURL)
require.Nil(t, gqlResponse.Errors)
require.JSONEq(t, tcase.result, string(gqlResponse.Data))
})
}
}
func TestAuthRulesWithMissingJWT(t *testing.T) {
testCases := []TestCase{
{name: "Query non auth field without JWT Token",
Expand Down
69 changes: 68 additions & 1 deletion graphql/e2e/auth/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -585,9 +585,76 @@ type TaskOccurrence @auth(
role: String @search(by: [exact, term, fulltext, regexp])
}

type Author {
id: ID!
name: String! @search(by: [exact])
posts: [Post] @hasInverse(field: author)
}

interface Post @auth(
query: { rule: """
query($USER: String!) {
queryPost{
author(filter: {name: {eq: $USER}}){
name
}
}
}""" }
){
id: ID!
text: String! @search(by: [exact])
datePublished: DateTime @search
author: Author!
}

interface MsgPost @auth(
query: { rule: "{$ROLE: { eq: \"ADMIN\" } }" }
){
sender: Author!
receiver: Author!
}

type Question implements Post @auth(
query:{ rule: """
query($ANS: Boolean!) {
queryQuestion(filter: { answered: $ANS } ) {
id
}
}""" }
){
answered: Boolean @search
}

type FbPost implements Post & MsgPost {
postCount: Int!
}

type Answer implements Post {
markedUseful: Boolean @search
}

interface A {
id: ID!
fieldA: String @search(by: [exact])
}

type B implements A {
fieldB: Boolean @search
}

type C implements A @auth(
query:{ rule: """
query($ANS: Boolean!) {
queryC(filter: { fieldC: $ANS } ) {
id
}
}""" }
){
fieldC: Boolean @search
}

type Todo {
id: ID
owner: String
text: String

}
50 changes: 49 additions & 1 deletion graphql/e2e/auth/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,53 @@
"dgraph.type": "UserSecret",
"UserSecret.aSecret": "Sensitive information",
"UserSecret.ownedBy": "user1"
}
},
{"uid":"_:Question1","Post.text":"A Question"},
{"uid":"_:Question2","Post.text":"B Question"},
{"uid":"_:Question3","Post.text":"C Question"},
{"uid":"_:FbPost2","Post.text":"B FbPost"},
{"uid":"_:FbPost1","Post.text":"A FbPost"},
{"uid":"_:Answer1","Post.text":"A Answer"},
{"uid":"_:Answer2","Post.text":"B Answer"},
{"uid":"_:Author1","Author.name":"[email protected]"},
{"uid":"_:Author2","Author.name":"[email protected]"},
{"uid":"_:Question1","Post.author":[{"uid":"_:Author1"}]},
{"uid":"_:Question2","Post.author":[{"uid":"_:Author2"}]},
{"uid":"_:Question3","Post.author":[{"uid":"_:Author1"}]},
{"uid":"_:FbPost2","Post.author":[{"uid":"_:Author2"}]},
{"uid":"_:FbPost1","Post.author":[{"uid":"_:Author1"}]},
{"uid":"_:Answer1","Post.author":[{"uid":"_:Author1"}]},
{"uid":"_:Answer2","Post.author":[{"uid":"_:Author2"}]},
{"uid":"_:Author1","dgraph.type":"Author"},
{"uid":"_:Author2","dgraph.type":"Author"},
{"uid":"_:Question1","dgraph.type":"Post"},
{"uid":"_:Question1","dgraph.type":"Question"},
{"uid":"_:Question2","dgraph.type":"Post"},
{"uid":"_:Question2","dgraph.type":"Question"},
{"uid":"_:Question3","dgraph.type":"Post"},
{"uid":"_:Question3","dgraph.type":"Question"},
{"uid":"_:FbPost2","dgraph.type":"Post"},
{"uid":"_:FbPost2","dgraph.type":"FbPost"},
{"uid":"_:FbPost2","dgraph.type":"MsgPost"},
{"uid":"_:FbPost1","dgraph.type":"Post"},
{"uid":"_:FbPost1","dgraph.type":"FbPost"},
{"uid":"_:FbPost1","dgraph.type":"MsgPost"},
{"uid":"_:Answer1","dgraph.type":"Answer"},
{"uid":"_:Answer1","dgraph.type":"Post"},
{"uid":"_:Answer2","dgraph.type":"Answer"},
{"uid":"_:Answer2","dgraph.type":"Post"},
{"uid":"_:Author1","Author.posts":[{"uid":"_:Question1"}]},
{"uid":"_:Author1","Author.posts":[{"uid":"_:Question3"}]},
{"uid":"_:Author2","Author.posts":[{"uid":"_:Question2"}]},
{"uid":"_:FbPost2","MsgPost.sender":[{"uid":"_:Author2"}]},
{"uid":"_:FbPost1","MsgPost.sender":[{"uid":"_:Author1"}]},
{"uid":"_:FbPost2","FbPost.postCount":2},
{"uid":"_:FbPost1","FbPost.postCount":1},
{"uid":"_:FbPost2","MsgPost.receiver":[{"uid":"_:Author1"}]},
{"uid":"_:FbPost1","MsgPost.receiver":[{"uid":"_:Author2"}]},
{"uid":"_:Question1","Question.answered":"true"},
{"uid":"_:Question2","Question.answered":"true"},
{"uid":"_:Question3","Question.answered":"false"},
{"uid":"_:Answer1","Answer.markedUseful":"true"},
{"uid":"_:Answer2","Answer.markedUseful":"true"}
]
20 changes: 20 additions & 0 deletions graphql/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,26 @@ func GetJWT(t *testing.T, user, role string, metaInfo *testutil.AuthMeta) http.H
return h
}

func GetJWTForInterfaceAuth(t *testing.T, user, role string, ans bool, metaInfo *testutil.AuthMeta) http.Header {
metaInfo.AuthVars = map[string]interface{}{}
if user != "" {
metaInfo.AuthVars["USER"] = user
}

if role != "" {
metaInfo.AuthVars["ROLE"] = role
}

metaInfo.AuthVars["ANS"] = ans

require.NotNil(t, metaInfo.PrivateKeyPath)
jwtToken, err := metaInfo.GetSignedToken(metaInfo.PrivateKeyPath, 300*time.Second)
require.NoError(t, err)
h := make(http.Header)
h.Add(metaInfo.Header, jwtToken)
return h
}

func BootstrapAuthData() ([]byte, []byte) {
schemaFile := "../auth/schema.graphql"
schema, err := ioutil.ReadFile(schemaFile)
Expand Down
2 changes: 1 addition & 1 deletion graphql/e2e/schema/generatedSchema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ directive @auth(
query: AuthRule,
add: AuthRule,
update: AuthRule,
delete:AuthRule) on OBJECT
delete:AuthRule) on OBJECT | INTERFACE
directive @custom(http: CustomHTTP, dql: String) on FIELD_DEFINITION
directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM
directive @cascade(fields: [String]) on FIELD
Expand Down
Loading