Access Control: Support other attributes than id for resource permissions (#46727)

* Add option to set ResourceAttribute for a permissions service
* Use prefix in access control sql filter to parse scopes
* Use prefix in access control metadata to check access
This commit is contained in:
Karl Persson
2022-03-21 17:58:18 +01:00
committed by GitHub
parent 79f5c7d7a7
commit 7ab1ef8d6e
26 changed files with 363 additions and 288 deletions
@@ -210,7 +210,7 @@ func (s *AccessControlStore) setResourcePermission(
`
var current []accesscontrol.Permission
if err := sess.SQL(rawSQL, role.ID, accesscontrol.GetResourceScope(cmd.Resource, cmd.ResourceID)).Find(&current); err != nil {
if err := sess.SQL(rawSQL, role.ID, accesscontrol.Scope(cmd.Resource, cmd.ResourceAttribute, cmd.ResourceID)).Find(&current); err != nil {
return nil, err
}
@@ -235,14 +235,14 @@ func (s *AccessControlStore) setResourcePermission(
}
for action := range missing {
id, err := s.createResourcePermission(sess, role.ID, action, cmd.Resource, cmd.ResourceID)
id, err := s.createResourcePermission(sess, role.ID, action, cmd.Resource, cmd.ResourceID, cmd.ResourceAttribute)
if err != nil {
return nil, err
}
keep = append(keep, id)
}
permissions, err := s.getResourcePermissionsByIds(sess, cmd.Resource, cmd.ResourceID, keep)
permissions, err := s.getResourcePermissionsByIds(sess, cmd.Resource, cmd.ResourceID, cmd.ResourceAttribute, keep)
if err != nil {
return nil, err
}
@@ -267,8 +267,8 @@ func (s *AccessControlStore) GetResourcePermissions(ctx context.Context, orgID i
return result, err
}
func (s *AccessControlStore) createResourcePermission(sess *sqlstore.DBSession, roleID int64, action, resource string, resourceID string) (int64, error) {
permission := managedPermission(action, resource, resourceID)
func (s *AccessControlStore) createResourcePermission(sess *sqlstore.DBSession, roleID int64, action, resource, resourceID, resourceAttribute string) (int64, error) {
permission := managedPermission(action, resource, resourceID, resourceAttribute)
permission.RoleID = roleID
permission.Created = time.Now()
permission.Updated = time.Now()
@@ -340,14 +340,14 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, or
where := `WHERE (r.org_id = ? OR r.org_id = 0) AND (p.scope = '*' OR p.scope = ? OR p.scope = ? OR p.scope = ?`
scope := accesscontrol.GetResourceScope(query.Resource, query.ResourceID)
scope := accesscontrol.Scope(query.Resource, query.ResourceAttribute, query.ResourceID)
args := []interface{}{
scope,
orgID,
orgID,
accesscontrol.GetResourceAllScope(query.Resource),
accesscontrol.GetResourceAllIDScope(query.Resource),
accesscontrol.Scope(query.Resource, "*"),
accesscontrol.Scope(query.Resource, query.ResourceAttribute, "*"),
scope,
}
@@ -370,14 +370,14 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, or
initialLength := len(args)
userFilter, err := accesscontrol.Filter(query.User, "u.id", "users", accesscontrol.ActionOrgUsersRead)
userFilter, err := accesscontrol.Filter(query.User, "u.id", "users:id:", accesscontrol.ActionOrgUsersRead)
if err != nil {
return nil, err
}
user := userSelect + userFrom + where + " AND " + userFilter.Where
args = append(args, userFilter.Args...)
teamFilter, err := accesscontrol.Filter(query.User, "t.id", "teams", accesscontrol.ActionTeamsRead)
teamFilter, err := accesscontrol.Filter(query.User, "t.id", "teams:id:", accesscontrol.ActionTeamsRead)
if err != nil {
return nil, err
}
@@ -574,7 +574,7 @@ func (s *AccessControlStore) getOrCreateManagedRole(sess *sqlstore.DBSession, or
return &role, nil
}
func (s *AccessControlStore) getResourcePermissionsByIds(sess *sqlstore.DBSession, resource, resourceID string, ids []int64) ([]flatResourcePermission, error) {
func (s *AccessControlStore) getResourcePermissionsByIds(sess *sqlstore.DBSession, resource, resourceID, resourceAttribute string, ids []int64) ([]flatResourcePermission, error) {
var result []flatResourcePermission
if len(ids) == 0 {
return result, nil
@@ -602,7 +602,7 @@ func (s *AccessControlStore) getResourcePermissionsByIds(sess *sqlstore.DBSessio
`
args := make([]interface{}, 0, len(ids)+1)
args = append(args, accesscontrol.GetResourceScope(resource, resourceID))
args = append(args, accesscontrol.Scope(resource, resourceAttribute, resourceID))
for _, id := range ids {
args = append(args, id)
}
@@ -614,9 +614,9 @@ func (s *AccessControlStore) getResourcePermissionsByIds(sess *sqlstore.DBSessio
return result, nil
}
func managedPermission(action, resource string, resourceID string) accesscontrol.Permission {
func managedPermission(action, resource string, resourceID, resourceAttribute string) accesscontrol.Permission {
return accesscontrol.Permission{
Action: action,
Scope: accesscontrol.GetResourceScope(resource, resourceID),
Scope: accesscontrol.Scope(resource, resourceAttribute, resourceID),
}
}
@@ -56,9 +56,11 @@ func getDSPermissions(b *testing.B, store *AccessControlStore, dataSources []int
dsId := dataSources[0]
permissions, err := store.GetResourcePermissions(context.Background(), accesscontrol.GlobalOrgID, types.GetResourcePermissionsQuery{
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsId)),
User: &models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: {"org.users:read": {"users:*"}, "teams:read": {"teams:*"}}}},
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsId)),
ResourceAttribute: "id",
})
require.NoError(b, err)
assert.GreaterOrEqual(b, len(permissions), 2)
@@ -96,9 +98,10 @@ func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *Acce
accesscontrol.GlobalOrgID,
accesscontrol.User{ID: userIds[i]},
types.SetResourcePermissionCommand{
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsID)),
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsID)),
ResourceAttribute: "id",
},
nil,
)
@@ -113,9 +116,10 @@ func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *Acce
accesscontrol.GlobalOrgID,
teamIds[i],
types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: strconv.Itoa(int(dsID)),
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: strconv.Itoa(int(dsID)),
ResourceAttribute: "id",
},
nil,
)
@@ -16,31 +16,34 @@ import (
)
type setUserResourcePermissionTest struct {
desc string
orgID int64
userID int64
actions []string
resource string
resourceID string
seeds []types.SetResourcePermissionCommand
desc string
orgID int64
userID int64
actions []string
resource string
resourceID string
resourceAttribute string
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
tests := []setUserResourcePermissionTest{
{
desc: "should set resource permission for user",
userID: 1,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
desc: "should set resource permission for user",
userID: 1,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should remove resource permission for user",
orgID: 1,
userID: 1,
actions: []string{},
resource: "datasources",
resourceID: "1",
desc: "should remove resource permission for user",
orgID: 1,
userID: 1,
actions: []string{},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
@@ -50,12 +53,13 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
},
},
{
desc: "should add new resource permission for user",
orgID: 1,
userID: 1,
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
desc: "should add new resource permission for user",
orgID: 1,
userID: 1,
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:write"},
@@ -76,9 +80,10 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
}
added, err := store.SetUserResourcePermission(context.Background(), test.userID, accesscontrol.User{ID: test.userID}, types.SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
}, nil)
require.NoError(t, err)
@@ -86,59 +91,65 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
assert.Equal(t, accesscontrol.ResourcePermission{}, *added)
} else {
assert.Len(t, added.Actions, len(test.actions))
assert.Equal(t, accesscontrol.GetResourceScope(test.resource, test.resourceID), added.Scope)
assert.Equal(t, accesscontrol.Scope(test.resource, test.resourceAttribute, test.resourceID), added.Scope)
}
})
}
}
type setTeamResourcePermissionTest struct {
desc string
orgID int64
teamID int64
actions []string
resource string
resourceID string
seeds []types.SetResourcePermissionCommand
desc string
orgID int64
teamID int64
actions []string
resource string
resourceID string
resourceAttribute string
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
tests := []setTeamResourcePermissionTest{
{
desc: "should add new resource permission for team",
orgID: 1,
teamID: 1,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
desc: "should add new resource permission for team",
orgID: 1,
teamID: 1,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should add new resource permission when others exist",
orgID: 1,
teamID: 1,
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
desc: "should add new resource permission when others exist",
orgID: 1,
teamID: 1,
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
{
desc: "should remove permissions for team",
orgID: 1,
teamID: 1,
actions: []string{},
resource: "datasources",
resourceID: "1",
desc: "should remove permissions for team",
orgID: 1,
teamID: 1,
actions: []string{},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
@@ -154,9 +165,10 @@ func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
}
added, err := store.SetTeamResourcePermission(context.Background(), test.orgID, test.teamID, types.SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
}, nil)
require.NoError(t, err)
@@ -164,59 +176,65 @@ func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
assert.Equal(t, accesscontrol.ResourcePermission{}, *added)
} else {
assert.Len(t, added.Actions, len(test.actions))
assert.Equal(t, accesscontrol.GetResourceScope(test.resource, test.resourceID), added.Scope)
assert.Equal(t, accesscontrol.Scope(test.resource, test.resourceAttribute, test.resourceID), added.Scope)
}
})
}
}
type setBuiltInResourcePermissionTest struct {
desc string
orgID int64
builtInRole string
actions []string
resource string
resourceID string
seeds []types.SetResourcePermissionCommand
desc string
orgID int64
builtInRole string
actions []string
resource string
resourceID string
resourceAttribute string
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
tests := []setBuiltInResourcePermissionTest{
{
desc: "should add new resource permission for builtin role",
orgID: 1,
builtInRole: "Viewer",
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
desc: "should add new resource permission for builtin role",
orgID: 1,
builtInRole: "Viewer",
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should add new resource permission when others exist",
orgID: 1,
builtInRole: "Viewer",
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
desc: "should add new resource permission when others exist",
orgID: 1,
builtInRole: "Viewer",
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
{
desc: "should remove permissions for builtin role",
orgID: 1,
builtInRole: "Viewer",
actions: []string{},
resource: "datasources",
resourceID: "1",
desc: "should remove permissions for builtin role",
orgID: 1,
builtInRole: "Viewer",
actions: []string{},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
@@ -232,9 +250,10 @@ func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
}
added, err := store.SetBuiltInResourcePermission(context.Background(), test.orgID, test.builtInRole, types.SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
}, nil)
require.NoError(t, err)
@@ -242,46 +261,51 @@ func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
assert.Equal(t, accesscontrol.ResourcePermission{}, *added)
} else {
assert.Len(t, added.Actions, len(test.actions))
assert.Equal(t, accesscontrol.GetResourceScope(test.resource, test.resourceID), added.Scope)
assert.Equal(t, accesscontrol.Scope(test.resource, test.resourceAttribute, test.resourceID), added.Scope)
}
})
}
}
type setResourcePermissionsTest struct {
desc string
orgID int64
commands []types.SetResourcePermissionsCommand
desc string
orgID int64
resourceAttribute string
commands []types.SetResourcePermissionsCommand
}
func TestAccessControlStore_SetResourcePermissions(t *testing.T) {
tests := []setResourcePermissionsTest{
{
desc: "should set all permissions provided",
orgID: 1,
desc: "should set all permissions provided",
orgID: 1,
resourceAttribute: "uid",
commands: []types.SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
{
TeamID: 3,
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
{
BuiltinRole: "Admin",
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
ResourceAttribute: "uid",
},
},
},
@@ -304,7 +328,7 @@ func TestAccessControlStore_SetResourcePermissions(t *testing.T) {
assert.Equal(t, c.TeamID, permissions[i].TeamId)
assert.Equal(t, c.User.ID, permissions[i].UserId)
assert.Equal(t, c.BuiltinRole, permissions[i].BuiltInRole)
assert.Equal(t, accesscontrol.GetResourceScope(c.Resource, c.ResourceID), permissions[i].Scope)
assert.Equal(t, accesscontrol.Scope(c.Resource, tt.resourceAttribute, c.ResourceID), permissions[i].Scope)
}
}
})
@@ -312,13 +336,14 @@ func TestAccessControlStore_SetResourcePermissions(t *testing.T) {
}
type getResourcePermissionsTest struct {
desc string
user *models.SignedInUser
numUsers int
actions []string
resource string
resourceID string
onlyManaged bool
desc string
user *models.SignedInUser
numUsers int
actions []string
resource string
resourceID string
resourceAttribute string
onlyManaged bool
}
func TestAccessControlStore_GetResourcePermissions(t *testing.T) {
@@ -330,10 +355,11 @@ func TestAccessControlStore_GetResourcePermissions(t *testing.T) {
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}},
}},
numUsers: 3,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
numUsers: 3,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
},
{
desc: "should return manage permissions for all resource ids",
@@ -342,11 +368,12 @@ func TestAccessControlStore_GetResourcePermissions(t *testing.T) {
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}},
}},
numUsers: 3,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
onlyManaged: true,
numUsers: 3,
actions: []string{"datasources:query"},
resource: "datasources",
resourceID: "1",
resourceAttribute: "uid",
onlyManaged: true,
},
}
@@ -389,14 +416,15 @@ func TestAccessControlStore_GetResourcePermissions(t *testing.T) {
})
require.NoError(t, err)
seedResourcePermissions(t, store, sql, test.actions, test.resource, test.resourceID, test.numUsers)
seedResourcePermissions(t, store, sql, test.actions, test.resource, test.resourceID, test.resourceAttribute, test.numUsers)
permissions, err := store.GetResourcePermissions(context.Background(), test.user.OrgId, types.GetResourcePermissionsQuery{
User: test.user,
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
OnlyManaged: test.onlyManaged,
User: test.user,
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
ResourceAttribute: test.resourceAttribute,
OnlyManaged: test.onlyManaged,
})
require.NoError(t, err)
@@ -409,7 +437,7 @@ func TestAccessControlStore_GetResourcePermissions(t *testing.T) {
}
}
func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlstore.SQLStore, actions []string, resource, resourceID string, numUsers int) {
func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlstore.SQLStore, actions []string, resource, resourceID, resourceAttribute string, numUsers int) {
t.Helper()
for i := 0; i < numUsers; i++ {
org, _ := sql.GetOrgByName("test")
@@ -427,9 +455,10 @@ func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlst
require.NoError(t, err)
_, err = store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: u.Id}, types.SetResourcePermissionCommand{
Actions: actions,
Resource: resource,
ResourceID: resourceID,
Actions: actions,
Resource: resource,
ResourceID: resourceID,
ResourceAttribute: resourceAttribute,
}, nil)
require.NoError(t, err)
}