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(acl): allow access to all the predicates using wildcard (#7991) #7993

Merged
merged 2 commits into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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