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

ACL: Allow users to query data for their groups, username, and permissions #4774

Merged
merged 11 commits into from
Feb 14, 2020
91 changes: 91 additions & 0 deletions edgraph/access_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,12 @@ func authorizeQuery(ctx context.Context, parsedReq *gql.Result) error {
}

if len(blockedPreds) != 0 {
for _, gq := range parsedReq.Query {
addUserFilterToQuery(gq, userId, groupIds)
}
for _, pred := range x.AllACLPredicates() {
delete(blockedPreds, pred)
}
parsedReq.Query = removePredsFromQuery(parsedReq.Query, blockedPreds)
}

Expand Down Expand Up @@ -819,6 +825,91 @@ func authorizeGroot(ctx context.Context) error {
return doAuthorizeGroot()
}

// addUserFilterToQuery applies makes sure that a user can access only its own
// acl info by applying filter of userid and groupid to acl predicates
func addUserFilterToQuery(gq *gql.GraphQuery, userId string, groupIds []string) {
addNewFilter := func(newFilter, filter *gql.FilterTree) *gql.FilterTree {
if filter == nil {
return newFilter
}
parentFilter := &gql.FilterTree{
Op: "AND",
Child: []*gql.FilterTree{filter, newFilter},
}
return parentFilter
}

if gq.Func != nil && gq.Func.Name == "type" {
for _, arg := range gq.Func.Args {
// The case where value of some varialble v (say) is "Group" and a
// query comes like `eq(dgraph.type, val(v))`, will be ingored here.
if arg.Value == "User" {
newFilter := &gql.FilterTree{
Func: &gql.Function{
Attr: "dgraph.xid",
Name: "eq",
Args: []gql.Arg{
gql.Arg{Value: userId},
animesh2049 marked this conversation as resolved.
Show resolved Hide resolved
},
},
}
gq.Filter = addNewFilter(newFilter, gq.Filter)
} else if arg.Value == "Group" {
child := &gql.FilterTree{
Func: &gql.Function{
Attr: "dgraph.xid",
Name: "eq",
},
}
for _, gid := range groupIds {
child.Func.Args = append(child.Func.Args,
gql.Arg{Value: gid})
}
newFilter := &gql.FilterTree{
Op: "OR",
Child: []*gql.FilterTree{child},
}
gq.Filter = addNewFilter(newFilter, gq.Filter)
}
}
}

switch gq.Attr {
case "dgraph.user.group":
child := &gql.FilterTree{
Func: &gql.Function{
Attr: "dgraph.xid",
Name: "eq",
},
}
for _, gid := range groupIds {
child.Func.Args = append(child.Func.Args, gql.Arg{Value: gid})
}
newFilter := &gql.FilterTree{
Op: "OR",
Child: []*gql.FilterTree{child},
}
gq.Filter = addNewFilter(newFilter, gq.Filter)
case "~dgraph.user.group":
newFilter := &gql.FilterTree{
Func: &gql.Function{
Attr: "dgraph.xid",
Name: "eq",
Args: []gql.Arg{
gql.Arg{Value: userId},
},
},
}
gq.Filter = addNewFilter(newFilter, gq.Filter)
}

for _, ch := range gq.Children {
addUserFilterToQuery(ch, userId, groupIds)
}
}

// removePredsFromQuery removes all the predicates in blockedPreds
// from all the queries in gqs.
func removePredsFromQuery(gqs []*gql.GraphQuery,
blockedPreds map[string]struct{}) []*gql.GraphQuery {

Expand Down
57 changes: 57 additions & 0 deletions ee/acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -877,3 +877,60 @@ func TestNonExistentGroup(t *testing.T) {
require.Error(t, err, "Setting permission for non-existent group should return error")
require.Contains(t, string(resp), `Unable to modify: Group <dev> doesn't exist`)
}

func TestQueryUserInfo(t *testing.T) {
ctx, _ := context.WithTimeout(context.Background(), 100*time.Second)

dg, err := testutil.DgraphClientWithGroot(testutil.SockAddr)
require.NoError(t, err)
addDataAndRules(ctx, t, dg)

userClient, err := testutil.DgraphClient(testutil.SockAddr)
require.NoError(t, err)
time.Sleep(6 * time.Second)

err = userClient.Login(ctx, userid, userpassword)
require.NoError(t, err)

query := `
{
me(func: type(User)) {
dgraph.xid
dgraph.user.group {
dgraph.xid
dgraph.acl.rule {
dgraph.rule.predicate
dgraph.rule.permission
}
}
}
}
`

resp, err := userClient.NewReadOnlyTxn().Query(ctx, query)
require.NoError(t, err)

testutil.CompareJSON(t, `
{
"me": [
{
"dgraph.xid": "alice",
"dgraph.user.group": [
{
"dgraph.xid": "dev",
"dgraph.acl.rule": [
{
"dgraph.rule.predicate": "name",
"dgraph.rule.permission": 4
},
{
"dgraph.rule.predicate": "nickname",
"dgraph.rule.permission": 2
}
]
}
]
}
]
}`, string(resp.Json))
}
10 changes: 9 additions & 1 deletion x/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,13 +595,21 @@ func IsAclPredicate(pred string) bool {
// ReservedPredicates returns the complete list of reserved predicates that needs to
// be expanded when * is given as a predicate.
func ReservedPredicates() []string {
var preds []string
preds := make([]string, 0, len(reservedPredicateMap))
for pred := range reservedPredicateMap {
preds = append(preds, pred)
}
return preds
}

func AllACLPredicates() []string {
preds := make([]string, 0, len(aclPredicateMap))
for pred := range aclPredicateMap {
preds = append(preds, pred)
}
return preds
}

// IsInternalPredicate returns true if the predicate is in the internal predicate list.
func IsInternalPredicate(pred string) bool {
_, ok := internalPredicateMap[strings.ToLower(pred)]
Expand Down