The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/accesscontrol/database/database.go

700 lines
18 KiB

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
}