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): Support auth with custom DQL #7775

Merged
merged 37 commits into from
May 26, 2021
Merged

Conversation

minhaj-shakeel
Copy link
Contributor

@minhaj-shakeel minhaj-shakeel commented May 3, 2021

Fixes GRAPHQL-1171.
This PR adds support for @auth on custom DQL queries.
This PR also adds the parsing step to the DQL query.
The Algorithm tries to figure out the queried type at the root of the query either from the root func or filters.
Suppose if the root func is func: type(Person) or func: has(Person.name) or func: eq(Person.age, 10) then
it infers that the queried type is Person.
However, there are certain DQL queries on which @auth rules are not added currently since the queried type is not clear.
for eg:

me(func: uid(0x1, 0x2)) {
 ...
}

or the query:

me(func: has(name@en) {
 ...
}

This change is Reviewable

@minhaj-shakeel minhaj-shakeel requested a review from pawanrawal as a code owner May 3, 2021 14:54
@github-actions github-actions bot added the area/graphql Issues related to GraphQL support on Dgraph. label May 3, 2021
Copy link
Contributor

@abhimanyusinghgaur abhimanyusinghgaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 9 files at r1.
Reviewable status: 1 of 9 files reviewed, 8 unresolved discussions (waiting on @minhaj-shakeel and @pawanrawal)


graphql/dgraph/graphquery.go, line 217 at r1 (raw file):

writeFilterArguments(b, q.Func.Args, q.Func.Attr, q.Func.Name)

maybe just pass q.Func and use that in the function


graphql/dgraph/graphquery.go, line 223 at r1 (raw file):

}

func maybeQuoteArg(fn string, arg interface{}) string {

I think we have a similar function in resolve pkg. Can't we reuse that here?
If it's a package dependency issue, we can move that function to a higher level package in the dependency tree (schema pkg), or in the packagex.


graphql/e2e/auth/schema.graphql, line 991 at r1 (raw file):

$search: string

stale


graphql/e2e/auth/schema.graphql, line 997 at r1 (raw file):

orderdesc: val(age)

can this directly work here? as the map age is a map from owner uid to age value, while the root func here is iterating over the Issue uids. So, I think this might not produce the intended results after all.
This var query might be needed:

		iss as var(func: type(Issue)) @filter(has(Issue.owner)) {
			Issue.owner {
				age as User.age
			}
			ownerAge as max(val(age))
		}

Then use val(ownerAge) in the orderdesc.


graphql/resolve/custom_auth_query_test.yaml, line 26 at r1 (raw file):

    }

- name : "custom DQL query with var block RBAC rules true"

can we also add another test where there are multiple var blocks in the DQL, and all of them are used in the user's query?


graphql/resolve/custom_auth_query_test.yaml, line 75 at r1 (raw file):

iss as var(func: uid(0x1)) @filter((uid(0x2) AND has(Issue.owner)))

👍
why is the AND has(Issue.owner) here?


graphql/resolve/query_rewriter.go, line 641 at r1 (raw file):

Quoted 12 lines of code…
	args := query.Arguments()
	vars := make(map[string]string)
	for k, v := range args {
		// dgoapi.Request{}.Vars accepts only string values for variables,
		// so need to convert all variable values to string
		vStr, err := convertScalarToString(v)
		if err != nil {
			return nil, err
		}
		// the keys in dgoapi.Request{}.Vars are assumed to be prefixed with $
		vars["$"+k] = vStr
	}

we should put this in a function, this piece of code is being used at two places


graphql/resolve/query_rewriter.go, line 680 at r1 (raw file):

	typeName = extractTypeFromFilter(dgQuery.Filter)
	return typeName

return extractTypeFromFilter(dgQuery.Filter)

also, should we try to extract type from order?

Copy link
Contributor Author

@minhaj-shakeel minhaj-shakeel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 8 unresolved discussions (waiting on @abhimanyusinghgaur and @pawanrawal)


graphql/dgraph/graphquery.go, line 217 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
writeFilterArguments(b, q.Func.Args, q.Func.Attr, q.Func.Name)

maybe just pass q.Func and use that in the function

Done.


graphql/dgraph/graphquery.go, line 223 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

I think we have a similar function in resolve pkg. Can't we reuse that here?
If it's a package dependency issue, we can move that function to a higher level package in the dependency tree (schema pkg), or in the packagex.

Done.


graphql/e2e/auth/schema.graphql, line 991 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
$search: string

stale

Done.


graphql/e2e/auth/schema.graphql, line 997 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
orderdesc: val(age)

can this directly work here? as the map age is a map from owner uid to age value, while the root func here is iterating over the Issue uids. So, I think this might not produce the intended results after all.
This var query might be needed:

		iss as var(func: type(Issue)) @filter(has(Issue.owner)) {
			Issue.owner {
				age as User.age
			}
			ownerAge as max(val(age))
		}

Then use val(ownerAge) in the orderdesc.

Done.


graphql/resolve/custom_auth_query_test.yaml, line 26 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…

can we also add another test where there are multiple var blocks in the DQL, and all of them are used in the user's query?

Done.


graphql/resolve/custom_auth_query_test.yaml, line 75 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
iss as var(func: uid(0x1)) @filter((uid(0x2) AND has(Issue.owner)))

👍
why is the AND has(Issue.owner) here?

because it is attached in the query.


graphql/resolve/query_rewriter.go, line 641 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
	args := query.Arguments()
	vars := make(map[string]string)
	for k, v := range args {
		// dgoapi.Request{}.Vars accepts only string values for variables,
		// so need to convert all variable values to string
		vStr, err := convertScalarToString(v)
		if err != nil {
			return nil, err
		}
		// the keys in dgoapi.Request{}.Vars are assumed to be prefixed with $
		vars["$"+k] = vStr
	}

we should put this in a function, this piece of code is being used at two places

Done.


graphql/resolve/query_rewriter.go, line 680 at r1 (raw file):

Previously, abhimanyusinghgaur (Abhimanyu Singh Gaur) wrote…
	typeName = extractTypeFromFilter(dgQuery.Filter)
	return typeName

return extractTypeFromFilter(dgQuery.Filter)

also, should we try to extract type from order?

Done. Added an example of order also.

Copy link
Contributor

@vmrajas vmrajas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@@ -112,7 +133,40 @@ func writeQuery(b *strings.Builder, query *gql.GraphQuery, prefix string) {
}
}

func writeUIDFunc(b *strings.Builder, uids []uint64, args []gql.Arg) {
func writeNeedVar(b *strings.Builder, query *gql.GraphQuery) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment

}

func writeFilterArguments(b *strings.Builder, args []gql.Arg) {
for i, arg := range args {
func writeFilterArguments(b *strings.Builder, q *gql.Function) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment. check if the condition needs to be updated.


type Query {

queryIssueSortedByOwnerAge: [Issue] @custom(dql: """
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix indentation if possible.

result: `{"queryProjectsOrderByName":[{"name": "Project1"},{"name": "Project2"}]}`,
},
{
name: "RBAC OR filter query; RBAC fail",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: RBAC false or user1 projects.

{
name: "RBAC OR filter query; RBAC Pass",
query: `
query{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indent

}
}

- name : "Auth rules with deep filter missing JWT"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add an explanation field for easier understanding.

Variables: vars,
}
parsedResult, err := gql.Parse(dqlReq)
for _, qry := range parsedResult.Query {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment

@@ -806,7 +995,7 @@ func (authRw *authRewriter) addAuthQueries(

// 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 && implementingTypesHasAuthRules == false {
if typ.IsInterface() && !implementingTypesHasAuthRules {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return false
}

func getFieldName(attr string) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be add a TODO here or in query rewriter that dgraph directive with custom DQL auth won't work and is to be added.

Copy link
Contributor Author

@minhaj-shakeel minhaj-shakeel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 15 unresolved discussions (waiting on @abhimanyusinghgaur, @minhaj-shakeel, @pawanrawal, and @vmrajas)


graphql/e2e/auth/schema.graphql, line 990 at r2 (raw file):

Previously, vmrajas wrote…

Fix indentation if possible.

Done.


graphql/resolve/custom_auth_query_test.yaml, line 154 at r2 (raw file):

Previously, vmrajas wrote…

add an explanation field for easier understanding.

Done.


graphql/resolve/query_rewriter.go, line 648 at r2 (raw file):

Previously, vmrajas wrote…

Add a comment

Done.


graphql/resolve/query_rewriter.go, line 998 at r2 (raw file):

Previously, vmrajas wrote…

👍

Done.


graphql/resolve/query_rewriter.go, line 1728 at r2 (raw file):

Previously, vmrajas wrote…

May be add a TODO here or in query rewriter that dgraph directive with custom DQL auth won't work and is to be added.

Done.

Copy link
Contributor

@abhimanyusinghgaur abhimanyusinghgaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 7 unresolved discussions (waiting on @abhimanyusinghgaur, @minhaj-shakeel, @pawanrawal, and @vmrajas)

@minhaj-shakeel
Copy link
Contributor Author

Fixes GRAPHQL-1171.
This PR adds support for @auth on custom DQL queries.
This PR also adds the parsing step to the DQL query.
The Algorithm tries to figure out the queried type at the root of the query either from the root func or filters.
Suppose if the root func is func: type(Person) or func: has(Person.name) or func: eq(Person.age, 10) then
it infers that the queried type is Person.
However, there are certain DQL queries on which @auth rules are not added currently since the queried type is not clear.
for eg:

me(func: uid(0x1, 0x2)) {
 ...
}

or the query:

me(func: has(name@en) {
 ...
}

@benwoodward
Copy link

Would be great if there was some example syntax. If I'm creating a @custom query, then I put it inside type Query {}. So presumably I add @auth directive rules to type Query {} as I would do if it were not a @custom query? Assuming this is the case, then if I have multiple @custom queries, can I create multiple type Query {} blocks, one for each @custom query, each block with its own @auth rules? How would you solve this problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/graphql Issues related to GraphQL support on Dgraph.
Development

Successfully merging this pull request may close these issues.

4 participants