diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index f28b7d0f8a6..afbf542ea09 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -28,6 +28,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/plugincontext" "github.com/grafana/grafana/pkg/plugins/plugindashboards" "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/datasourceproxy" @@ -81,6 +82,7 @@ type HTTPServer struct { ProvisioningService provisioning.ProvisioningService `inject:""` Login login.Service `inject:""` License models.Licensing `inject:""` + AccessControl accesscontrol.AccessControl `inject:""` BackendPluginManager backendplugin.Manager `inject:""` DataProxy *datasourceproxy.DatasourceProxyService `inject:""` PluginRequestValidator models.PluginRequestValidator `inject:""` diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 8c309536964..295f50fdc1d 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -9,6 +9,7 @@ import ( _ "github.com/crewjam/saml" _ "github.com/gobwas/glob" "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/validations" _ "github.com/grafana/loki/pkg/logproto" @@ -29,6 +30,7 @@ import ( func init() { registry.RegisterService(&licensing.OSSLicensingService{}) registry.RegisterService(&validations.OSSPluginRequestValidator{}) + registry.RegisterService(&ossaccesscontrol.OSSAccessControlService{}) } var IsEnterprise bool = false diff --git a/pkg/server/server.go b/pkg/server/server.go index 955ec21e9c4..29e8a216b8c 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -31,7 +31,6 @@ import ( "github.com/grafana/grafana/pkg/middleware" _ "github.com/grafana/grafana/pkg/plugins/manager" "github.com/grafana/grafana/pkg/registry" - _ "github.com/grafana/grafana/pkg/services/accesscontrol/manager" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/auth" _ "github.com/grafana/grafana/pkg/services/auth/jwt" diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index d5cbd2da758..354d43455fb 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -7,38 +7,12 @@ import ( ) type AccessControl interface { - Evaluator - Store -} - -type Evaluator interface { - // Evaluate evaluates access to the given resource + // Evaluate evaluates access to the given resource. Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error) -} -type Store interface { - // Database access methods - GetRoles(ctx context.Context, orgID int64) ([]*Role, error) - GetRole(ctx context.Context, orgID, roleID int64) (*RoleDTO, error) - GetRoleByUID(ctx context.Context, orgId int64, uid string) (*RoleDTO, error) - CreateRole(ctx context.Context, cmd CreateRoleCommand) (*Role, error) - CreateRoleWithPermissions(ctx context.Context, cmd CreateRoleWithPermissionsCommand) (*RoleDTO, error) - UpdateRole(ctx context.Context, cmd UpdateRoleCommand) (*RoleDTO, error) - DeleteRole(cmd *DeleteRoleCommand) error - GetRolePermissions(ctx context.Context, roleID int64) ([]Permission, error) - CreatePermission(ctx context.Context, cmd CreatePermissionCommand) (*Permission, error) - UpdatePermission(cmd *UpdatePermissionCommand) (*Permission, error) - DeletePermission(ctx context.Context, cmd *DeletePermissionCommand) error - GetTeamRoles(query *GetTeamRolesQuery) ([]*RoleDTO, error) - GetUserRoles(ctx context.Context, query GetUserRolesQuery) ([]*RoleDTO, error) - GetUserPermissions(ctx context.Context, query GetUserPermissionsQuery) ([]*Permission, error) - AddTeamRole(cmd *AddTeamRoleCommand) error - RemoveTeamRole(cmd *RemoveTeamRoleCommand) error - AddUserRole(cmd *AddUserRoleCommand) error - RemoveUserRole(cmd *RemoveUserRoleCommand) error - AddBuiltinRole(ctx context.Context, orgID, roleID int64, roleName string) error -} + // GetUserPermissions returns user permissions. + GetUserPermissions(ctx context.Context, user *models.SignedInUser, roles []string) ([]*Permission, error) -type Seeder interface { - Seed(ctx context.Context, orgID int64) error + // Middleware checks if service disabled or not to switch to fallback authorization. + IsDisabled() bool } diff --git a/pkg/services/accesscontrol/database/common_test.go b/pkg/services/accesscontrol/database/common_test.go deleted file mode 100644 index 6b187cf9cb8..00000000000 --- a/pkg/services/accesscontrol/database/common_test.go +++ /dev/null @@ -1,57 +0,0 @@ -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 -} diff --git a/pkg/services/accesscontrol/database/database.go b/pkg/services/accesscontrol/database/database.go deleted file mode 100644 index e9d49190565..00000000000 --- a/pkg/services/accesscontrol/database/database.go +++ /dev/null @@ -1,700 +0,0 @@ -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 { - role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID) - if err != nil { - return err - } - - if _, err := teamExists(cmd.OrgID, cmd.TeamID, sess); err != nil { - return err - } - - if res, err := sess.Query("SELECT 1 from team_role WHERE org_id=? and team_id=? and role_id=?", cmd.OrgID, cmd.TeamID, role.ID); err != nil { - return err - } else if len(res) == 1 { - return accesscontrol.ErrTeamRoleAlreadyAdded - } - - teamRole := &accesscontrol.TeamRole{ - OrgID: cmd.OrgID, - TeamID: cmd.TeamID, - RoleID: role.ID, - 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 { - role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID) - if err != nil { - return err - } - - if _, err := teamExists(cmd.OrgID, cmd.TeamID, 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, role.ID) - 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 { - role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID) - if err != nil { - return err - } - - if res, err := sess.Query("SELECT 1 from user_role WHERE org_id=? and user_id=? and role_id=?", cmd.OrgID, cmd.UserID, role.ID); err != nil { - return err - } else if len(res) == 1 { - return accesscontrol.ErrUserRoleAlreadyAdded - } - - userRole := &accesscontrol.UserRole{ - OrgID: cmd.OrgID, - UserID: cmd.UserID, - RoleID: role.ID, - 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 { - role, err := getRoleByUID(sess, cmd.RoleUID, cmd.OrgID) - if 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, role.ID) - 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 -} diff --git a/pkg/services/accesscontrol/database/database_bench_test.go b/pkg/services/accesscontrol/database/database_bench_test.go deleted file mode 100644 index 00ed0d9ffdd..00000000000 --- a/pkg/services/accesscontrol/database/database_bench_test.go +++ /dev/null @@ -1,65 +0,0 @@ -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) } diff --git a/pkg/services/accesscontrol/database/database_mig.go b/pkg/services/accesscontrol/database/database_mig.go deleted file mode 100644 index 8401b2811a8..00000000000 --- a/pkg/services/accesscontrol/database/database_mig.go +++ /dev/null @@ -1,118 +0,0 @@ -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])) -} diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go deleted file mode 100644 index 992e7b2f32a..00000000000 --- a/pkg/services/accesscontrol/database/database_test.go +++ /dev/null @@ -1,387 +0,0 @@ -// +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"}) - }) - } -} diff --git a/pkg/services/accesscontrol/manager/evaluator.go b/pkg/services/accesscontrol/evaluator/evaluator.go similarity index 78% rename from pkg/services/accesscontrol/manager/evaluator.go rename to pkg/services/accesscontrol/evaluator/evaluator.go index f731730fbf3..2351b43225d 100644 --- a/pkg/services/accesscontrol/manager/evaluator.go +++ b/pkg/services/accesscontrol/evaluator/evaluator.go @@ -1,4 +1,4 @@ -package manager +package evaluator import ( "context" @@ -11,7 +11,8 @@ import ( const roleGrafanaAdmin = "Grafana Admin" -func (m *Manager) Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error) { +// Evaluate evaluates access to the given resource, using provided AccessControl instance +func Evaluate(ctx context.Context, ac accesscontrol.AccessControl, user *models.SignedInUser, permission string, scope ...string) (bool, error) { roles := []string{string(user.OrgRole)} for _, role := range user.OrgRole.Children() { roles = append(roles, string(role)) @@ -20,11 +21,7 @@ func (m *Manager) Evaluate(ctx context.Context, user *models.SignedInUser, permi roles = append(roles, roleGrafanaAdmin) } - res, err := m.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{ - OrgID: user.OrgId, - UserID: user.UserId, - Roles: roles, - }) + res, err := ac.GetUserPermissions(ctx, user, roles) if err != nil { return false, err } diff --git a/pkg/services/accesscontrol/manager/manager.go b/pkg/services/accesscontrol/manager/manager.go deleted file mode 100644 index ac5524e4230..00000000000 --- a/pkg/services/accesscontrol/manager/manager.go +++ /dev/null @@ -1,57 +0,0 @@ -package manager - -import ( - "context" - - "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/services/accesscontrol/database" - "github.com/grafana/grafana/pkg/services/accesscontrol/seeder" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" - "github.com/grafana/grafana/pkg/setting" -) - -// Manager is the service implementing role based access control. -type Manager struct { - Cfg *setting.Cfg `inject:""` - RouteRegister routing.RouteRegister `inject:""` - Log log.Logger - *database.AccessControlStore `inject:""` -} - -func init() { - registry.RegisterService(&Manager{}) -} - -// Init initializes the Manager. -func (m *Manager) Init() error { - m.Log = log.New("accesscontrol") - - seeder := seeder.NewSeeder(m, m.Log) - - // TODO: Seed all orgs - err := seeder.Seed(context.TODO(), 1) - if err != nil { - return err - } - - return nil -} - -func (m *Manager) IsDisabled() bool { - if m.Cfg == nil { - return true - } - - _, exists := m.Cfg.FeatureToggles["accesscontrol"] - return !exists -} - -func (m *Manager) AddMigration(mg *migrator.Migrator) { - if m.IsDisabled() { - return - } - - database.AddAccessControlMigrations(mg) -} diff --git a/pkg/services/accesscontrol/manager/manager_test.go b/pkg/services/accesscontrol/manager/manager_test.go deleted file mode 100644 index 67b88151870..00000000000 --- a/pkg/services/accesscontrol/manager/manager_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package manager - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/infra/log" - "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/accesscontrol/database" - actesting "github.com/grafana/grafana/pkg/services/accesscontrol/testing" - "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" -) - -func setupTestEnv(t testing.TB) *Manager { - t.Helper() - - cfg := setting.NewCfg() - cfg.FeatureToggles = map[string]bool{"accesscontrol": true} - - ac := overrideAccessControlInRegistry(t, cfg) - - sqlStore := sqlstore.InitTestDB(t) - ac.AccessControlStore.SQLStore = sqlStore - - err := ac.Init() - require.NoError(t, err) - return &ac -} - -func overrideAccessControlInRegistry(t testing.TB, cfg *setting.Cfg) Manager { - t.Helper() - - ac := Manager{ - Cfg: cfg, - RouteRegister: routing.NewRouteRegister(), - Log: log.New("accesscontrol-test"), - AccessControlStore: &database.AccessControlStore{ - SQLStore: nil, - }, - } - - overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) { - if _, ok := descriptor.Instance.(*Manager); ok { - return ®istry.Descriptor{ - Name: "AccessControl", - Instance: &ac, - InitPriority: descriptor.InitPriority, - }, true - } - return nil, false - } - - registry.RegisterOverride(overrideServiceFunc) - - return ac -} - -type evaluatingPermissionsTestCase struct { - desc string - userName string - roles []actesting.RoleTestCase -} - -func TestEvaluatingPermissions(t *testing.T) { - testCases := []evaluatingPermissionsTestCase{ - { - desc: "should successfully evaluate access to the endpoint", - userName: "testuser", - roles: []actesting.RoleTestCase{ - { - Name: "CreateUser", Permissions: []actesting.PermissionTestCase{ - {Scope: "/api/admin/users", Permission: "post"}, - {Scope: "/api/report", Permission: "get"}, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - ac := setupTestEnv(t) - t.Cleanup(registry.ClearOverrides) - - actesting.CreateUserWithRole(t, ac.SQLStore, ac, tc.userName, tc.roles) - - userQuery := models.GetUserByLoginQuery{ - LoginOrEmail: tc.userName, - } - err := sqlstore.GetUserByLogin(&userQuery) - require.NoError(t, err) - - userRolesQuery := accesscontrol.GetUserRolesQuery{ - OrgID: 1, - UserID: userQuery.Result.Id, - } - - res, err := ac.GetUserRoles(context.Background(), userRolesQuery) - require.NoError(t, err) - assert.Len(t, res, len(tc.roles)) - - userPermissionsQuery := accesscontrol.GetUserPermissionsQuery{ - OrgID: 1, - UserID: userQuery.Result.Id, - } - - permissions, err := ac.GetUserPermissions(context.Background(), userPermissionsQuery) - require.NoError(t, err) - assert.Len(t, permissions, len(tc.roles[0].Permissions)) - }) - } -} diff --git a/pkg/services/accesscontrol/middleware/middleware.go b/pkg/services/accesscontrol/middleware/middleware.go index e363db6cefb..2975da36c22 100644 --- a/pkg/services/accesscontrol/middleware/middleware.go +++ b/pkg/services/accesscontrol/middleware/middleware.go @@ -11,8 +11,12 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" ) -func Middleware(ac accesscontrol.AccessControl) func(string, ...string) macaron.Handler { - return func(permission string, scopes ...string) macaron.Handler { +func Middleware(ac accesscontrol.AccessControl) func(macaron.Handler, string, ...string) macaron.Handler { + return func(fallback macaron.Handler, permission string, scopes ...string) macaron.Handler { + if ac.IsDisabled() { + return fallback + } + return func(c *models.ReqContext) { for i, scope := range scopes { var buf bytes.Buffer @@ -37,7 +41,7 @@ func Middleware(ac accesscontrol.AccessControl) func(string, ...string) macaron. return } if !hasAccess { - c.Logger.Info("Access denied", "error", err, "userID", c.UserId, "permission", permission, "scopes", scopes) + c.Logger.Info("Access denied", "userID", c.UserId, "permission", permission, "scopes", scopes) c.JsonApiErr(http.StatusForbidden, "Forbidden", nil) return } diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index e4dd4e2a4f0..7e683bdab18 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -54,123 +54,12 @@ type Permission struct { Created time.Time `json:"created"` } -type TeamRole struct { - ID int64 `json:"id" xorm:"pk autoincr 'id'"` - OrgID int64 `json:"orgId" xorm:"org_id"` - RoleID int64 `json:"roleId" xorm:"role_id"` - TeamID int64 `json:"teamId" xorm:"team_id"` - - Created time.Time -} - -type UserRole struct { - ID int64 `json:"id" xorm:"pk autoincr 'id'"` - OrgID int64 `json:"orgId" xorm:"org_id"` - RoleID int64 `json:"roleId" xorm:"role_id"` - UserID int64 `json:"userId" xorm:"user_id"` - - Created time.Time -} - -type BuiltinRole struct { - ID *int64 `json:"id" xorm:"pk autoincr 'id'"` - RoleID int64 `json:"roleId" xorm:"role_id"` - Role string - - Updated time.Time - Created time.Time -} - -type GetTeamRolesQuery struct { - OrgID int64 `json:"-"` - TeamID int64 `json:"teamId"` -} - -type GetUserRolesQuery struct { - OrgID int64 `json:"-"` - UserID int64 `json:"userId"` - Roles []string -} - type GetUserPermissionsQuery struct { OrgID int64 `json:"-"` UserID int64 `json:"userId"` Roles []string } -type CreatePermissionCommand struct { - RoleID int64 `json:"roleId"` - Permission string - Scope string -} - -type UpdatePermissionCommand struct { - ID int64 `json:"id"` - Permission string - Scope string -} - -type DeletePermissionCommand struct { - ID int64 `json:"id"` -} - -type CreateRoleCommand struct { - OrgID int64 `json:"-"` - UID string `json:"uid"` - Version int64 `json:"version"` - Name string `json:"name"` - Description string `json:"description"` -} - -type CreateRoleWithPermissionsCommand struct { - OrgID int64 `json:"orgId"` - UID string `json:"uid"` - Version int64 `json:"version"` - Name string `json:"name"` - Description string `json:"description"` - Permissions []Permission `json:"permissions"` -} - -type UpdateRoleCommand struct { - ID int64 `json:"id"` - OrgID int64 `json:"orgId"` - Version int64 `json:"version"` - UID string `json:"uid"` - Name string `json:"name"` - Description string `json:"description"` - Permissions []Permission `json:"permissions"` -} - -type DeleteRoleCommand struct { - ID int64 `json:"id"` - UID string `json:"uid"` - OrgID int64 `json:"org_id"` -} - -type AddTeamRoleCommand struct { - OrgID int64 `json:"org_id"` - RoleUID string `json:"role_uid"` - TeamID int64 `json:"team_id"` -} - -type RemoveTeamRoleCommand struct { - OrgID int64 `json:"org_id"` - RoleUID string `json:"role_uid"` - TeamID int64 `json:"team_id"` -} - -type AddUserRoleCommand struct { - OrgID int64 `json:"org_id"` - RoleUID string `json:"role_uid"` - UserID int64 `json:"user_id"` -} - -type RemoveUserRoleCommand struct { - OrgID int64 `json:"org_id"` - RoleUID string `json:"role_uid"` - UserID int64 `json:"user_id"` -} - type EvaluationResult struct { HasAccess bool Meta interface{} diff --git a/pkg/services/accesscontrol/ossaccesscontrol/builtin_roles.go b/pkg/services/accesscontrol/ossaccesscontrol/builtin_roles.go new file mode 100644 index 00000000000..5c7ab7f8fa3 --- /dev/null +++ b/pkg/services/accesscontrol/ossaccesscontrol/builtin_roles.go @@ -0,0 +1,42 @@ +package ossaccesscontrol + +import ( + "github.com/grafana/grafana/pkg/services/accesscontrol" +) + +var builtInRolesMap = map[string]accesscontrol.RoleDTO{ + "grafana:builtin:users:read:self": { + Name: "grafana:builtin:users:read:self", + Version: 1, + Permissions: []accesscontrol.Permission{ + { + Permission: "users:read", + Scope: "users:self", + }, + { + Permission: "users.tokens:list", + Scope: "users:self", + }, + { + Permission: "users.teams:read", + Scope: "users:self", + }, + }, + }, +} + +var builtInRoleGrants = map[string][]string{ + "Viewer": { + "grafana:builtin:users:read:self", + }, +} + +func getBuiltInRole(role string) *accesscontrol.RoleDTO { + var builtInRole accesscontrol.RoleDTO + if r, ok := builtInRolesMap[role]; ok { + // Do not modify builtInRoles + builtInRole = r + return &builtInRole + } + return nil +} diff --git a/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go new file mode 100644 index 00000000000..c5a7e57f6bf --- /dev/null +++ b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go @@ -0,0 +1,59 @@ +package ossaccesscontrol + +import ( + "context" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/evaluator" + "github.com/grafana/grafana/pkg/setting" +) + +// OSSAccessControlService is the service implementing role based access control. +type OSSAccessControlService struct { + Cfg *setting.Cfg `inject:""` + Log log.Logger +} + +// Init initializes the OSSAccessControlService. +func (ac *OSSAccessControlService) Init() error { + ac.Log = log.New("accesscontrol") + + return nil +} + +func (ac *OSSAccessControlService) IsDisabled() bool { + if ac.Cfg == nil { + return true + } + + _, exists := ac.Cfg.FeatureToggles["accesscontrol"] + return !exists +} + +// Evaluate evaluates access to the given resource +func (ac *OSSAccessControlService) Evaluate(ctx context.Context, user *models.SignedInUser, permission string, scope ...string) (bool, error) { + return evaluator.Evaluate(ctx, ac, user, permission, scope...) +} + +// GetUserPermissions returns user permissions based on built-in roles +func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user *models.SignedInUser, roles []string) ([]*accesscontrol.Permission, error) { + permissions := make([]*accesscontrol.Permission, 0) + for _, legacyRole := range roles { + if builtInRoleNames, ok := builtInRoleGrants[legacyRole]; ok { + for _, builtInRoleName := range builtInRoleNames { + builtInRole := getBuiltInRole(builtInRoleName) + if builtInRole == nil { + continue + } + for _, p := range builtInRole.Permissions { + permission := p + permissions = append(permissions, &permission) + } + } + } + } + + return permissions, nil +} diff --git a/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go new file mode 100644 index 00000000000..9268ddeac5e --- /dev/null +++ b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go @@ -0,0 +1,98 @@ +package ossaccesscontrol + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" +) + +func setupTestEnv(t testing.TB) *OSSAccessControlService { + t.Helper() + + cfg := setting.NewCfg() + cfg.FeatureToggles = map[string]bool{"accesscontrol": true} + + ac := OSSAccessControlService{ + Cfg: cfg, + Log: log.New("accesscontrol-test"), + } + + err := ac.Init() + require.NoError(t, err) + return &ac +} + +type evaluatingPermissionsTestCase struct { + desc string + user userTestCase + endpoints []endpointTestCase + evalResult bool +} + +type userTestCase struct { + name string + orgRole models.RoleType + isGrafanaAdmin bool +} + +type endpointTestCase struct { + permission string + scope []string +} + +func TestEvaluatingPermissions(t *testing.T) { + testCases := []evaluatingPermissionsTestCase{ + { + desc: "should successfully evaluate access to the endpoint", + user: userTestCase{ + name: "testuser", + orgRole: models.ROLE_EDITOR, + isGrafanaAdmin: false, + }, + endpoints: []endpointTestCase{ + {permission: "users.teams:read", scope: []string{"users:self"}}, + {permission: "users:read", scope: []string{"users:self"}}, + }, + evalResult: true, + }, + { + desc: "should restrict access to the unauthorized endpoints", + user: userTestCase{ + name: "testuser", + orgRole: models.ROLE_VIEWER, + isGrafanaAdmin: false, + }, + endpoints: []endpointTestCase{ + {permission: "users:create", scope: []string{"users"}}, + }, + evalResult: false, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + ac := setupTestEnv(t) + t.Cleanup(registry.ClearOverrides) + + user := &models.SignedInUser{ + UserId: 1, + OrgId: 1, + Name: tc.user.name, + OrgRole: tc.user.orgRole, + IsGrafanaAdmin: tc.user.isGrafanaAdmin, + } + + for _, endpoint := range tc.endpoints { + result, err := ac.Evaluate(context.Background(), user, endpoint.permission, endpoint.scope...) + require.NoError(t, err) + assert.Equal(t, tc.evalResult, result) + } + }) + } +} diff --git a/pkg/services/accesscontrol/seeder/seeder.go b/pkg/services/accesscontrol/seeder/seeder.go deleted file mode 100644 index aad5bb6c423..00000000000 --- a/pkg/services/accesscontrol/seeder/seeder.go +++ /dev/null @@ -1,219 +0,0 @@ -package seeder - -import ( - "context" - "errors" - "fmt" - - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/accesscontrol" -) - -type seeder struct { - Store accesscontrol.Store - log log.Logger -} - -var builtInRoles = []accesscontrol.RoleDTO{ - { - Name: "grafana:builtin:users:read:self", - Version: 1, - Permissions: []accesscontrol.Permission{ - { - Permission: "users:read", - Scope: "users:self", - }, - { - Permission: "users.tokens:list", - Scope: "users:self", - }, - { - Permission: "users.teams:read", - Scope: "users:self", - }, - }, - }, -} - -// FIXME: Make sure builtin grants can be removed without being recreated -var builtInRoleGrants = map[string][]string{ - "grafana:builtin:users:read:self": { - "Viewer", - }, -} - -func NewSeeder(s accesscontrol.AccessControl, log log.Logger) *seeder { - return &seeder{Store: s, log: log} -} - -func (s *seeder) Seed(ctx context.Context, orgID int64) error { - err := s.seed(ctx, orgID, builtInRoles, builtInRoleGrants) - return err -} - -func (s *seeder) seed(ctx context.Context, orgID int64, roles []accesscontrol.RoleDTO, roleGrants map[string][]string) error { - // FIXME: As this will run on startup, we want to optimize running this - existingRoles, err := s.Store.GetRoles(ctx, orgID) - if err != nil { - return err - } - roleSet := map[string]*accesscontrol.Role{} - for _, role := range existingRoles { - if role == nil { - continue - } - roleSet[role.Name] = role - } - - for _, role := range roles { - role.OrgID = orgID - - current, exists := roleSet[role.Name] - if exists { - if role.Version <= current.Version { - continue - } - } - - roleID, err := s.createOrUpdateRole(ctx, role, current) - if err != nil { - s.log.Error("failed to create/update role", "name", role.Name, "err", err) - continue - } - - if builtinRoles, exists := roleGrants[role.Name]; exists { - for _, builtinRole := range builtinRoles { - err := s.Store.AddBuiltinRole(ctx, orgID, roleID, builtinRole) - if err != nil && !errors.Is(err, accesscontrol.ErrUserRoleAlreadyAdded) { - s.log.Error("failed to assign role to role", - "name", role.Name, - "role", builtinRole, - "err", err, - ) - return err - } - } - } - } - - return nil -} - -func (s *seeder) createOrUpdateRole(ctx context.Context, role accesscontrol.RoleDTO, old *accesscontrol.Role) (int64, error) { - if role.Version == 0 { - return 0, fmt.Errorf("error when seeding '%s': all seeder roles must have a version", role.Name) - } - - if old == nil { - p, err := s.Store.CreateRoleWithPermissions(ctx, accesscontrol.CreateRoleWithPermissionsCommand{ - OrgID: role.OrgID, - Version: role.Version, - Name: role.Name, - Description: role.Description, - Permissions: role.Permissions, - }) - if err != nil { - return 0, err - } - return p.ID, nil - } - - _, err := s.Store.UpdateRole(ctx, accesscontrol.UpdateRoleCommand{ - UID: old.UID, - Name: role.Name, - Description: role.Description, - Version: role.Version, - }) - if err != nil { - if errors.Is(err, accesscontrol.ErrVersionLE) { - return old.ID, nil - } - return 0, err - } - - existingPermissions, err := s.Store.GetRolePermissions(ctx, old.ID) - if err != nil { - return 0, fmt.Errorf("failed to get current permissions for role '%s': %w", role.Name, err) - } - - err = s.idempotentUpdatePermissions(ctx, old.ID, role.Permissions, existingPermissions) - if err != nil { - return 0, fmt.Errorf("failed to update role permissions for role '%s': %w", role.Name, err) - } - return old.ID, nil -} - -func (s *seeder) idempotentUpdatePermissions(ctx context.Context, roleID int64, new []accesscontrol.Permission, old []accesscontrol.Permission) error { - if roleID == 0 { - return fmt.Errorf("refusing to add permissions to role with ID 0 (it should not exist)") - } - - added, removed := diffPermissionList(new, old) - - for _, p := range added { - _, err := s.Store.CreatePermission(ctx, accesscontrol.CreatePermissionCommand{ - RoleID: roleID, - Permission: p.Permission, - Scope: p.Scope, - }) - if err != nil { - return fmt.Errorf("could not create permission %s (%s): %w", p.Permission, p.Scope, err) - } - } - - for _, p := range removed { - err := s.Store.DeletePermission(ctx, &accesscontrol.DeletePermissionCommand{ - ID: p.ID, - }) - if err != nil { - return fmt.Errorf("could not delete permission %s (%s): %w", p.Permission, p.Scope, err) - } - } - - return nil -} - -func diffPermissionList(new, old []accesscontrol.Permission) (added, removed []accesscontrol.Permission) { - newMap, oldMap := permissionMap(new), permissionMap(old) - - added = []accesscontrol.Permission{} - removed = []accesscontrol.Permission{} - - for _, p := range newMap { - if _, exists := oldMap[permissionTuple{ - Permission: p.Permission, - Scope: p.Scope, - }]; exists { - continue - } - added = append(added, p) - } - - for _, p := range oldMap { - if _, exists := newMap[permissionTuple{ - Permission: p.Permission, - Scope: p.Scope, - }]; exists { - continue - } - removed = append(removed, p) - } - - return added, removed -} - -type permissionTuple struct { - Permission string - Scope string -} - -func permissionMap(l []accesscontrol.Permission) map[permissionTuple]accesscontrol.Permission { - m := make(map[permissionTuple]accesscontrol.Permission, len(l)) - for _, p := range l { - m[permissionTuple{ - Permission: p.Permission, - Scope: p.Scope, - }] = p - } - return m -} diff --git a/pkg/services/accesscontrol/seeder/seeder_test.go b/pkg/services/accesscontrol/seeder/seeder_test.go deleted file mode 100644 index 026d0c617ff..00000000000 --- a/pkg/services/accesscontrol/seeder/seeder_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package seeder - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/database" - "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 { - database.AccessControlStore -} - -func (ac *accessControlStoreTestImpl) AddMigration(mg *migrator.Migrator) { - database.AddAccessControlMigrations(mg) -} - -func setupTestEnv(t testing.TB) *accessControlStoreTestImpl { - t.Helper() - - cfg := setting.NewCfg() - store := overrideDatabaseInRegistry(t, cfg) - sqlStore := sqlstore.InitTestDB(t) - store.SQLStore = sqlStore - - err := store.Init() - require.NoError(t, err) - return &store -} - -func overrideDatabaseInRegistry(t testing.TB, cfg *setting.Cfg) accessControlStoreTestImpl { - t.Helper() - - store := accessControlStoreTestImpl{ - AccessControlStore: database.AccessControlStore{ - SQLStore: nil, - }, - } - - overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) { - if _, ok := descriptor.Instance.(*database.AccessControlStore); ok { - return ®istry.Descriptor{ - Name: "Database", - Instance: &store, - InitPriority: descriptor.InitPriority, - }, true - } - return nil, false - } - - registry.RegisterOverride(overrideServiceFunc) - - return store -} - -func TestSeeder(t *testing.T) { - ac := setupTestEnv(t) - - s := &seeder{ - Store: ac, - log: log.New("accesscontrol-test"), - } - - v1 := accesscontrol.RoleDTO{ - OrgID: 1, - Name: "grafana:tests:fake", - Version: 1, - Permissions: []accesscontrol.Permission{ - { - Permission: "ice_cream:eat", - Scope: "flavor:vanilla", - }, - { - Permission: "ice_cream:eat", - Scope: "flavor:chocolate", - }, - }, - } - v2 := accesscontrol.RoleDTO{ - OrgID: 1, - Name: "grafana:tests:fake", - Version: 2, - Permissions: []accesscontrol.Permission{ - { - Permission: "ice_cream:eat", - Scope: "flavor:vanilla", - }, - { - Permission: "ice_cream:serve", - Scope: "flavor:mint", - }, - { - Permission: "candy.liquorice:eat", - Scope: "", - }, - }, - } - - t.Run("create role", func(t *testing.T) { - id, err := s.createOrUpdateRole( - context.Background(), - v1, - nil, - ) - require.NoError(t, err) - assert.NotZero(t, id) - - p, err := s.Store.GetRole(context.Background(), 1, id) - require.NoError(t, err) - - lookup := permissionMap(p.Permissions) - assert.Contains(t, lookup, permissionTuple{ - Permission: "ice_cream:eat", - Scope: "flavor:vanilla", - }) - assert.Contains(t, lookup, permissionTuple{ - Permission: "ice_cream:eat", - Scope: "flavor:chocolate", - }) - - role := p.Role() - - t.Run("update to same version", func(t *testing.T) { - err := s.seed(context.Background(), 1, []accesscontrol.RoleDTO{v1}, nil) - require.NoError(t, err) - }) - t.Run("update to new role version", func(t *testing.T) { - err := s.seed(context.Background(), 1, []accesscontrol.RoleDTO{v2}, nil) - require.NoError(t, err) - - p, err := s.Store.GetRole(context.Background(), 1, role.ID) - require.NoError(t, err) - assert.Len(t, p.Permissions, len(v2.Permissions)) - - lookup := permissionMap(p.Permissions) - assert.Contains(t, lookup, permissionTuple{ - Permission: "candy.liquorice:eat", - Scope: "", - }) - assert.NotContains(t, lookup, permissionTuple{ - Permission: "ice_cream:eat", - Scope: "flavor:chocolate", - }) - }) - }) -} diff --git a/pkg/services/accesscontrol/testing/common.go b/pkg/services/accesscontrol/testing/common.go deleted file mode 100644 index 0e57fffb692..00000000000 --- a/pkg/services/accesscontrol/testing/common.go +++ /dev/null @@ -1,119 +0,0 @@ -package testing - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/sqlstore" -) - -type RoleTestCase struct { - Name string - UID string - Permissions []PermissionTestCase -} - -type PermissionTestCase struct { - Permission string - Scope string -} - -func CreateRole(t *testing.T, ac accesscontrol.Store, p RoleTestCase) *accesscontrol.RoleDTO { - createRoleCmd := accesscontrol.CreateRoleWithPermissionsCommand{ - OrgID: 1, - UID: p.UID, - Name: p.Name, - Permissions: []accesscontrol.Permission{}, - } - for _, perm := range p.Permissions { - createRoleCmd.Permissions = append(createRoleCmd.Permissions, accesscontrol.Permission{ - Permission: perm.Permission, - Scope: perm.Scope, - }) - } - - res, err := ac.CreateRoleWithPermissions(context.Background(), createRoleCmd) - require.NoError(t, err) - - return res -} - -func CreateUserWithRole(t *testing.T, db *sqlstore.SQLStore, ac accesscontrol.Store, user string, roles []RoleTestCase) { - createUserCmd := models.CreateUserCommand{ - Email: user + "@test.com", - Name: user, - Login: user, - OrgId: 1, - } - - u, err := db.CreateUser(context.Background(), createUserCmd) - require.NoError(t, err) - userId := u.Id - - for _, p := range roles { - createRoleCmd := accesscontrol.CreateRoleCommand{ - OrgID: 1, - Name: p.Name, - } - role, err := ac.CreateRole(context.Background(), createRoleCmd) - require.NoError(t, err) - - for _, perm := range p.Permissions { - permCmd := accesscontrol.CreatePermissionCommand{ - RoleID: role.ID, - Permission: perm.Permission, - Scope: perm.Scope, - } - - _, err := ac.CreatePermission(context.Background(), permCmd) - require.NoError(t, err) - } - - addUserRoleCmd := accesscontrol.AddUserRoleCommand{ - OrgID: 1, - RoleUID: role.UID, - UserID: userId, - } - err = ac.AddUserRole(&addUserRoleCmd) - require.NoError(t, err) - } -} - -func CreateTeamWithRole(t *testing.T, db *sqlstore.SQLStore, ac accesscontrol.Store, teamname string, roles []RoleTestCase) { - email, orgID := teamname+"@test.com", int64(1) - team, err := db.CreateTeam(teamname, email, orgID) - require.NoError(t, err) - teamId := team.Id - - for _, p := range roles { - createRoleCmd := accesscontrol.CreateRoleCommand{ - OrgID: orgID, - Name: p.Name, - } - role, err := ac.CreateRole(context.Background(), createRoleCmd) - require.NoError(t, err) - - for _, perm := range p.Permissions { - permCmd := accesscontrol.CreatePermissionCommand{ - RoleID: role.ID, - Permission: perm.Permission, - Scope: perm.Scope, - } - - _, err := ac.CreatePermission(context.Background(), permCmd) - require.NoError(t, err) - } - - addTeamRoleCmd := accesscontrol.AddTeamRoleCommand{ - OrgID: 1, - RoleUID: role.UID, - TeamID: teamId, - } - err = ac.AddTeamRole(&addTeamRoleCmd) - require.NoError(t, err) - } -} diff --git a/pkg/services/accesscontrol/testing/common_bench.go b/pkg/services/accesscontrol/testing/common_bench.go deleted file mode 100644 index 9023f2bdd16..00000000000 --- a/pkg/services/accesscontrol/testing/common_bench.go +++ /dev/null @@ -1,107 +0,0 @@ -package testing - -import ( - "context" - "fmt" - "math" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/sqlstore" -) - -const ( - usernamePrefix = "user" - teamPrefix = "team" - PermissionsPerRole = 10 - UsersPerTeam = 10 -) - -func GenerateRoles(b *testing.B, db *sqlstore.SQLStore, ac accesscontrol.Store, rolesPerUser, users int) { - numberOfTeams := int(math.Ceil(float64(users) / UsersPerTeam)) - globalUserId := 0 - for i := 0; i < numberOfTeams; i++ { - // Create team - teamName := fmt.Sprintf("%s%v", teamPrefix, i) - teamEmail := fmt.Sprintf("%s@test.com", teamName) - team, err := db.CreateTeam(teamName, teamEmail, 1) - require.NoError(b, err) - teamId := team.Id - - // Create team roles - for j := 0; j < rolesPerUser; j++ { - roleName := fmt.Sprintf("role_%s_%v", teamName, j) - createRoleCmd := accesscontrol.CreateRoleCommand{OrgID: 1, Name: roleName} - role, err := ac.CreateRole(context.Background(), createRoleCmd) - require.NoError(b, err) - - for k := 0; k < PermissionsPerRole; k++ { - permission := fmt.Sprintf("permission_%v", k) - scope := fmt.Sprintf("scope_%v", k) - permCmd := accesscontrol.CreatePermissionCommand{ - RoleID: role.ID, - Permission: permission, - Scope: scope, - } - - _, err := ac.CreatePermission(context.Background(), permCmd) - require.NoError(b, err) - } - - addTeamRoleCmd := accesscontrol.AddTeamRoleCommand{ - OrgID: 1, - RoleUID: role.UID, - TeamID: teamId, - } - err = ac.AddTeamRole(&addTeamRoleCmd) - require.NoError(b, err) - } - - // Create team users - for u := 0; u < UsersPerTeam; u++ { - userName := fmt.Sprintf("%s%v", usernamePrefix, globalUserId) - userEmail := fmt.Sprintf("%s@test.com", userName) - createUserCmd := models.CreateUserCommand{Email: userEmail, Name: userName, Login: userName, OrgId: 1} - - user, err := db.CreateUser(context.Background(), createUserCmd) - require.NoError(b, err) - userId := user.Id - globalUserId++ - - // Create user roles - for j := 0; j < rolesPerUser; j++ { - roleName := fmt.Sprintf("role_%s_%v", userName, j) - createRoleCmd := accesscontrol.CreateRoleCommand{OrgID: 1, Name: roleName} - role, err := ac.CreateRole(context.Background(), createRoleCmd) - require.NoError(b, err) - - for k := 0; k < PermissionsPerRole; k++ { - permission := fmt.Sprintf("permission_%v", k) - scope := fmt.Sprintf("scope_%v", k) - permCmd := accesscontrol.CreatePermissionCommand{ - RoleID: role.ID, - Permission: permission, - Scope: scope, - } - - _, err := ac.CreatePermission(context.Background(), permCmd) - require.NoError(b, err) - } - - addUserRoleCmd := accesscontrol.AddUserRoleCommand{ - OrgID: 1, - RoleUID: role.UID, - UserID: userId, - } - err = ac.AddUserRole(&addUserRoleCmd) - require.NoError(b, err) - } - - err = db.AddTeamMember(userId, 1, teamId, false, 1) - require.NoError(b, err) - } - } -}