diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index ad224a92dad..6fe746ed7f0 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -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")), @@ -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 { diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index 6a3a928c04a..76436ce39be 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -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 "RandomGuy" . + _:a "23" . + _:a "RG" . + _:a "TypeName" . + _:b "RandomGuy2" . + _:b "25" . + _:b "RG2" . + _:b "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 "RandomGuy" . + _:a "23" . + _:a "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) diff --git a/worker/acl_cache.go b/worker/acl_cache.go index 4a02ea69bbc..fc181218dbc 100644 --- a/worker/acl_cache.go +++ b/worker/acl_cache.go @@ -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 @@ -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,