diff --git a/graphql/e2e/auth/auth_test.go b/graphql/e2e/auth/auth_test.go index 385c7a60299..25e2f5371ce 100644 --- a/graphql/e2e/auth/auth_test.go +++ b/graphql/e2e/auth/auth_test.go @@ -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"` @@ -118,6 +130,7 @@ type TaskOccurrence struct { type TestCase struct { user string role string + ans bool result string name string filter map[string]interface{} @@ -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: "user1@dgraph.io", + ans: true, + result: `{"queryQuestion":[{"text": "A Question"}]}`, + }, + { + name: "Query Should return empty for non-existent user", + query: ` + query{ + queryQuestion{ + text + } + } + `, + user: "user3@dgraph.io", + 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: "user1@dgraph.io", + result: `{"queryAnswer": [{"text": "A Answer"}]}`, + }, + { + name: "Types inherit auth rules from all the different Interfaces", + query: ` + query{ + queryFbPost{ + text + } + } + `, + user: "user2@dgraph.io", + 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: "user1@dgraph.io", + 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: "user1@dgraph.io", + 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", diff --git a/graphql/e2e/auth/schema.graphql b/graphql/e2e/auth/schema.graphql index 69776e38c2e..9e9053665dc 100644 --- a/graphql/e2e/auth/schema.graphql +++ b/graphql/e2e/auth/schema.graphql @@ -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 - } diff --git a/graphql/e2e/auth/test_data.json b/graphql/e2e/auth/test_data.json index 8401210af90..9436560ab12 100644 --- a/graphql/e2e/auth/test_data.json +++ b/graphql/e2e/auth/test_data.json @@ -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":"user1@dgraph.io"}, + {"uid":"_:Author2","Author.name":"user2@dgraph.io"}, + {"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"} ] diff --git a/graphql/e2e/common/common.go b/graphql/e2e/common/common.go index 2948588e66e..44bf5461ad1 100644 --- a/graphql/e2e/common/common.go +++ b/graphql/e2e/common/common.go @@ -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) diff --git a/graphql/e2e/schema/generatedSchema.graphql b/graphql/e2e/schema/generatedSchema.graphql index a6bfe0b0850..077b41fb187 100644 --- a/graphql/e2e/schema/generatedSchema.graphql +++ b/graphql/e2e/schema/generatedSchema.graphql @@ -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 diff --git a/graphql/resolve/auth_query_test.yaml b/graphql/resolve/auth_query_test.yaml index 7c4a64c12a1..3e71756bd1e 100644 --- a/graphql/resolve/auth_query_test.yaml +++ b/graphql/resolve/auth_query_test.yaml @@ -1141,4 +1141,363 @@ comment : Review.comment dgraph.uid : uid } - } \ No newline at end of file + } + +- name: "Type should apply Interface's query rules and along with its own auth rules" + gqlquery: | + query { + queryQuestion { + id + text + } + } + jwtvar: + ANS: "true" + USER: "Random" + dgquery: |- + query { + queryQuestion(func: uid(QuestionRoot)) { + id : uid + text : Post.text + } + QuestionRoot as var(func: uid(Question1)) @filter((uid(QuestionAuth2) AND uid(QuestionAuth3))) + Question1 as var(func: type(Question)) + QuestionAuth2 as var(func: uid(Question1)) @filter(eq(Question.answered, true)) @cascade { + id : uid + } + QuestionAuth3 as var(func: uid(Question1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } + +- name: "Type should apply only Interface's query auth rules" + gqlquery: | + query { + queryAnswer { + id + text + } + } + jwtvar: + USER: "Random" + dgquery: |- + query { + queryAnswer(func: uid(AnswerRoot)) { + id : uid + text : Post.text + } + AnswerRoot as var(func: uid(Answer1)) @filter(uid(AnswerAuth2)) + Answer1 as var(func: type(Answer)) + AnswerAuth2 as var(func: uid(Answer1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } + +- name: "Type should apply query auth rules from all the interfaces that it implements." + gqlquery: | + query { + queryFbPost { + id + postCount + } + } + jwtvar: + ROLE: "ADMIN" + USER: "Random" + dgquery: |- + query { + queryFbPost(func: uid(FbPostRoot)) { + id : uid + postCount : FbPost.postCount + } + FbPostRoot as var(func: uid(FbPost1)) @filter(uid(FbPostAuth2)) + FbPost1 as var(func: type(FbPost)) + FbPostAuth2 as var(func: uid(FbPost1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } + +- name: "Type which inherits auth rules from interfaces returns no results when auth rules fail" + gqlquery: | + query { + queryFbPost { + id + postCount + } + } + jwtvar: + ROLE: "REGULAR" + USER: "Random" + dgquery: |- + query { + queryFbPost() + } + +- name: "Auth rules of All the implementing types should Apply to the interface also" + gqlquery: | + query { + queryPost { + text + } + } + jwtvar: + ROLE: "ADMIN" + ANS: "true" + USER: "Random" + dgquery: |- + query { + queryPost(func: uid(PostRoot)) { + dgraph.type + text : Post.text + dgraph.uid : uid + } + PostRoot as var(func: uid(Post1)) @filter(((uid(QuestionAuth2) AND uid(QuestionAuth3)) OR uid(FbPostAuth4) OR uid(AnswerAuth5))) + Post1 as var(func: type(Post)) + Question1 as var(func: type(Question)) + QuestionAuth2 as var(func: uid(Question1)) @filter(eq(Question.answered, true)) @cascade { + id : uid + } + QuestionAuth3 as var(func: uid(Question1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + FbPost1 as var(func: type(FbPost)) + FbPostAuth4 as var(func: uid(FbPost1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + Answer1 as var(func: type(Answer)) + AnswerAuth5 as var(func: uid(Answer1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } +- name: "Filters on query Interface should work correctly" + gqlquery: | + query { + queryPost(filter: {text: {eq: "A Post"}}, order: { desc: text}, first: 10, offset: 5 ) { + text + } + } + jwtvar: + ROLE: "ADMIN" + ANS: "true" + USER: "Random" + dgquery: |- + query { + queryPost(func: uid(PostRoot), orderdesc: Post.text, first: 10, offset: 5) { + dgraph.type + text : Post.text + dgraph.uid : uid + } + PostRoot as var(func: uid(Post1)) @filter(((uid(QuestionAuth2) AND uid(QuestionAuth3)) OR uid(FbPostAuth4) OR uid(AnswerAuth5))) + Post1 as var(func: type(Post)) @filter(eq(Post.text, "A Post")) + Question1 as var(func: type(Question)) + QuestionAuth2 as var(func: uid(Question1)) @filter(eq(Question.answered, true)) @cascade { + id : uid + } + QuestionAuth3 as var(func: uid(Question1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + FbPost1 as var(func: type(FbPost)) + FbPostAuth4 as var(func: uid(FbPost1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + Answer1 as var(func: type(Answer)) + AnswerAuth5 as var(func: uid(Answer1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } +- name: "Query interface should return empty if the auth rule of interface is not satisfied" + gqlquery: | + query { + queryPost { + text + } + } + jwtvar: + ROLE: "ADMIN" + ANS: "true" + dgquery: |- + query { + queryPost() + } +- name: "Query interface should return partial types if the auth rule of interface is not satisfied" + gqlquery: | + query { + queryPost { + text + } + } + jwtvar: + USER: "Random" + ANS: "true" + dgquery: |- + query { + queryPost(func: uid(PostRoot)) { + dgraph.type + text : Post.text + dgraph.uid : uid + } + PostRoot as var(func: uid(Post1)) @filter(((uid(QuestionAuth2) AND uid(QuestionAuth3)) OR uid(AnswerAuth4))) + Post1 as var(func: type(Post)) + Question1 as var(func: type(Question)) + QuestionAuth2 as var(func: uid(Question1)) @filter(eq(Question.answered, true)) @cascade { + id : uid + } + QuestionAuth3 as var(func: uid(Question1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + Answer1 as var(func: type(Answer)) + AnswerAuth4 as var(func: uid(Answer1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } +- name: "Get Query interface having Auth Rules apply Auth filters of types also" + gqlquery: | + query { + getPost(id: "0x1") { + text + } + } + jwtvar: + USER: "Random" + ANS: "true" + dgquery: |- + query { + getPost(func: uid(PostRoot)) @filter(type(Post)) { + dgraph.type + text : Post.text + dgraph.uid : uid + } + PostRoot as var(func: uid(Post1)) @filter(((uid(QuestionAuth2) AND uid(QuestionAuth3)) OR uid(AnswerAuth4))) + Post1 as var(func: uid(0x1)) + Question1 as var(func: type(Question)) + QuestionAuth2 as var(func: uid(Question1)) @filter(eq(Question.answered, true)) @cascade { + id : uid + } + QuestionAuth3 as var(func: uid(Question1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + Answer1 as var(func: type(Answer)) + AnswerAuth4 as var(func: uid(Answer1)) @cascade { + dgraph.type + author : Post.author @filter(eq(Author.name, "Random")) { + name : Author.name + dgraph.uid : uid + } + dgraph.uid : uid + } + } +- name: "Get Query interface having Auth Rules should return empty if the Auth rules are not satisfied" + gqlquery: | + query { + getPost(id: "0x1") { + text + } + } + jwtvar: + dgquery: |- + query { + getPost() + } +- name: "Query interface having no Auth Rules should apply auth rules on implementing types that are satisfied" + gqlquery: | + query { + queryA { + fieldA + } + } + jwtvar: + ANS: "true" + dgquery: |- + query { + queryA(func: uid(ARoot)) { + dgraph.type + fieldA : A.fieldA + dgraph.uid : uid + } + ARoot as var(func: uid(A1)) @filter((uid(B1) OR uid(CAuth2))) + A1 as var(func: type(A)) + B1 as var(func: type(B)) + C1 as var(func: type(C)) + CAuth2 as var(func: uid(C1)) @filter(eq(C.fieldC, true)) @cascade { + id : uid + } + } +- name: "Query interface having no Auth Rules but some type have Auth rules and those are not satified are excluded (for eg: type C )" + gqlquery: | + query { + queryA { + fieldA + } + } + jwtvar: + dgquery: |- + query { + queryA(func: uid(ARoot)) { + dgraph.type + fieldA : A.fieldA + dgraph.uid : uid + } + ARoot as var(func: uid(A1)) @filter((uid(B1))) + A1 as var(func: type(A)) + B1 as var(func: type(B)) + } diff --git a/graphql/resolve/query_rewriter.go b/graphql/resolve/query_rewriter.go index 3c40aa303b9..6ad3a4c142a 100644 --- a/graphql/resolve/query_rewriter.go +++ b/graphql/resolve/query_rewriter.go @@ -89,10 +89,6 @@ func (qr *queryRewriter) Rewrite( ctx context.Context, gqlQuery schema.Query) (*gql.GraphQuery, error) { - if gqlQuery.ConstructedFor().InterfaceImplHasAuthRules() { - return &gql.GraphQuery{Attr: gqlQuery.ResponseName() + "()"}, nil - } - authVariables, _ := ctx.Value(authorization.AuthVariables).(map[string]interface{}) if authVariables == nil { @@ -356,16 +352,37 @@ func rewriteAsGet( var dgQuery *gql.GraphQuery rbac := auth.evaluateStaticRules(field.Type()) + + // If Get query is for Type and none of the authrules are satisfied, then it is + // caught here but in case of interface, we need to check validity on each + // implementing type as Rules for the interface are made empty. if rbac == schema.Negative { return &gql.GraphQuery{Attr: field.ResponseName() + "()"} } + // For interface, empty query should be returned if Auth rules are + // not satisfied even for a single implementing type + if field.Type().IsInterface() { + implementingTypesHasFailedRules := false + implementingTypes := field.Type().ImplementingTypes() + for _, typ := range implementingTypes { + if auth.evaluateStaticRules(typ) != schema.Negative { + implementingTypesHasFailedRules = true + } + } + + if !implementingTypesHasFailedRules { + return &gql.GraphQuery{Attr: field.ResponseName() + "()"} + } + } + if xid == nil { dgQuery = rewriteAsQueryByIds(field, []uint64{uid}, auth) // Add the type filter to the top level get query. When the auth has been written into the // query the top level get query may be present in query's children. addTopLevelTypeFilter(dgQuery, field) + return dgQuery } @@ -425,7 +442,7 @@ func addCommonRules(field schema.Field, fieldType schema.Type, authRw *authRewri if authRw != nil && (authRw.isWritingAuth || authRw.filterByUid) && (authRw.varName != "" || authRw.parentVarName != "") { // When rewriting auth rules, they always start like - // Todo2 as var(func: uid(Todo1)) @cascade { + // Todo2 as var(func: uid(Todo1)) @cascade { // Where Todo1 is the variable generated from the filter of the field // we are adding auth to. @@ -488,6 +505,112 @@ func (authRw *authRewriter) addAuthQueries( authRw.varName = authRw.varGen.Next(typ, "", "", authRw.isWritingAuth) fldAuthQueries, filter := authRw.rewriteAuthQueries(typ) + + // If We are adding AuthRules on an Interfaces's operation, + // we need to construct auth filters by verifying Auth rules on the + // implementing types. + + if typ.IsInterface() { + // First we fetch the list of Implementing types here + implementingTypes := make([]schema.Type, 0) + implementingTypes = append(implementingTypes, typ.ImplementingTypes()...) + + var qrys []*gql.GraphQuery + var filts []*gql.FilterTree + implementingTypesHasQueryAuthRules := false + for _, object := range implementingTypes { + + // It could be the case that None of implementing Types have Auth Rules, which clearly + // indicates that neither the interface, nor any of the implementing type has its own + // Auth rules. + // ImplementingTypeHasAuthRules is set to true even if one of the implemented type have + // query Auth rules or Interface has its own Query auth rule, in the latter case, all the + // implemented types must have inherited those auth rules. + if object.AuthRules().Rules != nil && object.AuthRules().Rules.Query != nil { + implementingTypesHasQueryAuthRules = true + } + + // First Check if the Auth Rules of the given type are satisfied or not. + // It might be possible that auth rule inherited from some other interface + // is not being satisfied. In that case we have to Drop this type + rbac := authRw.evaluateStaticRules(object) + if rbac == schema.Negative { + continue + } + + // Form Query Like Todo1 as var(func: type(Todo)) + queryVar := object.Name() + "1" + varQry := &gql.GraphQuery{ + Attr: "var", + Var: queryVar, + Func: &gql.Function{ + Name: "type", + Args: []gql.Arg{{Value: object.Name()}}, + }, + } + qrys = append(qrys, varQry) + + // Form Auth Queries for the given object + objAuthQueries, objfilter := (&authRewriter{ + authVariables: authRw.authVariables, + varGen: authRw.varGen, + varName: queryVar, + selector: authRw.selector, + parentVarName: authRw.parentVarName, + hasAuthRules: authRw.hasAuthRules, + }).rewriteAuthQueries(object) + + // If there is no Auth Query for the Given type then it means that + // neither the inherited interface, nor this type has any query Auth rules. + // In this case the query must return all the nodes of this type. + // then simply we need to Put uid(Todo1) with OR in the main query filter. + if len(objAuthQueries) == 0 { + objfilter = &gql.FilterTree{ + Func: &gql.Function{ + Name: "uid", + Args: []gql.Arg{gql.Arg{Value: queryVar, IsValueVar: false, IsGraphQLVar: false}}, + }, + } + filts = append(filts, objfilter) + } else { + qrys = append(qrys, objAuthQueries...) + filts = append(filts, objfilter) + } + } + + // For an interface having Auth rules in some of the implementing types, len(qrys) = 0 + // indicates that None of the type satisfied the Auth rules, We must return Empty Query here. + if implementingTypesHasQueryAuthRules == true && len(qrys) == 0 { + return &gql.GraphQuery{ + Attr: dgQuery.Attr + "()", + } + } + + // Join all the queries in qrys using OR filter and + // append these queries into fldAuthQueries + fldAuthQueries = append(fldAuthQueries, qrys...) + objOrfilter := &gql.FilterTree{ + Op: "or", + Child: filts, + } + + // if filts is non empty, which means it was a query on interface + // having Either any of the types satisfying auth rules or having + // some type with no Auth rules, In this case, the query will be different + // and will look somewhat like this: + // PostRoot as var(func: uid(Post1)) @filter((uid(QuestionAuth2) OR uid(AnswerAuth4))) + if len(filts) > 0 { + filter = objOrfilter + } + + // Adding the case of Query on interface in which None of the implementing type have + // Auth Query Rules, in that case, we also return simple query. + if typ.IsInterface() == true && implementingTypesHasQueryAuthRules == false { + return dgQuery + } + + } + if len(fldAuthQueries) == 0 && !authRw.hasAuthRules { return dgQuery } @@ -665,6 +788,13 @@ func (authRw *authRewriter) rewriteRuleNode( Args: []gql.Arg{{Value: varName}}, }, } + case rn.DQLRule != nil: + return []*gql.GraphQuery{rn.DQLRule}, &gql.FilterTree{ + Func: &gql.Function{ + Name: "uid", + Args: []gql.Arg{{Value: rn.DQLRule.Var}}, + }, + } } return nil, nil } diff --git a/graphql/schema/auth.go b/graphql/schema/auth.go index 78cdfbeaecd..4ceb5d2e243 100644 --- a/graphql/schema/auth.go +++ b/graphql/schema/auth.go @@ -20,6 +20,7 @@ import ( "regexp" "strings" + "github.com/dgraph-io/dgraph/gql" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/parser" @@ -41,6 +42,7 @@ type RuleNode struct { And []*RuleNode Not *RuleNode Rule Query + DQLRule *gql.GraphQuery RBACRule *RBACQuery Variables ast.VariableDefinitionList } @@ -142,6 +144,18 @@ type TypeAuth struct { Fields map[string]*AuthContainer } +func createEmptyDQLRule(typeName string) *RuleNode { + return &RuleNode{DQLRule: &gql.GraphQuery{ + Attr: typeName + "Root", + Var: typeName + "Root", + Func: &gql.Function{ + Name: "type", + Args: []gql.Arg{{Value: typeName}}, + }, + }, + } +} + func authRules(s *ast.Schema) (map[string]*TypeAuth, error) { //TODO: Add position in error. var errResult, err error @@ -165,9 +179,79 @@ func authRules(s *ast.Schema) (map[string]*TypeAuth, error) { } } + // Merge the Auth rules on interfaces into the implementing types + for _, typ := range s.Types { + name := typeName(typ) + if typ.Kind == ast.Object { + for _, intrface := range typ.Interfaces { + interfaceName := typeName(s.Types[intrface]) + if authRules[interfaceName] != nil && authRules[interfaceName].Rules != nil { + authRules[name].Rules = mergeAuthRules(authRules[name].Rules, authRules[interfaceName].Rules, mergeAuthNodeWithAnd) + } + } + } + } + + // Reinitialize the Interface's auth to be empty as Any operation on interface + // will be broken into an operation on subsequent implementing types and auth rules + // will be verified against the types only. + for _, typ := range s.Types { + name := typeName(typ) + if typ.Kind == ast.Interface { + authRules[name] = &TypeAuth{} + } + } + return authRules, errResult } +func mergeAuthNodeWithOr(objectAuth, interfaceAuth *RuleNode) *RuleNode { + if objectAuth == nil { + return interfaceAuth + } + + if interfaceAuth == nil { + return objectAuth + } + + ruleNode := &RuleNode{} + ruleNode.Or = append(ruleNode.Or, objectAuth, interfaceAuth) + return ruleNode +} + +func mergeAuthNodeWithAnd(objectAuth, interfaceAuth *RuleNode) *RuleNode { + if objectAuth == nil { + return interfaceAuth + } + + if interfaceAuth == nil { + return objectAuth + } + + ruleNode := &RuleNode{} + ruleNode.And = append(ruleNode.And, objectAuth, interfaceAuth) + return ruleNode +} + +func mergeAuthRules(objectAuthRules, interfaceAuthRules *AuthContainer, mergeAuthNode func(*RuleNode, *RuleNode) *RuleNode) *AuthContainer { + // return copy of interfaceAuthRules since it is a pointer and otherwise it will lead + // to unnecessary errors + if objectAuthRules == nil { + return &AuthContainer{ + Query: interfaceAuthRules.Query, + Add: interfaceAuthRules.Add, + Delete: interfaceAuthRules.Delete, + Update: interfaceAuthRules.Update, + } + } + + objectAuthRules.Query = mergeAuthNode(objectAuthRules.Query, interfaceAuthRules.Query) + objectAuthRules.Add = mergeAuthNode(objectAuthRules.Add, interfaceAuthRules.Add) + objectAuthRules.Delete = mergeAuthNode(objectAuthRules.Delete, interfaceAuthRules.Delete) + objectAuthRules.Update = mergeAuthNode(objectAuthRules.Update, interfaceAuthRules.Update) + return objectAuthRules +} + func parseAuthDirective( s *ast.Schema, typ *ast.Definition, diff --git a/graphql/schema/gqlschema.go b/graphql/schema/gqlschema.go index 0eccc3132a5..76eb5c98c8e 100644 --- a/graphql/schema/gqlschema.go +++ b/graphql/schema/gqlschema.go @@ -270,7 +270,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 @@ -514,7 +514,7 @@ var directiveLocationMap = map[string]map[ast.DefinitionKind]bool{ idDirective: nil, subscriptionDirective: {ast.Object: true, ast.Interface: true}, secretDirective: {ast.Object: true, ast.Interface: true}, - authDirective: {ast.Object: true}, + authDirective: {ast.Object: true, ast.Interface: true}, customDirective: nil, remoteDirective: {ast.Object: true, ast.Interface: true, ast.Union: true, ast.InputObject: true, ast.Enum: true}, diff --git a/graphql/schema/gqlschema_test.yml b/graphql/schema/gqlschema_test.yml index 01aa86e37bd..70b921df2bb 100644 --- a/graphql/schema/gqlschema_test.yml +++ b/graphql/schema/gqlschema_test.yml @@ -2483,29 +2483,6 @@ invalid_schemas: "locations":[{"line":1, "column":6}]}, ] - - name: "@auth on interface" - input: | - interface X @auth( - query: { rule: """ - query($USER: String!) { - queryX(filter: { username: { eq: $USER } }) { - __typename - } - } - """ } - ){ - username: String! @id - age: Int - } - type Y implements X { - userRole: String @search(by: [hash]) - } - errlist: [ - {"message": "Type X; has the @auth directive, but it is not applicable on types of INTERFACE kind.", - "locations":[{"line":1, "column":14}]}, - ] - - - name: "There shoudnt be any reserved arguments on any field" input: | type T { diff --git a/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql b/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql new file mode 100644 index 00000000000..aeea83344ad --- /dev/null +++ b/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql @@ -0,0 +1,30 @@ +type Author { + id: ID! + name: String! @search(by: [hash]) + posts: [Post] @hasInverse(field: author) +} + +interface Post @auth( + query: { rule: """ + query($TEXT: String!) { + queryPost(filter: { text : {eq: $TEXT } } ) { + id + } + }""" } +){ + id: ID! + text: String @search(by: [exact]) + datePublished: DateTime @search + author: Author! +} + +type Question implements Post @auth( + query: { rule: """ + query($ANS: Boolean!) { + queryQuestion(filter: { answered: $ANS } ) { + id + } + }""" } +){ + answered: Boolean @search +} \ No newline at end of file diff --git a/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql b/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql new file mode 100644 index 00000000000..66a1932eb41 --- /dev/null +++ b/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql @@ -0,0 +1,524 @@ +####################### +# Input Schema +####################### + +type Author { + id: ID! + name: String! @search(by: [hash]) + posts(filter: PostFilter, order: PostOrder, first: Int, offset: Int): [Post] @hasInverse(field: author) +} + +interface Post @auth(query: {rule:"query($TEXT: String!) { \n queryPost(filter: { text : {eq: $TEXT } } ) { \n id \n } \n}"}) { + id: ID! + text: String @search(by: [exact]) + datePublished: DateTime @search + author(filter: AuthorFilter): Author! @hasInverse(field: posts) +} + +type Question implements Post @auth(query: {rule:"query($ANS: Boolean!) { \n queryQuestion(filter: { answered: $ANS } ) { \n id \n } \n}"}) { + id: ID! + text: String @search(by: [exact]) + datePublished: DateTime @search + author(filter: AuthorFilter): Author! @hasInverse(field: posts) + answered: Boolean @search +} + +####################### +# Extended Definitions +####################### + +""" +The Int64 scalar type represents a signed 64‐bit numeric non‐fractional value. +Int64 can represent values in range [-(2^63),(2^63 - 1)]. +""" +scalar Int64 + +""" +The DateTime scalar type represents date and time as a string in RFC3339 format. +For example: "1985-04-12T23:20:50.52Z" represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC. +""" +scalar DateTime + +input IntRange{ + min: Int + max: Int +} + +input FloatRange{ + min: Float + max: Float +} + +input Int64Range{ + min: Int64 + max: Int64 +} + +input DateTimeRange{ + min: DateTime + max: DateTime +} + +input StringRange{ + min: String + max: String +} + +enum DgraphIndex { + int + int64 + float + bool + hash + exact + term + fulltext + trigram + regexp + year + month + day + hour + geo +} + +input AuthRule { + and: [AuthRule] + or: [AuthRule] + not: AuthRule + rule: String +} + +enum HTTPMethod { + GET + POST + PUT + PATCH + DELETE +} + +enum Mode { + BATCH + SINGLE +} + +input CustomHTTP { + url: String! + method: HTTPMethod! + body: String + graphql: String + mode: Mode + forwardHeaders: [String!] + secretHeaders: [String!] + introspectionHeaders: [String!] + skipIntrospection: Boolean +} + +type Point { + longitude: Float! + latitude: Float! +} + +input PointRef { + longitude: Float! + latitude: Float! +} + +input NearFilter { + distance: Float! + coordinate: PointRef! +} + +input PointGeoFilter { + near: NearFilter + within: WithinFilter +} + +type PointList { + points: [Point!]! +} + +input PointListRef { + points: [PointRef!]! +} + +type Polygon { + coordinates: [PointList!]! +} + +input PolygonRef { + coordinates: [PointListRef!]! +} + +type MultiPolygon { + polygons: [Polygon!]! +} + +input MultiPolygonRef { + polygons: [PolygonRef!]! +} + +input WithinFilter { + polygon: PolygonRef! +} + +input ContainsFilter { + point: PointRef + polygon: PolygonRef +} + +input IntersectsFilter { + polygon: PolygonRef + multiPolygon: MultiPolygonRef +} + +input PolygonGeoFilter { + near: NearFilter + within: WithinFilter + contains: ContainsFilter + intersects: IntersectsFilter +} + +input GenerateQueryParams { + get: Boolean + query: Boolean + password: Boolean + aggregate: Boolean +} + +input GenerateMutationParams { + add: Boolean + update: Boolean + delete: Boolean +} + +directive @hasInverse(field: String!) on FIELD_DEFINITION +directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION +directive @id on FIELD_DEFINITION +directive @withSubscription on OBJECT | INTERFACE +directive @secret(field: String!, pred: String) on OBJECT | INTERFACE +directive @auth( + query: AuthRule, + add: AuthRule, + update: AuthRule, + 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 +directive @lambda on FIELD_DEFINITION +directive @cacheControl(maxAge: Int!) on QUERY +directive @generate( + query: GenerateQueryParams, + mutation: GenerateMutationParams, + subscription: Boolean) on OBJECT | INTERFACE + +input IntFilter { + eq: Int + le: Int + lt: Int + ge: Int + gt: Int + between: IntRange +} + +input Int64Filter { + eq: Int64 + le: Int64 + lt: Int64 + ge: Int64 + gt: Int64 + between: Int64Range +} + +input FloatFilter { + eq: Float + le: Float + lt: Float + ge: Float + gt: Float + between: FloatRange +} + +input DateTimeFilter { + eq: DateTime + le: DateTime + lt: DateTime + ge: DateTime + gt: DateTime + between: DateTimeRange +} + +input StringTermFilter { + allofterms: String + anyofterms: String +} + +input StringRegExpFilter { + regexp: String +} + +input StringFullTextFilter { + alloftext: String + anyoftext: String +} + +input StringExactFilter { + eq: String + in: [String] + le: String + lt: String + ge: String + gt: String + between: StringRange +} + +input StringHashFilter { + eq: String + in: [String] +} + +####################### +# Generated Types +####################### + +type AddAuthorPayload { + author(filter: AuthorFilter, order: AuthorOrder, first: Int, offset: Int): [Author] + numUids: Int +} + +type AddQuestionPayload { + question(filter: QuestionFilter, order: QuestionOrder, first: Int, offset: Int): [Question] + numUids: Int +} + +type AuthorAggregateResult { + count: Int +} + +type DeleteAuthorPayload { + author(filter: AuthorFilter, order: AuthorOrder, first: Int, offset: Int): [Author] + msg: String + numUids: Int +} + +type DeletePostPayload { + post(filter: PostFilter, order: PostOrder, first: Int, offset: Int): [Post] + msg: String + numUids: Int +} + +type DeleteQuestionPayload { + question(filter: QuestionFilter, order: QuestionOrder, first: Int, offset: Int): [Question] + msg: String + numUids: Int +} + +type PostAggregateResult { + count: Int +} + +type QuestionAggregateResult { + count: Int +} + +type UpdateAuthorPayload { + author(filter: AuthorFilter, order: AuthorOrder, first: Int, offset: Int): [Author] + numUids: Int +} + +type UpdatePostPayload { + post(filter: PostFilter, order: PostOrder, first: Int, offset: Int): [Post] + numUids: Int +} + +type UpdateQuestionPayload { + question(filter: QuestionFilter, order: QuestionOrder, first: Int, offset: Int): [Question] + numUids: Int +} + +####################### +# Generated Enums +####################### + +enum AuthorHasFilter { + name + posts +} + +enum AuthorOrderable { + name +} + +enum PostHasFilter { + text + datePublished + author +} + +enum PostOrderable { + text + datePublished +} + +enum QuestionHasFilter { + text + datePublished + author + answered +} + +enum QuestionOrderable { + text + datePublished +} + +####################### +# Generated Inputs +####################### + +input AddAuthorInput { + name: String! + posts: [PostRef] +} + +input AddQuestionInput { + text: String + datePublished: DateTime + author: AuthorRef! + answered: Boolean +} + +input AuthorFilter { + id: [ID!] + name: StringHashFilter + has: AuthorHasFilter + and: AuthorFilter + or: AuthorFilter + not: AuthorFilter +} + +input AuthorOrder { + asc: AuthorOrderable + desc: AuthorOrderable + then: AuthorOrder +} + +input AuthorPatch { + name: String + posts: [PostRef] +} + +input AuthorRef { + id: ID + name: String + posts: [PostRef] +} + +input PostFilter { + id: [ID!] + text: StringExactFilter + datePublished: DateTimeFilter + has: PostHasFilter + and: PostFilter + or: PostFilter + not: PostFilter +} + +input PostOrder { + asc: PostOrderable + desc: PostOrderable + then: PostOrder +} + +input PostPatch { + text: String + datePublished: DateTime + author: AuthorRef +} + +input PostRef { + id: ID! +} + +input QuestionFilter { + id: [ID!] + text: StringExactFilter + datePublished: DateTimeFilter + answered: Boolean + has: QuestionHasFilter + and: QuestionFilter + or: QuestionFilter + not: QuestionFilter +} + +input QuestionOrder { + asc: QuestionOrderable + desc: QuestionOrderable + then: QuestionOrder +} + +input QuestionPatch { + text: String + datePublished: DateTime + author: AuthorRef + answered: Boolean +} + +input QuestionRef { + id: ID + text: String + datePublished: DateTime + author: AuthorRef + answered: Boolean +} + +input UpdateAuthorInput { + filter: AuthorFilter! + set: AuthorPatch + remove: AuthorPatch +} + +input UpdatePostInput { + filter: PostFilter! + set: PostPatch + remove: PostPatch +} + +input UpdateQuestionInput { + filter: QuestionFilter! + set: QuestionPatch + remove: QuestionPatch +} + +####################### +# Generated Query +####################### + +type Query { + getAuthor(id: ID!): Author + queryAuthor(filter: AuthorFilter, order: AuthorOrder, first: Int, offset: Int): [Author] + aggregateAuthor(filter: AuthorFilter): AuthorAggregateResult + getPost(id: ID!): Post + queryPost(filter: PostFilter, order: PostOrder, first: Int, offset: Int): [Post] + aggregatePost(filter: PostFilter): PostAggregateResult + getQuestion(id: ID!): Question + queryQuestion(filter: QuestionFilter, order: QuestionOrder, first: Int, offset: Int): [Question] + aggregateQuestion(filter: QuestionFilter): QuestionAggregateResult +} + +####################### +# Generated Mutations +####################### + +type Mutation { + addAuthor(input: [AddAuthorInput!]!): AddAuthorPayload + updateAuthor(input: UpdateAuthorInput!): UpdateAuthorPayload + deleteAuthor(filter: AuthorFilter!): DeleteAuthorPayload + updatePost(input: UpdatePostInput!): UpdatePostPayload + deletePost(filter: PostFilter!): DeletePostPayload + addQuestion(input: [AddQuestionInput!]!): AddQuestionPayload + updateQuestion(input: UpdateQuestionInput!): UpdateQuestionPayload + deleteQuestion(filter: QuestionFilter!): DeleteQuestionPayload +} + diff --git a/graphql/schema/testdata/schemagen/output/authorization.graphql b/graphql/schema/testdata/schemagen/output/authorization.graphql index e933f7920b1..847acc99e13 100644 --- a/graphql/schema/testdata/schemagen/output/authorization.graphql +++ b/graphql/schema/testdata/schemagen/output/authorization.graphql @@ -197,7 +197,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 diff --git a/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql b/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql index 34dcf94db0d..68aa2013f20 100755 --- a/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql +++ b/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql @@ -212,7 +212,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 diff --git a/graphql/schema/testdata/schemagen/output/custom-mutation.graphql b/graphql/schema/testdata/schemagen/output/custom-mutation.graphql index 98abdc018e9..4c8c9558778 100644 --- a/graphql/schema/testdata/schemagen/output/custom-mutation.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-mutation.graphql @@ -190,7 +190,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 diff --git a/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql b/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql index 7a266bd2064..a254e20ecc7 100755 --- a/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql @@ -207,7 +207,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 diff --git a/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql b/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql index 2c6b08b00a1..238af9345a8 100644 --- a/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql @@ -191,7 +191,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 diff --git a/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql index 6270f7f6d44..a132e6d6033 100755 --- a/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql @@ -190,7 +190,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 diff --git a/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql index 1b9b2ab3832..edffe81ad71 100755 --- a/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql @@ -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 diff --git a/graphql/schema/testdata/schemagen/output/deprecated.graphql b/graphql/schema/testdata/schemagen/output/deprecated.graphql index 365a2ee14eb..8bbfb377cea 100755 --- a/graphql/schema/testdata/schemagen/output/deprecated.graphql +++ b/graphql/schema/testdata/schemagen/output/deprecated.graphql @@ -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 diff --git a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql index 662971b16cb..03986a5b13b 100755 --- a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql @@ -200,7 +200,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 diff --git a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql index ece0ded76f5..19fc02bccbd 100755 --- a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql @@ -200,7 +200,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 diff --git a/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql b/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql index 564c50544b4..568531ac356 100755 --- a/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql @@ -199,7 +199,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 diff --git a/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql b/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql index 50cd11763de..26407fb628d 100755 --- a/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql @@ -193,7 +193,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 diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql index 2b800017c9b..8f27cd18a35 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql @@ -194,7 +194,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 diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql index 35e505105b5..a13718f1bae 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql @@ -196,7 +196,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 diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql index ae4eb4164f5..68c2f598026 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql @@ -190,7 +190,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 diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql index 2fa00a1133d..e4870db51c9 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql @@ -196,7 +196,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 diff --git a/graphql/schema/testdata/schemagen/output/generate-directive.graphql b/graphql/schema/testdata/schemagen/output/generate-directive.graphql index f727a30e2de..19cfbc5910b 100644 --- a/graphql/schema/testdata/schemagen/output/generate-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/generate-directive.graphql @@ -199,7 +199,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 diff --git a/graphql/schema/testdata/schemagen/output/geo-type.graphql b/graphql/schema/testdata/schemagen/output/geo-type.graphql index 107aedd5d5f..cbe57861525 100644 --- a/graphql/schema/testdata/schemagen/output/geo-type.graphql +++ b/graphql/schema/testdata/schemagen/output/geo-type.graphql @@ -192,7 +192,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 diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql index 0e2076f258f..2f9ecec1c18 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql @@ -210,7 +210,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 diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql index bfa6ed28f54..803404b03f3 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql @@ -211,7 +211,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 diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql index 0e2076f258f..2f9ecec1c18 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql @@ -210,7 +210,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 diff --git a/graphql/schema/testdata/schemagen/output/hasInverse.graphql b/graphql/schema/testdata/schemagen/output/hasInverse.graphql index 63cd1725ab0..f0a27584b68 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse.graphql @@ -191,7 +191,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 diff --git a/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql b/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql index 1d8f97b9709..fc161d10573 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql @@ -191,7 +191,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 diff --git a/graphql/schema/testdata/schemagen/output/hasfilter.graphql b/graphql/schema/testdata/schemagen/output/hasfilter.graphql index 3e2ff93b76d..ef5a43b4bfa 100644 --- a/graphql/schema/testdata/schemagen/output/hasfilter.graphql +++ b/graphql/schema/testdata/schemagen/output/hasfilter.graphql @@ -194,7 +194,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 diff --git a/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql b/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql index 525e0b983a8..53cad5f28f2 100755 --- a/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql @@ -193,7 +193,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 diff --git a/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql b/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql index 96bda78a312..08d3b15bc4a 100644 --- a/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql @@ -200,7 +200,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 diff --git a/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql b/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql index 897febef368..abedf6d8912 100755 --- a/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql @@ -195,7 +195,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 diff --git a/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql b/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql index d3832cd6b47..2c156bfe71d 100755 --- a/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql @@ -195,7 +195,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 diff --git a/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql b/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql index 5e3d61fc209..ede4988485c 100755 --- a/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql +++ b/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql @@ -217,7 +217,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 diff --git a/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql b/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql index 8f20e7cac2f..93c5633dba1 100755 --- a/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql +++ b/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql @@ -217,7 +217,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 diff --git a/graphql/schema/testdata/schemagen/output/lambda-directive.graphql b/graphql/schema/testdata/schemagen/output/lambda-directive.graphql index ca41b148552..9adec1d4a10 100644 --- a/graphql/schema/testdata/schemagen/output/lambda-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/lambda-directive.graphql @@ -188,7 +188,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 diff --git a/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql index 5184a682a1c..cb95340aace 100755 --- a/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql +++ b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql @@ -185,7 +185,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 diff --git a/graphql/schema/testdata/schemagen/output/no-id-field.graphql b/graphql/schema/testdata/schemagen/output/no-id-field.graphql index f69d4a93944..0e380bb39e5 100755 --- a/graphql/schema/testdata/schemagen/output/no-id-field.graphql +++ b/graphql/schema/testdata/schemagen/output/no-id-field.graphql @@ -197,7 +197,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 diff --git a/graphql/schema/testdata/schemagen/output/password-type.graphql b/graphql/schema/testdata/schemagen/output/password-type.graphql index 25263e9e9a7..652dbcd08a8 100755 --- a/graphql/schema/testdata/schemagen/output/password-type.graphql +++ b/graphql/schema/testdata/schemagen/output/password-type.graphql @@ -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 diff --git a/graphql/schema/testdata/schemagen/output/searchables-references.graphql b/graphql/schema/testdata/schemagen/output/searchables-references.graphql index ab3bb33d795..846328ac236 100755 --- a/graphql/schema/testdata/schemagen/output/searchables-references.graphql +++ b/graphql/schema/testdata/schemagen/output/searchables-references.graphql @@ -195,7 +195,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 diff --git a/graphql/schema/testdata/schemagen/output/searchables.graphql b/graphql/schema/testdata/schemagen/output/searchables.graphql index b81abd58667..431b86794b3 100755 --- a/graphql/schema/testdata/schemagen/output/searchables.graphql +++ b/graphql/schema/testdata/schemagen/output/searchables.graphql @@ -214,7 +214,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 diff --git a/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql b/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql index d4f622fa3de..b14965d644d 100755 --- a/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql +++ b/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql @@ -194,7 +194,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 diff --git a/graphql/schema/testdata/schemagen/output/single-type.graphql b/graphql/schema/testdata/schemagen/output/single-type.graphql index d3f8e4cbc56..293d54ff146 100755 --- a/graphql/schema/testdata/schemagen/output/single-type.graphql +++ b/graphql/schema/testdata/schemagen/output/single-type.graphql @@ -189,7 +189,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 diff --git a/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql b/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql index ccc1968d7ed..022e9b48f50 100755 --- a/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql @@ -201,7 +201,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 diff --git a/graphql/schema/testdata/schemagen/output/type-reference.graphql b/graphql/schema/testdata/schemagen/output/type-reference.graphql index 892999b6334..ebfb13500a8 100755 --- a/graphql/schema/testdata/schemagen/output/type-reference.graphql +++ b/graphql/schema/testdata/schemagen/output/type-reference.graphql @@ -193,7 +193,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 diff --git a/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql b/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql index e6d10630105..4658c0e65aa 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql @@ -194,7 +194,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 diff --git a/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql index 2c90f7a8faa..2759a2ebe40 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql @@ -193,7 +193,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 diff --git a/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql b/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql index c0c1825a8c3..4d4d034c4e5 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql @@ -193,7 +193,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 diff --git a/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql b/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql index f4ec157b4cb..60880a3f28c 100644 --- a/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql +++ b/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql @@ -188,7 +188,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 diff --git a/graphql/schema/testdata/schemagen/output/union.graphql b/graphql/schema/testdata/schemagen/output/union.graphql index cc9bbd1c641..b4c260fb61c 100644 --- a/graphql/schema/testdata/schemagen/output/union.graphql +++ b/graphql/schema/testdata/schemagen/output/union.graphql @@ -231,7 +231,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 diff --git a/graphql/schema/wrappers.go b/graphql/schema/wrappers.go index 064376bf1c8..9b8dfdfa56c 100644 --- a/graphql/schema/wrappers.go +++ b/graphql/schema/wrappers.go @@ -178,10 +178,12 @@ type Type interface { Nullable() bool // true if this is a union type IsUnion() bool + IsInterface() bool // returns a list of member types for this union UnionMembers([]interface{}) []Type ListType() Type Interfaces() []string + ImplementingTypes() []Type EnsureNonNulls(map[string]interface{}, string) error FieldOriginatedFrom(fieldName string) string AuthRules() *TypeAuth @@ -1832,6 +1834,10 @@ func (t *astType) Nullable() bool { return !t.typ.NonNull } +func (t *astType) IsInterface() bool { + return t.inSchema.schema.Types[t.typ.Name()].Kind == ast.Interface +} + func (t *astType) IsUnion() bool { return t.inSchema.schema.Types[t.typ.Name()].Kind == ast.Union } @@ -2001,6 +2007,22 @@ func (t *astType) Interfaces() []string { return names } +func (t *astType) ImplementingTypes() []Type { + objects := t.inSchema.schema.PossibleTypes[t.typ.Name()] + if len(objects) == 0 { + return nil + } + types := make([]Type, 0, len(objects)) + for _, typ := range objects { + types = append(types, &astType{ + typ: &ast.Type{NamedType: typ.Name}, + inSchema: t.inSchema, + dgraphPredicate: t.dgraphPredicate, + }) + } + return types +} + // CheckNonNulls checks that any non nullables in t are present in obj. // Fields of type ID are not checked, nor is any exclusion. //