Skip to content

Commit

Permalink
feat(acl): allow access to all the predicates using wildcard (#7991) (#…
Browse files Browse the repository at this point in the history
…7993)

* feat(acl): allow access to all the predicates using wildcard (#7991)

There are usecases that need read/write/modify permissions over all the predicates of the namespace. It is quite tedious to manage the permissions every time a new predicate is created.
This PR adds a feature to allow a group, access to all the predicates in the namespace using wildcard dgraph.all.

This example provides to dev group, read+write access to all the predicates

mutation {
  updateGroup(
    input: {
      filter: { name: { eq: "dev" } }
      set: { rules: [{ predicate: "dgraph.all", permission: 6 }] }
    }
  ) {
    group {
      name
      rules {
        permission
        predicate
      }
    }
  }
}

NOTE: The permission to a predicate for a group (say dev) is a union of permissions from dgraph.all and the permissions to specific predicate (say name). So suppose dgraph.all is given READ permission, while predicate name is given WRITE permission. Then the group will have both READ and WRITE permission.
(cherry picked from commit 3504044)

* fix(acl): subscribe for the correct predicates (#7992)

We were subscribing to the wrong predicates. Hence the ACL cache was not getting updated.

(cherry picked from commit 1b75c01)
  • Loading branch information
NamanJain8 authored Aug 19, 2021
1 parent ff9ef37 commit e87694d
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 12 deletions.
12 changes: 9 additions & 3 deletions edgraph/access_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ const queryAcls = `
`

var aclPrefixes = [][]byte{
x.PredicatePrefix(x.GalaxyAttr("dgraph.acl.permission")),
x.PredicatePrefix(x.GalaxyAttr("dgraph.acl.predicate")),
x.PredicatePrefix(x.GalaxyAttr("dgraph.rule.permission")),
x.PredicatePrefix(x.GalaxyAttr("dgraph.rule.predicate")),
x.PredicatePrefix(x.GalaxyAttr("dgraph.acl.rule")),
x.PredicatePrefix(x.GalaxyAttr("dgraph.user.group")),
x.PredicatePrefix(x.GalaxyAttr("dgraph.type.Group")),
Expand Down Expand Up @@ -642,8 +642,14 @@ func authorizePreds(ctx context.Context, userData *userData, preds []string,
blockedPreds[pred] = struct{}{}
}
}

if worker.HasAccessToAllPreds(ns, groupIds, aclOp) {
// Setting allowed to nil allows access to all predicates. Note that the access to ACL
// predicates will still be blocked.
return &authPredResult{allowed: nil, blocked: blockedPreds}, nil
}
// User can have multiple permission for same predicate, add predicate
allowedPreds := make([]string, len(worker.AclCachePtr.GetUserPredPerms(userId)))
allowedPreds := make([]string, 0, len(worker.AclCachePtr.GetUserPredPerms(userId)))
// only if the acl.Op is covered in the set of permissions for the user
for predicate, perm := range worker.AclCachePtr.GetUserPredPerms(userId) {
if (perm & aclOp.Code) > 0 {
Expand Down
160 changes: 160 additions & 0 deletions ee/acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,166 @@ func TestValQueryWithACLPermissions(t *testing.T) {
}

}

func TestAllPredsPermission(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
defer cancel()
dg, err := testutil.DgraphClientWithGroot(testutil.SockAddr)
require.NoError(t, err)

testutil.DropAll(t, dg)

op := api.Operation{Schema: `
name : string @index(exact) .
nickname : string @index(exact) .
age : int .
type TypeName {
name: string
nickname: string
age: int
}
`}
require.NoError(t, dg.Alter(ctx, &op))

resetUser(t)

token, err := testutil.HttpLogin(&testutil.LoginParams{
Endpoint: adminEndpoint,
UserID: "groot",
Passwd: "password",
Namespace: x.GalaxyNamespace,
})
require.NoError(t, err, "login failed")

createGroup(t, token, devGroup)
addToGroup(t, token, userid, devGroup)

txn := dg.NewTxn()
mutation := &api.Mutation{
SetNquads: []byte(`
_:a <name> "RandomGuy" .
_:a <age> "23" .
_:a <nickname> "RG" .
_:a <dgraph.type> "TypeName" .
_:b <name> "RandomGuy2" .
_:b <age> "25" .
_:b <nickname> "RG2" .
_:b <dgraph.type> "TypeName" .
`),
CommitNow: true,
}
_, err = txn.Mutate(ctx, mutation)
require.NoError(t, err)

query := `{q1(func: has(name)){
v as name
a as age
}
q2(func: eq(val(v), "RandomGuy")) {
val(v)
val(a)
}}`

// Test that groot has access to all the predicates
resp, err := dg.NewReadOnlyTxn().Query(ctx, query)
require.NoError(t, err, "Error while querying data")
testutil.CompareJSON(t, `{"q1":[{"name":"RandomGuy","age":23},{"name":"RandomGuy2","age":25}],"q2":[{"val(v)":"RandomGuy","val(a)":23}]}`,
string(resp.GetJson()))

// All test cases
tests := []struct {
input string
descriptionNoPerm string
outputNoPerm string
descriptionNamePerm string
outputNamePerm string
descriptionNameAgePerm string
outputNameAgePerm string
}{
{
`
{
q1(func: has(name), orderasc: name) {
n as name
a as age
}
q2(func: eq(val(n), "RandomGuy")) {
val(n)
val(a)
}
}
`,
"alice doesn't have access to name or age",
`{}`,

`alice has access to name`,
`{"q1":[{"name":"RandomGuy"},{"name":"RandomGuy2"}],"q2":[{"val(n)":"RandomGuy"}]}`,

"alice has access to name and age",
`{"q1":[{"name":"RandomGuy","age":23},{"name":"RandomGuy2","age":25}],"q2":[{"val(n)":"RandomGuy","val(a)":23}]}`,
},
}

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

err = userClient.LoginIntoNamespace(ctx, userid, userpassword, x.GalaxyNamespace)
require.NoError(t, err)

// Query via user when user has no permissions
for _, tc := range tests {
desc := tc.descriptionNoPerm
t.Run(desc, func(t *testing.T) {
resp, err := userClient.NewTxn().Query(ctx, tc.input)
require.NoError(t, err)
testutil.CompareJSON(t, tc.outputNoPerm, string(resp.Json))
})
}

// Login to groot to modify accesses (1)
token, err = testutil.HttpLogin(&testutil.LoginParams{
Endpoint: adminEndpoint,
UserID: "groot",
Passwd: "password",
Namespace: x.GalaxyNamespace,
})
require.NoError(t, err, "login failed")

// Give read access of all predicates to dev
addRulesToGroup(t, token, devGroup, []rule{{"dgraph.all", Read.Code}})
time.Sleep(defaultTimeToSleep)

for _, tc := range tests {
desc := tc.descriptionNameAgePerm
t.Run(desc, func(t *testing.T) {
resp, err := userClient.NewTxn().Query(ctx, tc.input)
require.NoError(t, err)
testutil.CompareJSON(t, tc.outputNameAgePerm, string(resp.Json))
})
}

// Mutation shall fail.
mutation = &api.Mutation{
SetNquads: []byte(`
_:a <name> "RandomGuy" .
_:a <age> "23" .
_:a <dgraph.type> "TypeName" .
`),
CommitNow: true,
}
txn = userClient.NewTxn()
_, err = txn.Mutate(ctx, mutation)
require.Error(t, err)
require.Contains(t, err.Error(), "unauthorized to mutate")

// Give write access of all predicates to dev. Now mutation should succeed.
addRulesToGroup(t, token, devGroup, []rule{{"dgraph.all", Write.Code | Read.Code}})
time.Sleep(defaultTimeToSleep)
txn = userClient.NewTxn()
_, err = txn.Mutate(ctx, mutation)
require.NoError(t, err)
}
func TestNewACLPredicates(t *testing.T) {
ctx, _ := context.WithTimeout(context.Background(), 100*time.Second)

Expand Down
38 changes: 29 additions & 9 deletions worker/acl_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,18 +139,17 @@ func (cache *AclCache) Update(ns uint64, groups []acl.Group) {

func (cache *AclCache) AuthorizePredicate(groups []string, predicate string,
operation *acl.Operation) error {
if x.IsAclPredicate(x.ParseAttr(predicate)) {
ns, attr := x.ParseNamespaceAttr(predicate)
if x.IsAclPredicate(attr) {
return errors.Errorf("only groot is allowed to access the ACL predicate: %s", predicate)
}

AclCachePtr.RLock()
predPerms := AclCachePtr.predPerms
AclCachePtr.RUnlock()

if groupPerms, found := predPerms[predicate]; found {
if hasRequiredAccess(groupPerms, groups, operation) {
return nil
}
// Check if group has access to all the predicates (using "dgraph.all" wildcard).
if HasAccessToAllPreds(ns, groups, operation) {
return nil
}
if hasAccessToPred(predicate, groups, operation) {
return nil
}

// no rule has been defined that can match the predicate
Expand All @@ -160,6 +159,27 @@ func (cache *AclCache) AuthorizePredicate(groups []string, predicate string,

}

// accessAllPredicate is a wildcard to allow access to all non-ACL predicates to non-guardian group.
const accessAllPredicate = "dgraph.all"

func HasAccessToAllPreds(ns uint64, groups []string, operation *acl.Operation) bool {
pred := x.NamespaceAttr(ns, accessAllPredicate)
return hasAccessToPred(pred, groups, operation)
}

func hasAccessToPred(pred string, groups []string, operation *acl.Operation) bool {
AclCachePtr.RLock()
defer AclCachePtr.RUnlock()
predPerms := AclCachePtr.predPerms

if groupPerms, found := predPerms[pred]; found {
if hasRequiredAccess(groupPerms, groups, operation) {
return true
}
}
return false
}

// hasRequiredAccess checks if any group in the passed in groups is allowed to perform the operation
// according to the acl rules stored in groupPerms
func hasRequiredAccess(groupPerms map[string]int32, groups []string,
Expand Down

0 comments on commit e87694d

Please sign in to comment.