Skip to content

Commit e87694d

Browse files
authored
feat(acl): allow access to all the predicates using wildcard (#7991) (#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)
1 parent ff9ef37 commit e87694d

3 files changed

Lines changed: 198 additions & 12 deletions

File tree

edgraph/access_ee.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@ const queryAcls = `
404404
`
405405

406406
var aclPrefixes = [][]byte{
407-
x.PredicatePrefix(x.GalaxyAttr("dgraph.acl.permission")),
408-
x.PredicatePrefix(x.GalaxyAttr("dgraph.acl.predicate")),
407+
x.PredicatePrefix(x.GalaxyAttr("dgraph.rule.permission")),
408+
x.PredicatePrefix(x.GalaxyAttr("dgraph.rule.predicate")),
409409
x.PredicatePrefix(x.GalaxyAttr("dgraph.acl.rule")),
410410
x.PredicatePrefix(x.GalaxyAttr("dgraph.user.group")),
411411
x.PredicatePrefix(x.GalaxyAttr("dgraph.type.Group")),
@@ -642,8 +642,14 @@ func authorizePreds(ctx context.Context, userData *userData, preds []string,
642642
blockedPreds[pred] = struct{}{}
643643
}
644644
}
645+
646+
if worker.HasAccessToAllPreds(ns, groupIds, aclOp) {
647+
// Setting allowed to nil allows access to all predicates. Note that the access to ACL
648+
// predicates will still be blocked.
649+
return &authPredResult{allowed: nil, blocked: blockedPreds}, nil
650+
}
645651
// User can have multiple permission for same predicate, add predicate
646-
allowedPreds := make([]string, len(worker.AclCachePtr.GetUserPredPerms(userId)))
652+
allowedPreds := make([]string, 0, len(worker.AclCachePtr.GetUserPredPerms(userId)))
647653
// only if the acl.Op is covered in the set of permissions for the user
648654
for predicate, perm := range worker.AclCachePtr.GetUserPredPerms(userId) {
649655
if (perm & aclOp.Code) > 0 {

ee/acl/acl_test.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,166 @@ func TestValQueryWithACLPermissions(t *testing.T) {
16831683
}
16841684

16851685
}
1686+
1687+
func TestAllPredsPermission(t *testing.T) {
1688+
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
1689+
defer cancel()
1690+
dg, err := testutil.DgraphClientWithGroot(testutil.SockAddr)
1691+
require.NoError(t, err)
1692+
1693+
testutil.DropAll(t, dg)
1694+
1695+
op := api.Operation{Schema: `
1696+
name : string @index(exact) .
1697+
nickname : string @index(exact) .
1698+
age : int .
1699+
type TypeName {
1700+
name: string
1701+
nickname: string
1702+
age: int
1703+
}
1704+
`}
1705+
require.NoError(t, dg.Alter(ctx, &op))
1706+
1707+
resetUser(t)
1708+
1709+
token, err := testutil.HttpLogin(&testutil.LoginParams{
1710+
Endpoint: adminEndpoint,
1711+
UserID: "groot",
1712+
Passwd: "password",
1713+
Namespace: x.GalaxyNamespace,
1714+
})
1715+
require.NoError(t, err, "login failed")
1716+
1717+
createGroup(t, token, devGroup)
1718+
addToGroup(t, token, userid, devGroup)
1719+
1720+
txn := dg.NewTxn()
1721+
mutation := &api.Mutation{
1722+
SetNquads: []byte(`
1723+
_:a <name> "RandomGuy" .
1724+
_:a <age> "23" .
1725+
_:a <nickname> "RG" .
1726+
_:a <dgraph.type> "TypeName" .
1727+
_:b <name> "RandomGuy2" .
1728+
_:b <age> "25" .
1729+
_:b <nickname> "RG2" .
1730+
_:b <dgraph.type> "TypeName" .
1731+
`),
1732+
CommitNow: true,
1733+
}
1734+
_, err = txn.Mutate(ctx, mutation)
1735+
require.NoError(t, err)
1736+
1737+
query := `{q1(func: has(name)){
1738+
v as name
1739+
a as age
1740+
}
1741+
q2(func: eq(val(v), "RandomGuy")) {
1742+
val(v)
1743+
val(a)
1744+
}}`
1745+
1746+
// Test that groot has access to all the predicates
1747+
resp, err := dg.NewReadOnlyTxn().Query(ctx, query)
1748+
require.NoError(t, err, "Error while querying data")
1749+
testutil.CompareJSON(t, `{"q1":[{"name":"RandomGuy","age":23},{"name":"RandomGuy2","age":25}],"q2":[{"val(v)":"RandomGuy","val(a)":23}]}`,
1750+
string(resp.GetJson()))
1751+
1752+
// All test cases
1753+
tests := []struct {
1754+
input string
1755+
descriptionNoPerm string
1756+
outputNoPerm string
1757+
descriptionNamePerm string
1758+
outputNamePerm string
1759+
descriptionNameAgePerm string
1760+
outputNameAgePerm string
1761+
}{
1762+
{
1763+
`
1764+
{
1765+
q1(func: has(name), orderasc: name) {
1766+
n as name
1767+
a as age
1768+
}
1769+
q2(func: eq(val(n), "RandomGuy")) {
1770+
val(n)
1771+
val(a)
1772+
}
1773+
}
1774+
`,
1775+
"alice doesn't have access to name or age",
1776+
`{}`,
1777+
1778+
`alice has access to name`,
1779+
`{"q1":[{"name":"RandomGuy"},{"name":"RandomGuy2"}],"q2":[{"val(n)":"RandomGuy"}]}`,
1780+
1781+
"alice has access to name and age",
1782+
`{"q1":[{"name":"RandomGuy","age":23},{"name":"RandomGuy2","age":25}],"q2":[{"val(n)":"RandomGuy","val(a)":23}]}`,
1783+
},
1784+
}
1785+
1786+
userClient, err := testutil.DgraphClient(testutil.SockAddr)
1787+
require.NoError(t, err)
1788+
time.Sleep(defaultTimeToSleep)
1789+
1790+
err = userClient.LoginIntoNamespace(ctx, userid, userpassword, x.GalaxyNamespace)
1791+
require.NoError(t, err)
1792+
1793+
// Query via user when user has no permissions
1794+
for _, tc := range tests {
1795+
desc := tc.descriptionNoPerm
1796+
t.Run(desc, func(t *testing.T) {
1797+
resp, err := userClient.NewTxn().Query(ctx, tc.input)
1798+
require.NoError(t, err)
1799+
testutil.CompareJSON(t, tc.outputNoPerm, string(resp.Json))
1800+
})
1801+
}
1802+
1803+
// Login to groot to modify accesses (1)
1804+
token, err = testutil.HttpLogin(&testutil.LoginParams{
1805+
Endpoint: adminEndpoint,
1806+
UserID: "groot",
1807+
Passwd: "password",
1808+
Namespace: x.GalaxyNamespace,
1809+
})
1810+
require.NoError(t, err, "login failed")
1811+
1812+
// Give read access of all predicates to dev
1813+
addRulesToGroup(t, token, devGroup, []rule{{"dgraph.all", Read.Code}})
1814+
time.Sleep(defaultTimeToSleep)
1815+
1816+
for _, tc := range tests {
1817+
desc := tc.descriptionNameAgePerm
1818+
t.Run(desc, func(t *testing.T) {
1819+
resp, err := userClient.NewTxn().Query(ctx, tc.input)
1820+
require.NoError(t, err)
1821+
testutil.CompareJSON(t, tc.outputNameAgePerm, string(resp.Json))
1822+
})
1823+
}
1824+
1825+
// Mutation shall fail.
1826+
mutation = &api.Mutation{
1827+
SetNquads: []byte(`
1828+
_:a <name> "RandomGuy" .
1829+
_:a <age> "23" .
1830+
_:a <dgraph.type> "TypeName" .
1831+
`),
1832+
CommitNow: true,
1833+
}
1834+
txn = userClient.NewTxn()
1835+
_, err = txn.Mutate(ctx, mutation)
1836+
require.Error(t, err)
1837+
require.Contains(t, err.Error(), "unauthorized to mutate")
1838+
1839+
// Give write access of all predicates to dev. Now mutation should succeed.
1840+
addRulesToGroup(t, token, devGroup, []rule{{"dgraph.all", Write.Code | Read.Code}})
1841+
time.Sleep(defaultTimeToSleep)
1842+
txn = userClient.NewTxn()
1843+
_, err = txn.Mutate(ctx, mutation)
1844+
require.NoError(t, err)
1845+
}
16861846
func TestNewACLPredicates(t *testing.T) {
16871847
ctx, _ := context.WithTimeout(context.Background(), 100*time.Second)
16881848

worker/acl_cache.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,17 @@ func (cache *AclCache) Update(ns uint64, groups []acl.Group) {
139139

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

146-
AclCachePtr.RLock()
147-
predPerms := AclCachePtr.predPerms
148-
AclCachePtr.RUnlock()
149-
150-
if groupPerms, found := predPerms[predicate]; found {
151-
if hasRequiredAccess(groupPerms, groups, operation) {
152-
return nil
153-
}
147+
// Check if group has access to all the predicates (using "dgraph.all" wildcard).
148+
if HasAccessToAllPreds(ns, groups, operation) {
149+
return nil
150+
}
151+
if hasAccessToPred(predicate, groups, operation) {
152+
return nil
154153
}
155154

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

161160
}
162161

162+
// accessAllPredicate is a wildcard to allow access to all non-ACL predicates to non-guardian group.
163+
const accessAllPredicate = "dgraph.all"
164+
165+
func HasAccessToAllPreds(ns uint64, groups []string, operation *acl.Operation) bool {
166+
pred := x.NamespaceAttr(ns, accessAllPredicate)
167+
return hasAccessToPred(pred, groups, operation)
168+
}
169+
170+
func hasAccessToPred(pred string, groups []string, operation *acl.Operation) bool {
171+
AclCachePtr.RLock()
172+
defer AclCachePtr.RUnlock()
173+
predPerms := AclCachePtr.predPerms
174+
175+
if groupPerms, found := predPerms[pred]; found {
176+
if hasRequiredAccess(groupPerms, groups, operation) {
177+
return true
178+
}
179+
}
180+
return false
181+
}
182+
163183
// hasRequiredAccess checks if any group in the passed in groups is allowed to perform the operation
164184
// according to the acl rules stored in groupPerms
165185
func hasRequiredAccess(groupPerms map[string]int32, groups []string,

0 commit comments

Comments
 (0)