mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access control: Basic structure and functionality behind feature toggle (#31893)
Co-authored-by: Alexander Zobnin <alexander.zobnin@grafana.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> Co-authored-by: Arve Knudsen <arve.knudsen@grafana.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@grafana.com>
This commit is contained in:
57
pkg/services/accesscontrol/database/common_test.go
Normal file
57
pkg/services/accesscontrol/database/common_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// accessControlStoreTestImpl is a test store implementation which additionally executes a database migrations
|
||||
type accessControlStoreTestImpl struct {
|
||||
AccessControlStore
|
||||
}
|
||||
|
||||
func (ac *accessControlStoreTestImpl) AddMigration(mg *migrator.Migrator) {
|
||||
AddAccessControlMigrations(mg)
|
||||
}
|
||||
|
||||
func setupTestEnv(t testing.TB) *accessControlStoreTestImpl {
|
||||
t.Helper()
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
store := overrideDatabaseInRegistry(cfg)
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
store.SQLStore = sqlStore
|
||||
|
||||
err := store.Init()
|
||||
require.NoError(t, err)
|
||||
return &store
|
||||
}
|
||||
|
||||
func overrideDatabaseInRegistry(cfg *setting.Cfg) accessControlStoreTestImpl {
|
||||
store := accessControlStoreTestImpl{
|
||||
AccessControlStore: AccessControlStore{
|
||||
SQLStore: nil,
|
||||
},
|
||||
}
|
||||
|
||||
overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) {
|
||||
if _, ok := descriptor.Instance.(*AccessControlStore); ok {
|
||||
return ®istry.Descriptor{
|
||||
Name: "Database",
|
||||
Instance: &store,
|
||||
InitPriority: descriptor.InitPriority,
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
registry.RegisterOverride(overrideServiceFunc)
|
||||
|
||||
return store
|
||||
}
|
||||
696
pkg/services/accesscontrol/database/database.go
Normal file
696
pkg/services/accesscontrol/database/database.go
Normal file
@@ -0,0 +1,696 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// TimeNow makes it possible to test usage of time
|
||||
var TimeNow = time.Now
|
||||
|
||||
type AccessControlStore struct {
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterService(&AccessControlStore{})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetRoles(ctx context.Context, orgID int64) ([]*accesscontrol.Role, error) {
|
||||
var result []*accesscontrol.Role
|
||||
err := ac.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
roles := make([]*accesscontrol.Role, 0)
|
||||
q := "SELECT id, uid, org_id, name, description, updated FROM role WHERE org_id = ?"
|
||||
if err := sess.SQL(q, orgID).Find(&roles); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result = roles
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetRole(ctx context.Context, orgID, roleID int64) (*accesscontrol.RoleDTO, error) {
|
||||
var result *accesscontrol.RoleDTO
|
||||
|
||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
role, err := getRoleById(sess, roleID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permissions, err := getRolePermissions(sess, roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role.Permissions = permissions
|
||||
result = role
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetRoleByUID(ctx context.Context, orgId int64, uid string) (*accesscontrol.RoleDTO, error) {
|
||||
var result *accesscontrol.RoleDTO
|
||||
|
||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
role, err := getRoleByUID(sess, uid, orgId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permissions, err := getRolePermissions(sess, role.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role.Permissions = permissions
|
||||
result = role
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) CreateRole(ctx context.Context, cmd accesscontrol.CreateRoleCommand) (*accesscontrol.Role, error) {
|
||||
var result *accesscontrol.Role
|
||||
|
||||
err := ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
role, err := ac.createRole(sess, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result = role
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) createRole(sess *sqlstore.DBSession, cmd accesscontrol.CreateRoleCommand) (*accesscontrol.Role, error) {
|
||||
if cmd.UID == "" {
|
||||
uid, err := generateNewRoleUID(sess, cmd.OrgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate UID for role %q: %w", cmd.Name, err)
|
||||
}
|
||||
cmd.UID = uid
|
||||
}
|
||||
|
||||
role := &accesscontrol.Role{
|
||||
OrgID: cmd.OrgID,
|
||||
UID: cmd.UID,
|
||||
Name: cmd.Name,
|
||||
Description: cmd.Description,
|
||||
Created: TimeNow(),
|
||||
Updated: TimeNow(),
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(role); err != nil {
|
||||
if ac.SQLStore.Dialect.IsUniqueConstraintViolation(err) && strings.Contains(err.Error(), "name") {
|
||||
return nil, fmt.Errorf("role with the name '%s' already exists: %w", cmd.Name, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) CreateRoleWithPermissions(ctx context.Context, cmd accesscontrol.CreateRoleWithPermissionsCommand) (*accesscontrol.RoleDTO, error) {
|
||||
var result *accesscontrol.RoleDTO
|
||||
|
||||
err := ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
createRoleCmd := accesscontrol.CreateRoleCommand{
|
||||
OrgID: cmd.OrgID,
|
||||
UID: cmd.UID,
|
||||
Name: cmd.Name,
|
||||
Description: cmd.Description,
|
||||
}
|
||||
|
||||
role, err := ac.createRole(sess, createRoleCmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result = &accesscontrol.RoleDTO{
|
||||
ID: role.ID,
|
||||
UID: role.UID,
|
||||
OrgID: role.OrgID,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
Created: role.Created,
|
||||
Updated: role.Updated,
|
||||
}
|
||||
|
||||
// Add permissions
|
||||
for _, p := range cmd.Permissions {
|
||||
createPermissionCmd := accesscontrol.CreatePermissionCommand{
|
||||
RoleID: role.ID,
|
||||
Permission: p.Permission,
|
||||
Scope: p.Scope,
|
||||
}
|
||||
|
||||
permission, err := createPermission(sess, createPermissionCmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Permissions = append(result.Permissions, *permission)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// UpdateRole updates role with permissions
|
||||
func (ac *AccessControlStore) UpdateRole(ctx context.Context, cmd accesscontrol.UpdateRoleCommand) (*accesscontrol.RoleDTO, error) {
|
||||
var result *accesscontrol.RoleDTO
|
||||
err := ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
// TODO: work with both ID and UID
|
||||
existingRole, err := getRoleByUID(sess, cmd.UID, cmd.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := existingRole.Version + 1
|
||||
if cmd.Version != 0 {
|
||||
if existingRole.Version >= cmd.Version {
|
||||
return fmt.Errorf(
|
||||
"could not update '%s' (UID %s) from version %d to %d: %w",
|
||||
cmd.Name,
|
||||
existingRole.UID,
|
||||
existingRole.Version,
|
||||
cmd.Version,
|
||||
accesscontrol.ErrVersionLE,
|
||||
)
|
||||
}
|
||||
version = cmd.Version
|
||||
}
|
||||
|
||||
role := &accesscontrol.Role{
|
||||
ID: existingRole.ID,
|
||||
UID: existingRole.UID,
|
||||
Version: version,
|
||||
OrgID: existingRole.OrgID,
|
||||
Name: cmd.Name,
|
||||
Description: cmd.Description,
|
||||
Updated: TimeNow(),
|
||||
}
|
||||
|
||||
affectedRows, err := sess.ID(existingRole.ID).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRows == 0 {
|
||||
return accesscontrol.ErrRoleNotFound
|
||||
}
|
||||
|
||||
result = &accesscontrol.RoleDTO{
|
||||
ID: role.ID,
|
||||
Version: version,
|
||||
UID: role.UID,
|
||||
OrgID: role.OrgID,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
Created: role.Created,
|
||||
Updated: role.Updated,
|
||||
}
|
||||
|
||||
// Delete role's permissions
|
||||
_, err = sess.Exec("DELETE FROM permission WHERE role_id = ?", existingRole.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add permissions
|
||||
for _, p := range cmd.Permissions {
|
||||
createPermissionCmd := accesscontrol.CreatePermissionCommand{
|
||||
RoleID: role.ID,
|
||||
Permission: p.Permission,
|
||||
Scope: p.Scope,
|
||||
}
|
||||
|
||||
permission, err := createPermission(sess, createPermissionCmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Permissions = append(result.Permissions, *permission)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) DeleteRole(cmd *accesscontrol.DeleteRoleCommand) error {
|
||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
roleId := cmd.ID
|
||||
if roleId == 0 {
|
||||
role, err := getRoleByUID(sess, cmd.UID, cmd.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
roleId = role.ID
|
||||
}
|
||||
|
||||
// Delete role's permissions
|
||||
_, err := sess.Exec("DELETE FROM permission WHERE role_id = ?", roleId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Exec("DELETE FROM role WHERE id = ? AND org_id = ?", roleId, cmd.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetRolePermissions(ctx context.Context, roleID int64) ([]accesscontrol.Permission, error) {
|
||||
var result []accesscontrol.Permission
|
||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
permissions, err := getRolePermissions(sess, roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result = permissions
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) CreatePermission(ctx context.Context, cmd accesscontrol.CreatePermissionCommand) (*accesscontrol.Permission, error) {
|
||||
var result *accesscontrol.Permission
|
||||
err := ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
permission, err := createPermission(sess, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result = permission
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) UpdatePermission(cmd *accesscontrol.UpdatePermissionCommand) (*accesscontrol.Permission, error) {
|
||||
var result *accesscontrol.Permission
|
||||
err := ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
permission := &accesscontrol.Permission{
|
||||
Permission: cmd.Permission,
|
||||
Scope: cmd.Scope,
|
||||
Updated: TimeNow(),
|
||||
}
|
||||
|
||||
affectedRows, err := sess.ID(cmd.ID).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRows == 0 {
|
||||
return accesscontrol.ErrPermissionNotFound
|
||||
}
|
||||
|
||||
result = permission
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) DeletePermission(ctx context.Context, cmd *accesscontrol.DeletePermissionCommand) error {
|
||||
return ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
_, err := sess.Exec("DELETE FROM permission WHERE id = ?", cmd.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetTeamRoles(query *accesscontrol.GetTeamRolesQuery) ([]*accesscontrol.RoleDTO, error) {
|
||||
var result []*accesscontrol.RoleDTO
|
||||
err := ac.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
q := `SELECT
|
||||
role.id,
|
||||
role.name AS name,
|
||||
role.description AS description,
|
||||
role.updated FROM role AS role
|
||||
INNER JOIN team_role ON role.id = team_role.role_id AND team_role.team_id = ?
|
||||
WHERE role.org_id = ? `
|
||||
|
||||
if err := sess.SQL(q, query.TeamID, query.OrgID).Find(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetUserRoles(ctx context.Context, query accesscontrol.GetUserRolesQuery) ([]*accesscontrol.RoleDTO, error) {
|
||||
var result []*accesscontrol.RoleDTO
|
||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
// TODO: optimize this
|
||||
filter, params := ac.userRolesFilter(query.OrgID, query.UserID, query.Roles)
|
||||
|
||||
q := `SELECT
|
||||
role.id,
|
||||
role.org_id,
|
||||
role.name,
|
||||
role.description,
|
||||
role.created,
|
||||
role.updated
|
||||
FROM role
|
||||
` + filter
|
||||
|
||||
err := sess.SQL(q, params...).Find(&result)
|
||||
return err
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]*accesscontrol.Permission, error) {
|
||||
var result []*accesscontrol.Permission
|
||||
err := ac.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
filter, params := ac.userRolesFilter(query.OrgID, query.UserID, query.Roles)
|
||||
|
||||
// TODO: optimize this
|
||||
q := `SELECT
|
||||
permission.id,
|
||||
permission.role_id,
|
||||
permission.permission,
|
||||
permission.scope,
|
||||
permission.updated,
|
||||
permission.created
|
||||
FROM permission
|
||||
INNER JOIN role ON role.id = permission.role_id
|
||||
` + filter
|
||||
|
||||
if err := sess.SQL(q, params...).Find(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (*AccessControlStore) userRolesFilter(orgID, userID int64, roles []string) (string, []interface{}) {
|
||||
q := `WHERE role.id IN (
|
||||
SELECT up.role_id FROM user_role AS up WHERE up.user_id = ?
|
||||
UNION
|
||||
SELECT tp.role_id FROM team_role as tp
|
||||
INNER JOIN team_member as tm ON tm.team_id = tp.team_id
|
||||
WHERE tm.user_id = ?`
|
||||
params := []interface{}{userID, userID}
|
||||
|
||||
if len(roles) != 0 {
|
||||
q += `
|
||||
UNION
|
||||
SELECT br.role_id FROM builtin_role AS br
|
||||
WHERE role IN (? ` + strings.Repeat(", ?", len(roles)-1) + `)`
|
||||
for _, role := range roles {
|
||||
params = append(params, role)
|
||||
}
|
||||
}
|
||||
|
||||
q += `) and role.org_id = ?`
|
||||
params = append(params, orgID)
|
||||
|
||||
return q, params
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) AddTeamRole(cmd *accesscontrol.AddTeamRoleCommand) error {
|
||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from team_role WHERE org_id=? and team_id=? and role_id=?", cmd.OrgID, cmd.TeamID, cmd.RoleID); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
return accesscontrol.ErrTeamRoleAlreadyAdded
|
||||
}
|
||||
|
||||
if _, err := teamExists(cmd.OrgID, cmd.TeamID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := roleExists(cmd.OrgID, cmd.RoleID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
teamRole := &accesscontrol.TeamRole{
|
||||
OrgID: cmd.OrgID,
|
||||
TeamID: cmd.TeamID,
|
||||
RoleID: cmd.RoleID,
|
||||
Created: TimeNow(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(teamRole)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) RemoveTeamRole(cmd *accesscontrol.RemoveTeamRoleCommand) error {
|
||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
if _, err := teamExists(cmd.OrgID, cmd.TeamID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := roleExists(cmd.OrgID, cmd.RoleID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := "DELETE FROM team_role WHERE org_id=? and team_id=? and role_id=?"
|
||||
res, err := sess.Exec(q, cmd.OrgID, cmd.TeamID, cmd.RoleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := res.RowsAffected()
|
||||
if rows == 0 {
|
||||
return accesscontrol.ErrTeamRoleNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) AddUserRole(cmd *accesscontrol.AddUserRoleCommand) error {
|
||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from user_role WHERE org_id=? and user_id=? and role_id=?", cmd.OrgID, cmd.UserID, cmd.RoleID); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
return accesscontrol.ErrUserRoleAlreadyAdded
|
||||
}
|
||||
|
||||
if _, err := roleExists(cmd.OrgID, cmd.RoleID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userRole := &accesscontrol.UserRole{
|
||||
OrgID: cmd.OrgID,
|
||||
UserID: cmd.UserID,
|
||||
RoleID: cmd.RoleID,
|
||||
Created: TimeNow(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(userRole)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) RemoveUserRole(cmd *accesscontrol.RemoveUserRoleCommand) error {
|
||||
return ac.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
if _, err := roleExists(cmd.OrgID, cmd.RoleID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := "DELETE FROM user_role WHERE org_id=? and user_id=? and role_id=?"
|
||||
res, err := sess.Exec(q, cmd.OrgID, cmd.UserID, cmd.RoleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := res.RowsAffected()
|
||||
if rows == 0 {
|
||||
return accesscontrol.ErrUserRoleNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ac *AccessControlStore) AddBuiltinRole(ctx context.Context, orgID, roleID int64, roleName string) error {
|
||||
if !models.RoleType(roleName).IsValid() && roleName != "Grafana Admin" {
|
||||
return fmt.Errorf("role '%s' is not a valid role", roleName)
|
||||
}
|
||||
|
||||
return ac.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from builtin_role WHERE role_id=? and role=?", roleID, roleName); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
return accesscontrol.ErrUserRoleAlreadyAdded
|
||||
}
|
||||
|
||||
if _, err := roleExists(orgID, roleID, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role := accesscontrol.BuiltinRole{
|
||||
RoleID: roleID,
|
||||
Role: roleName,
|
||||
Updated: TimeNow(),
|
||||
Created: TimeNow(),
|
||||
}
|
||||
|
||||
_, err := sess.Table("builtin_role").Insert(role)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func getRoleById(sess *sqlstore.DBSession, roleId int64, orgId int64) (*accesscontrol.RoleDTO, error) {
|
||||
role := accesscontrol.Role{OrgID: orgId, ID: roleId}
|
||||
has, err := sess.Get(&role)
|
||||
if !has {
|
||||
return nil, accesscontrol.ErrRoleNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleDTO := accesscontrol.RoleDTO{
|
||||
ID: roleId,
|
||||
OrgID: role.OrgID,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
Permissions: nil,
|
||||
Created: role.Created,
|
||||
Updated: role.Updated,
|
||||
}
|
||||
|
||||
return &roleDTO, nil
|
||||
}
|
||||
|
||||
func getRoleByUID(sess *sqlstore.DBSession, uid string, orgId int64) (*accesscontrol.RoleDTO, error) {
|
||||
role := accesscontrol.Role{OrgID: orgId, UID: uid}
|
||||
has, err := sess.Get(&role)
|
||||
if !has {
|
||||
return nil, accesscontrol.ErrRoleNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleDTO := accesscontrol.RoleDTO{
|
||||
ID: role.ID,
|
||||
UID: role.UID,
|
||||
Version: role.Version,
|
||||
OrgID: role.OrgID,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
Permissions: nil,
|
||||
Created: role.Created,
|
||||
Updated: role.Updated,
|
||||
}
|
||||
|
||||
return &roleDTO, nil
|
||||
}
|
||||
|
||||
func getRolePermissions(sess *sqlstore.DBSession, roleId int64) ([]accesscontrol.Permission, error) {
|
||||
permissions := make([]accesscontrol.Permission, 0)
|
||||
q := "SELECT id, role_id, permission, scope, updated, created FROM permission WHERE role_id = ?"
|
||||
if err := sess.SQL(q, roleId).Find(&permissions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func createPermission(sess *sqlstore.DBSession, cmd accesscontrol.CreatePermissionCommand) (*accesscontrol.Permission, error) {
|
||||
permission := &accesscontrol.Permission{
|
||||
RoleID: cmd.RoleID,
|
||||
Permission: cmd.Permission,
|
||||
Scope: cmd.Scope,
|
||||
Created: TimeNow(),
|
||||
Updated: TimeNow(),
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(permission); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return permission, nil
|
||||
}
|
||||
|
||||
func teamExists(orgId int64, teamId int64, sess *sqlstore.DBSession) (bool, error) {
|
||||
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
|
||||
return false, err
|
||||
} else if len(res) != 1 {
|
||||
return false, accesscontrol.ErrTeamNotFound
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func roleExists(orgId int64, roleId int64, sess *sqlstore.DBSession) (bool, error) {
|
||||
if res, err := sess.Query("SELECT 1 from role WHERE org_id=? and id=?", orgId, roleId); err != nil {
|
||||
return false, err
|
||||
} else if len(res) != 1 {
|
||||
return false, accesscontrol.ErrRoleNotFound
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func generateNewRoleUID(sess *sqlstore.DBSession, orgID int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&accesscontrol.Role{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return uid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", accesscontrol.ErrRoleFailedGenerateUniqueUID
|
||||
}
|
||||
|
||||
func MockTimeNow() {
|
||||
var timeSeed int64
|
||||
TimeNow = func() time.Time {
|
||||
fakeNow := time.Unix(timeSeed, 0).UTC()
|
||||
timeSeed++
|
||||
return fakeNow
|
||||
}
|
||||
}
|
||||
|
||||
func ResetTimeNow() {
|
||||
TimeNow = time.Now
|
||||
}
|
||||
65
pkg/services/accesscontrol/database/database_bench_test.go
Normal file
65
pkg/services/accesscontrol/database/database_bench_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
actesting "github.com/grafana/grafana/pkg/services/accesscontrol/testing"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
func setup(b *testing.B, rolesPerUser, users int) *accessControlStoreTestImpl {
|
||||
ac := setupTestEnv(b)
|
||||
b.Cleanup(registry.ClearOverrides)
|
||||
actesting.GenerateRoles(b, ac.SQLStore, ac, rolesPerUser, users)
|
||||
return ac
|
||||
}
|
||||
|
||||
func getRoles(b *testing.B, ac accesscontrol.Store, rolesPerUser, users int) {
|
||||
userQuery := models.GetUserByLoginQuery{
|
||||
LoginOrEmail: "user1@test.com",
|
||||
}
|
||||
err := sqlstore.GetUserByLogin(&userQuery)
|
||||
require.NoError(b, err)
|
||||
userId := userQuery.Result.Id
|
||||
|
||||
userPermissionsQuery := accesscontrol.GetUserPermissionsQuery{OrgID: 1, UserID: userId}
|
||||
res, err := ac.GetUserPermissions(context.Background(), userPermissionsQuery)
|
||||
require.NoError(b, err)
|
||||
expectedPermissions := actesting.PermissionsPerRole * rolesPerUser
|
||||
assert.Greater(b, len(res), expectedPermissions)
|
||||
}
|
||||
|
||||
func benchmarkRoles(b *testing.B, rolesPerUser, users int) {
|
||||
ac := setup(b, rolesPerUser, users)
|
||||
// We don't wanna measure DB initialization
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
getRoles(b, ac, rolesPerUser, users)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRolesUsers10_10(b *testing.B) { benchmarkRoles(b, 10, 10) }
|
||||
|
||||
func BenchmarkRolesUsers10_100(b *testing.B) { benchmarkRoles(b, 10, 100) }
|
||||
func BenchmarkRolesUsers10_500(b *testing.B) { benchmarkRoles(b, 10, 500) }
|
||||
func BenchmarkRolesUsers10_1000(b *testing.B) { benchmarkRoles(b, 10, 1000) }
|
||||
func BenchmarkRolesUsers10_5000(b *testing.B) { benchmarkRoles(b, 10, 5000) }
|
||||
func BenchmarkRolesUsers10_10000(b *testing.B) {
|
||||
if testing.Short() {
|
||||
b.Skip("Skipping benchmark in short mode")
|
||||
}
|
||||
benchmarkRoles(b, 10, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkRolesPerUser10_10(b *testing.B) { benchmarkRoles(b, 10, 10) }
|
||||
func BenchmarkRolesPerUser100_10(b *testing.B) { benchmarkRoles(b, 100, 10) }
|
||||
func BenchmarkRolesPerUser500_10(b *testing.B) { benchmarkRoles(b, 500, 10) }
|
||||
func BenchmarkRolesPerUser1000_10(b *testing.B) { benchmarkRoles(b, 1000, 10) }
|
||||
118
pkg/services/accesscontrol/database/database_mig.go
Normal file
118
pkg/services/accesscontrol/database/database_mig.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package database
|
||||
|
||||
import "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func AddAccessControlMigrations(mg *migrator.Migrator) {
|
||||
permissionV1 := migrator.Table{
|
||||
Name: "permission",
|
||||
Columns: []*migrator.Column{
|
||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
||||
{Name: "permission", Type: migrator.DB_Varchar, Length: 190, Nullable: false},
|
||||
{Name: "scope", Type: migrator.DB_Varchar, Length: 190, Nullable: false},
|
||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"role_id"}},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create permission table", migrator.NewAddTableMigration(permissionV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add unique index permission.role_id", migrator.NewAddIndexMigration(permissionV1, permissionV1.Indices[0]))
|
||||
|
||||
roleV1 := migrator.Table{
|
||||
Name: "role",
|
||||
Columns: []*migrator.Column{
|
||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false},
|
||||
{Name: "description", Type: migrator.DB_Text, Nullable: true},
|
||||
{Name: "version", Type: migrator.DB_BigInt, Nullable: false},
|
||||
{Name: "org_id", Type: migrator.DB_BigInt},
|
||||
{Name: "uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: false},
|
||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "name"}, Type: migrator.UniqueIndex},
|
||||
{Cols: []string{"org_id", "uid"}, Type: migrator.UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create role table", migrator.NewAddTableMigration(roleV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index role.org_id", migrator.NewAddIndexMigration(roleV1, roleV1.Indices[0]))
|
||||
mg.AddMigration("add unique index role_org_id_name", migrator.NewAddIndexMigration(roleV1, roleV1.Indices[1]))
|
||||
mg.AddMigration("add index role_org_id_uid", migrator.NewAddIndexMigration(roleV1, roleV1.Indices[2]))
|
||||
|
||||
teamRoleV1 := migrator.Table{
|
||||
Name: "team_role",
|
||||
Columns: []*migrator.Column{
|
||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: migrator.DB_BigInt},
|
||||
{Name: "team_id", Type: migrator.DB_BigInt},
|
||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "team_id", "role_id"}, Type: migrator.UniqueIndex},
|
||||
{Cols: []string{"team_id"}},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create team role table", migrator.NewAddTableMigration(teamRoleV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index team_role.org_id", migrator.NewAddIndexMigration(teamRoleV1, teamRoleV1.Indices[0]))
|
||||
mg.AddMigration("add unique index team_role_org_id_team_id_role_id", migrator.NewAddIndexMigration(teamRoleV1, teamRoleV1.Indices[1]))
|
||||
mg.AddMigration("add index team_role.team_id", migrator.NewAddIndexMigration(teamRoleV1, teamRoleV1.Indices[2]))
|
||||
|
||||
userRoleV1 := migrator.Table{
|
||||
Name: "user_role",
|
||||
Columns: []*migrator.Column{
|
||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: migrator.DB_BigInt},
|
||||
{Name: "user_id", Type: migrator.DB_BigInt},
|
||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "user_id", "role_id"}, Type: migrator.UniqueIndex},
|
||||
{Cols: []string{"user_id"}},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create user role table", migrator.NewAddTableMigration(userRoleV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index user_role.org_id", migrator.NewAddIndexMigration(userRoleV1, userRoleV1.Indices[0]))
|
||||
mg.AddMigration("add unique index user_role_org_id_user_id_role_id", migrator.NewAddIndexMigration(userRoleV1, userRoleV1.Indices[1]))
|
||||
mg.AddMigration("add index user_role.user_id", migrator.NewAddIndexMigration(userRoleV1, userRoleV1.Indices[2]))
|
||||
|
||||
builtinRoleV1 := migrator.Table{
|
||||
Name: "builtin_role",
|
||||
Columns: []*migrator.Column{
|
||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "role", Type: migrator.DB_NVarchar, Length: 190, Nullable: false},
|
||||
{Name: "role_id", Type: migrator.DB_BigInt},
|
||||
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"role_id"}},
|
||||
{Cols: []string{"role"}},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create builtin role table", migrator.NewAddTableMigration(builtinRoleV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index builtin_role.role_id", migrator.NewAddIndexMigration(builtinRoleV1, builtinRoleV1.Indices[0]))
|
||||
mg.AddMigration("add index builtin_role.name", migrator.NewAddIndexMigration(builtinRoleV1, builtinRoleV1.Indices[1]))
|
||||
}
|
||||
387
pkg/services/accesscontrol/database/database_test.go
Normal file
387
pkg/services/accesscontrol/database/database_test.go
Normal file
@@ -0,0 +1,387 @@
|
||||
// +build integration
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
actesting "github.com/grafana/grafana/pkg/services/accesscontrol/testing"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
func TestCreatingRole(t *testing.T) {
|
||||
MockTimeNow()
|
||||
t.Cleanup(ResetTimeNow)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
role actesting.RoleTestCase
|
||||
permissions []actesting.PermissionTestCase
|
||||
|
||||
expectedError error
|
||||
expectedUpdated time.Time
|
||||
}{
|
||||
{
|
||||
desc: "should successfully create simple role",
|
||||
role: actesting.RoleTestCase{
|
||||
Name: "a name",
|
||||
Permissions: nil,
|
||||
},
|
||||
expectedUpdated: time.Unix(1, 0).UTC(),
|
||||
},
|
||||
{
|
||||
desc: "should successfully create role with UID",
|
||||
role: actesting.RoleTestCase{
|
||||
Name: "a name",
|
||||
UID: "testUID",
|
||||
Permissions: nil,
|
||||
},
|
||||
expectedUpdated: time.Unix(3, 0).UTC(),
|
||||
},
|
||||
{
|
||||
desc: "should successfully create role with permissions",
|
||||
role: actesting.RoleTestCase{
|
||||
Name: "a name",
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "users", Permission: "admin.users:create"},
|
||||
{Scope: "reports", Permission: "reports:read"},
|
||||
},
|
||||
},
|
||||
expectedUpdated: time.Unix(5, 0).UTC(),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
store := setupTestEnv(t)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
createRoleRes := actesting.CreateRole(t, store, tc.role)
|
||||
|
||||
res, err := store.GetRoleByUID(context.Background(), 1, createRoleRes.UID)
|
||||
role := res
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedUpdated, role.Updated)
|
||||
|
||||
if tc.role.UID != "" {
|
||||
assert.Equal(t, tc.role.UID, role.UID)
|
||||
}
|
||||
|
||||
if tc.role.Permissions == nil {
|
||||
assert.Empty(t, role.Permissions)
|
||||
} else {
|
||||
assert.Len(t, tc.role.Permissions, len(role.Permissions))
|
||||
for i, p := range role.Permissions {
|
||||
assert.Equal(t, tc.role.Permissions[i].Permission, p.Permission)
|
||||
assert.Equal(t, tc.role.Permissions[i].Scope, p.Scope)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatingRole(t *testing.T) {
|
||||
MockTimeNow()
|
||||
t.Cleanup(ResetTimeNow)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
role actesting.RoleTestCase
|
||||
newRole actesting.RoleTestCase
|
||||
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
desc: "should successfully update role name",
|
||||
role: actesting.RoleTestCase{
|
||||
Name: "a name",
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "reports", Permission: "reports:read"},
|
||||
},
|
||||
},
|
||||
newRole: actesting.RoleTestCase{
|
||||
Name: "a different name",
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "reports", Permission: "reports:create"},
|
||||
{Scope: "reports", Permission: "reports:read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should successfully create role with permissions",
|
||||
role: actesting.RoleTestCase{
|
||||
Name: "a name",
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "users", Permission: "admin.users:create"},
|
||||
{Scope: "reports", Permission: "reports:read"},
|
||||
},
|
||||
},
|
||||
newRole: actesting.RoleTestCase{
|
||||
Name: "a different name",
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "users", Permission: "admin.users:read"},
|
||||
{Scope: "reports", Permission: "reports:create"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
store := setupTestEnv(t)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
role := actesting.CreateRole(t, store, tc.role)
|
||||
updated := role.Updated
|
||||
|
||||
updateRoleCmd := accesscontrol.UpdateRoleCommand{
|
||||
UID: role.UID,
|
||||
Name: tc.newRole.Name,
|
||||
}
|
||||
for _, perm := range tc.newRole.Permissions {
|
||||
updateRoleCmd.Permissions = append(updateRoleCmd.Permissions, accesscontrol.Permission{
|
||||
Permission: perm.Permission,
|
||||
Scope: perm.Scope,
|
||||
})
|
||||
}
|
||||
|
||||
_, err := store.UpdateRole(context.Background(), updateRoleCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedRole, err := store.GetRoleByUID(context.Background(), 1, role.UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.newRole.Name, updatedRole.Name)
|
||||
assert.True(t, updatedRole.Updated.After(updated))
|
||||
assert.Equal(t, len(tc.newRole.Permissions), len(updatedRole.Permissions))
|
||||
|
||||
// Check permissions
|
||||
require.NoError(t, err)
|
||||
for i, updatedPermission := range updatedRole.Permissions {
|
||||
assert.Equal(t, tc.newRole.Permissions[i].Permission, updatedPermission.Permission)
|
||||
assert.Equal(t, tc.newRole.Permissions[i].Scope, updatedPermission.Scope)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type userRoleTestCase struct {
|
||||
desc string
|
||||
userName string
|
||||
teamName string
|
||||
userRoles []actesting.RoleTestCase
|
||||
teamRoles []actesting.RoleTestCase
|
||||
}
|
||||
|
||||
func TestUserRole(t *testing.T) {
|
||||
MockTimeNow()
|
||||
t.Cleanup(ResetTimeNow)
|
||||
|
||||
testCases := []userRoleTestCase{
|
||||
{
|
||||
desc: "should successfully get user roles",
|
||||
userName: "testuser",
|
||||
teamName: "team1",
|
||||
userRoles: []actesting.RoleTestCase{
|
||||
{
|
||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{},
|
||||
},
|
||||
},
|
||||
teamRoles: nil,
|
||||
},
|
||||
{
|
||||
desc: "should successfully get user and team roles",
|
||||
userName: "testuser",
|
||||
teamName: "team1",
|
||||
userRoles: []actesting.RoleTestCase{
|
||||
{
|
||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{},
|
||||
},
|
||||
},
|
||||
teamRoles: []actesting.RoleTestCase{
|
||||
{
|
||||
Name: "CreateDataSource", Permissions: []actesting.PermissionTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "EditDataSource", Permissions: []actesting.PermissionTestCase{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should successfully get user and team roles if user has no roles",
|
||||
userName: "testuser",
|
||||
teamName: "team1",
|
||||
userRoles: nil,
|
||||
teamRoles: []actesting.RoleTestCase{
|
||||
{
|
||||
Name: "CreateDataSource", Permissions: []actesting.PermissionTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "EditDataSource", Permissions: []actesting.PermissionTestCase{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
store := setupTestEnv(t)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
actesting.CreateUserWithRole(t, store.SQLStore, store, tc.userName, tc.userRoles)
|
||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, tc.teamName, tc.teamRoles)
|
||||
|
||||
// Create more teams
|
||||
for i := 0; i < 10; i++ {
|
||||
teamName := fmt.Sprintf("faketeam%v", i)
|
||||
roles := []actesting.RoleTestCase{
|
||||
{
|
||||
Name: fmt.Sprintf("fakerole%v", i),
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "datasources", Permission: "datasources:create"},
|
||||
},
|
||||
},
|
||||
}
|
||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, teamName, roles)
|
||||
}
|
||||
|
||||
userQuery := models.GetUserByLoginQuery{
|
||||
LoginOrEmail: tc.userName,
|
||||
}
|
||||
err := sqlstore.GetUserByLogin(&userQuery)
|
||||
require.NoError(t, err)
|
||||
userId := userQuery.Result.Id
|
||||
|
||||
teamQuery := models.SearchTeamsQuery{
|
||||
OrgId: 1,
|
||||
Name: tc.teamName,
|
||||
}
|
||||
err = sqlstore.SearchTeams(&teamQuery)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, teamQuery.Result.Teams, 1)
|
||||
teamId := teamQuery.Result.Teams[0].Id
|
||||
|
||||
err = store.SQLStore.AddTeamMember(userId, 1, teamId, false, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
userRolesQuery := accesscontrol.GetUserRolesQuery{
|
||||
OrgID: 1,
|
||||
UserID: userQuery.Result.Id,
|
||||
}
|
||||
|
||||
res, err := store.GetUserRoles(context.Background(), userRolesQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(tc.userRoles)+len(tc.teamRoles), len(res))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type userTeamRoleTestCase struct {
|
||||
desc string
|
||||
userName string
|
||||
teamName string
|
||||
userRoles []actesting.RoleTestCase
|
||||
teamRoles []actesting.RoleTestCase
|
||||
}
|
||||
|
||||
func TestUserPermissions(t *testing.T) {
|
||||
MockTimeNow()
|
||||
t.Cleanup(ResetTimeNow)
|
||||
|
||||
testCases := []userTeamRoleTestCase{
|
||||
{
|
||||
desc: "should successfully get user and team permissions",
|
||||
userName: "testuser",
|
||||
teamName: "team1",
|
||||
userRoles: []actesting.RoleTestCase{
|
||||
{
|
||||
Name: "CreateUser", Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "users", Permission: "admin.users:create"},
|
||||
{Scope: "reports", Permission: "reports:read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
teamRoles: []actesting.RoleTestCase{
|
||||
{
|
||||
Name: "CreateDataSource", Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "datasources", Permission: "datasources:create"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
store := setupTestEnv(t)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
actesting.CreateUserWithRole(t, store.SQLStore, store, tc.userName, tc.userRoles)
|
||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, tc.teamName, tc.teamRoles)
|
||||
|
||||
// Create more teams
|
||||
for i := 0; i < 10; i++ {
|
||||
teamName := fmt.Sprintf("faketeam%v", i)
|
||||
roles := []actesting.RoleTestCase{
|
||||
{
|
||||
Name: fmt.Sprintf("fakerole%v", i),
|
||||
Permissions: []actesting.PermissionTestCase{
|
||||
{Scope: "datasources", Permission: "datasources:create"},
|
||||
},
|
||||
},
|
||||
}
|
||||
actesting.CreateTeamWithRole(t, store.SQLStore, store, teamName, roles)
|
||||
}
|
||||
|
||||
userQuery := models.GetUserByLoginQuery{
|
||||
LoginOrEmail: tc.userName,
|
||||
}
|
||||
err := sqlstore.GetUserByLogin(&userQuery)
|
||||
require.NoError(t, err)
|
||||
userId := userQuery.Result.Id
|
||||
|
||||
teamQuery := models.SearchTeamsQuery{
|
||||
OrgId: 1,
|
||||
Name: tc.teamName,
|
||||
}
|
||||
err = sqlstore.SearchTeams(&teamQuery)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, teamQuery.Result.Teams, 1)
|
||||
teamId := teamQuery.Result.Teams[0].Id
|
||||
|
||||
err = store.SQLStore.AddTeamMember(userId, 1, teamId, false, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
userPermissionsQuery := accesscontrol.GetUserPermissionsQuery{
|
||||
OrgID: 1,
|
||||
UserID: userId,
|
||||
}
|
||||
|
||||
getUserTeamsQuery := models.GetTeamsByUserQuery{
|
||||
OrgId: 1,
|
||||
UserId: userId,
|
||||
}
|
||||
err = sqlstore.GetTeamsByUser(&getUserTeamsQuery)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, getUserTeamsQuery.Result, 1)
|
||||
|
||||
expectedPermissions := []actesting.PermissionTestCase{}
|
||||
for _, p := range tc.userRoles {
|
||||
expectedPermissions = append(expectedPermissions, p.Permissions...)
|
||||
}
|
||||
for _, p := range tc.teamRoles {
|
||||
expectedPermissions = append(expectedPermissions, p.Permissions...)
|
||||
}
|
||||
|
||||
res, err := store.GetUserPermissions(context.Background(), userPermissionsQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res, len(expectedPermissions))
|
||||
assert.Contains(t, expectedPermissions, actesting.PermissionTestCase{Scope: "datasources", Permission: "datasources:create"})
|
||||
assert.NotContains(t, expectedPermissions, actesting.PermissionTestCase{Scope: "/api/restricted", Permission: "restricted:read"})
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user