mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 19:30:36 -06:00
0f919671e7
* Revert "Serviceaccounts: #48995
Do not display service accounts assigned to team (#48995)"
This reverts commit cbf71fbd7f
.
* fix: test to not include more actions than necessary
* adding service accounts to teams - backend and frontend changes
* also support SA addition through the old team membership endpoints
* fix tests
* tests
* serviceaccounts permission tests
* serviceaccounts permission service tests run
* added back test that was removed by accident
* lint
* refactor: add testoptionsTeams
* fix a bug
* service account picker change
* explicitly set SA managed permissions to false for dash and folders
* lint
* allow team creator to list service accounts
Co-authored-by: IevaVasiljeva <ieva.vasiljeva@grafana.com>
422 lines
12 KiB
Go
422 lines
12 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/annotations"
|
|
)
|
|
|
|
// RoleRegistration stores a role and its assignments to built-in roles
|
|
// (Viewer, Editor, Admin, Grafana Admin)
|
|
type RoleRegistration struct {
|
|
Role RoleDTO
|
|
Grants []string
|
|
}
|
|
|
|
// Role is the model for Role in RBAC.
|
|
type Role struct {
|
|
ID int64 `json:"-" xorm:"pk autoincr 'id'"`
|
|
OrgID int64 `json:"-" xorm:"org_id"`
|
|
Version int64 `json:"version"`
|
|
UID string `xorm:"uid" json:"uid"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
Group string `xorm:"group_name" json:"group"`
|
|
Description string `json:"description"`
|
|
Hidden bool `json:"hidden"`
|
|
|
|
Updated time.Time `json:"updated"`
|
|
Created time.Time `json:"created"`
|
|
}
|
|
|
|
func (r *Role) Global() bool {
|
|
return r.OrgID == GlobalOrgID
|
|
}
|
|
|
|
func (r *Role) IsFixed() bool {
|
|
return strings.HasPrefix(r.Name, FixedRolePrefix)
|
|
}
|
|
|
|
func (r *Role) IsBasic() bool {
|
|
return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix)
|
|
}
|
|
|
|
func (r *Role) GetDisplayName() string {
|
|
if r.IsFixed() && r.DisplayName == "" {
|
|
r.DisplayName = fallbackDisplayName(r.Name)
|
|
}
|
|
return r.DisplayName
|
|
}
|
|
|
|
func (r Role) MarshalJSON() ([]byte, error) {
|
|
type Alias Role
|
|
|
|
r.DisplayName = r.GetDisplayName()
|
|
return json.Marshal(&struct {
|
|
Alias
|
|
Global bool `json:"global" xorm:"-"`
|
|
}{
|
|
Alias: (Alias)(r),
|
|
Global: r.Global(),
|
|
})
|
|
}
|
|
|
|
type RoleDTO struct {
|
|
Version int64 `json:"version"`
|
|
UID string `xorm:"uid" json:"uid"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
Description string `json:"description"`
|
|
Group string `xorm:"group_name" json:"group"`
|
|
Permissions []Permission `json:"permissions,omitempty"`
|
|
Delegatable *bool `json:"delegatable,omitempty"`
|
|
Hidden bool `json:"hidden,omitempty"`
|
|
|
|
ID int64 `json:"-" xorm:"pk autoincr 'id'"`
|
|
OrgID int64 `json:"-" xorm:"org_id"`
|
|
|
|
Updated time.Time `json:"updated"`
|
|
Created time.Time `json:"created"`
|
|
}
|
|
|
|
func (r *RoleDTO) LogID() string {
|
|
var org string
|
|
|
|
if r.Global() {
|
|
org = "Global"
|
|
} else {
|
|
org = fmt.Sprintf("OrgId:%v", r.OrgID)
|
|
}
|
|
|
|
if r.UID != "" {
|
|
return fmt.Sprintf("[%s RoleUID:%v]", org, r.UID)
|
|
}
|
|
return fmt.Sprintf("[%s Role:%v]", org, r.Name)
|
|
}
|
|
|
|
func (r *RoleDTO) Role() Role {
|
|
return Role{
|
|
ID: r.ID,
|
|
OrgID: r.OrgID,
|
|
UID: r.UID,
|
|
Version: r.Version,
|
|
Name: r.Name,
|
|
DisplayName: r.DisplayName,
|
|
Group: r.Group,
|
|
Description: r.Description,
|
|
Hidden: r.Hidden,
|
|
Updated: r.Updated,
|
|
Created: r.Created,
|
|
}
|
|
}
|
|
|
|
func (r *RoleDTO) Global() bool {
|
|
return r.OrgID == GlobalOrgID
|
|
}
|
|
|
|
func (r *RoleDTO) IsFixed() bool {
|
|
return strings.HasPrefix(r.Name, FixedRolePrefix)
|
|
}
|
|
|
|
func (r *RoleDTO) IsBasic() bool {
|
|
return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix)
|
|
}
|
|
|
|
func (r *RoleDTO) GetDisplayName() string {
|
|
if r.IsFixed() && r.DisplayName == "" {
|
|
r.DisplayName = fallbackDisplayName(r.Name)
|
|
}
|
|
if r.DisplayName == "" {
|
|
return r.Name
|
|
}
|
|
return r.DisplayName
|
|
}
|
|
|
|
func (r RoleDTO) MarshalJSON() ([]byte, error) {
|
|
type Alias RoleDTO
|
|
|
|
r.DisplayName = r.GetDisplayName()
|
|
return json.Marshal(&struct {
|
|
Alias
|
|
Global bool `json:"global" xorm:"-"`
|
|
}{
|
|
Alias: (Alias)(r),
|
|
Global: r.Global(),
|
|
})
|
|
}
|
|
|
|
// fallbackDisplayName provides a fallback name for role
|
|
// that can be displayed in the ui for better readability
|
|
// example: currently this would give:
|
|
// fixed:datasources:name -> datasources name
|
|
// datasources:admin -> datasources admin
|
|
func fallbackDisplayName(rName string) string {
|
|
// removing prefix for fixed roles
|
|
rNameWithoutPrefix := strings.Replace(rName, FixedRolePrefix, "", 1)
|
|
return strings.TrimSpace(strings.Replace(rNameWithoutPrefix, ":", " ", -1))
|
|
}
|
|
|
|
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"`
|
|
OrgID int64 `json:"orgId" xorm:"org_id"`
|
|
Role string
|
|
|
|
Updated time.Time
|
|
Created time.Time
|
|
}
|
|
|
|
// Permission is the model for access control permissions.
|
|
type Permission struct {
|
|
ID int64 `json:"-" xorm:"pk autoincr 'id'"`
|
|
RoleID int64 `json:"-" xorm:"role_id"`
|
|
Action string `json:"action"`
|
|
Scope string `json:"scope"`
|
|
|
|
Updated time.Time `json:"updated"`
|
|
Created time.Time `json:"created"`
|
|
}
|
|
|
|
func (p Permission) OSSPermission() Permission {
|
|
return Permission{
|
|
Action: p.Action,
|
|
Scope: p.Scope,
|
|
}
|
|
}
|
|
|
|
type GetUserPermissionsQuery struct {
|
|
OrgID int64 `json:"-"`
|
|
UserID int64 `json:"userId"`
|
|
Roles []string
|
|
Actions []string
|
|
}
|
|
|
|
// ScopeParams holds the parameters used to fill in scope templates
|
|
type ScopeParams struct {
|
|
OrgID int64
|
|
URLParams map[string]string
|
|
}
|
|
|
|
// ResourcePermission is structure that holds all actions that either a team / user / builtin-role
|
|
// can perform against specific resource.
|
|
type ResourcePermission struct {
|
|
ID int64
|
|
RoleName string
|
|
Actions []string
|
|
Scope string
|
|
UserId int64
|
|
UserLogin string
|
|
UserEmail string
|
|
UserIsServiceAccount bool
|
|
TeamId int64
|
|
TeamEmail string
|
|
Team string
|
|
BuiltInRole string
|
|
IsManaged bool
|
|
Created time.Time
|
|
Updated time.Time
|
|
}
|
|
|
|
func (p *ResourcePermission) Contains(targetActions []string) bool {
|
|
if len(p.Actions) < len(targetActions) {
|
|
return false
|
|
}
|
|
|
|
var contain = func(arr []string, s string) bool {
|
|
for _, item := range arr {
|
|
if item == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
for _, a := range targetActions {
|
|
if !contain(p.Actions, a) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
type SetResourcePermissionCommand struct {
|
|
UserID int64
|
|
TeamID int64
|
|
BuiltinRole string
|
|
Permission string
|
|
}
|
|
|
|
const (
|
|
GlobalOrgID = 0
|
|
FixedRolePrefix = "fixed:"
|
|
ManagedRolePrefix = "managed:"
|
|
BasicRolePrefix = "basic:"
|
|
BasicRoleUIDPrefix = "basic_"
|
|
RoleGrafanaAdmin = "Grafana Admin"
|
|
|
|
GeneralFolderUID = "general"
|
|
|
|
// Permission actions
|
|
|
|
ActionAPIKeyRead = "apikeys:read"
|
|
ActionAPIKeyCreate = "apikeys:create"
|
|
ActionAPIKeyDelete = "apikeys:delete"
|
|
|
|
// Users actions
|
|
ActionUsersRead = "users:read"
|
|
ActionUsersWrite = "users:write"
|
|
// We can ignore gosec G101 since this does not contain any credentials.
|
|
// nolint:gosec
|
|
ActionUsersAuthTokenList = "users.authtoken:read"
|
|
// We can ignore gosec G101 since this does not contain any credentials.
|
|
// nolint:gosec
|
|
ActionUsersAuthTokenUpdate = "users.authtoken:write"
|
|
// We can ignore gosec G101 since this does not contain any credentials.
|
|
// nolint:gosec
|
|
ActionUsersPasswordUpdate = "users.password:write"
|
|
ActionUsersDelete = "users:delete"
|
|
ActionUsersCreate = "users:create"
|
|
ActionUsersEnable = "users:enable"
|
|
ActionUsersDisable = "users:disable"
|
|
ActionUsersPermissionsUpdate = "users.permissions:write"
|
|
ActionUsersLogout = "users:logout"
|
|
ActionUsersQuotasList = "users.quotas:read"
|
|
ActionUsersQuotasUpdate = "users.quotas:write"
|
|
|
|
// Org actions
|
|
ActionOrgUsersRead = "org.users:read"
|
|
ActionOrgUsersAdd = "org.users:add"
|
|
ActionOrgUsersRemove = "org.users:remove"
|
|
ActionOrgUsersWrite = "org.users:write"
|
|
|
|
// LDAP actions
|
|
ActionLDAPUsersRead = "ldap.user:read"
|
|
ActionLDAPUsersSync = "ldap.user:sync"
|
|
ActionLDAPStatusRead = "ldap.status:read"
|
|
ActionLDAPConfigReload = "ldap.config:reload"
|
|
|
|
// Server actions
|
|
ActionServerStatsRead = "server.stats:read"
|
|
|
|
// Settings actions
|
|
ActionSettingsRead = "settings:read"
|
|
|
|
// Datasources actions
|
|
ActionDatasourcesExplore = "datasources:explore"
|
|
|
|
// Global Scopes
|
|
ScopeGlobalUsersAll = "global.users:*"
|
|
|
|
// APIKeys scope
|
|
ScopeAPIKeysAll = "apikeys:*"
|
|
|
|
// Users scope
|
|
ScopeUsersAll = "users:*"
|
|
|
|
// Settings scope
|
|
ScopeSettingsAll = "settings:*"
|
|
|
|
// Team related actions
|
|
ActionTeamsCreate = "teams:create"
|
|
ActionTeamsDelete = "teams:delete"
|
|
ActionTeamsRead = "teams:read"
|
|
ActionTeamsWrite = "teams:write"
|
|
ActionTeamsPermissionsRead = "teams.permissions:read"
|
|
ActionTeamsPermissionsWrite = "teams.permissions:write"
|
|
|
|
// Team related scopes
|
|
ScopeTeamsAll = "teams:*"
|
|
|
|
// Annotations related actions
|
|
ActionAnnotationsCreate = "annotations:create"
|
|
ActionAnnotationsDelete = "annotations:delete"
|
|
ActionAnnotationsRead = "annotations:read"
|
|
ActionAnnotationsWrite = "annotations:write"
|
|
|
|
// Alert scopes are divided into two groups. The internal (to Grafana) and the external ones.
|
|
// For the Grafana ones, given we have ACID control we're able to provide better granularity by defining CRUD options.
|
|
// For the external ones, we only have read and write permissions due to the lack of atomicity control of the external system.
|
|
|
|
// Alerting rules actions
|
|
ActionAlertingRuleCreate = "alert.rules:create"
|
|
ActionAlertingRuleRead = "alert.rules:read"
|
|
ActionAlertingRuleUpdate = "alert.rules:write"
|
|
ActionAlertingRuleDelete = "alert.rules:delete"
|
|
|
|
// Alerting instances (+silences) actions
|
|
ActionAlertingInstanceCreate = "alert.instances:create"
|
|
ActionAlertingInstanceUpdate = "alert.instances:write"
|
|
ActionAlertingInstanceRead = "alert.instances:read"
|
|
|
|
// Alerting Notification policies actions
|
|
ActionAlertingNotificationsRead = "alert.notifications:read"
|
|
ActionAlertingNotificationsWrite = "alert.notifications:write"
|
|
|
|
// External alerting rule actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
|
|
ActionAlertingRuleExternalWrite = "alert.rules.external:write"
|
|
ActionAlertingRuleExternalRead = "alert.rules.external:read"
|
|
|
|
// External alerting instances actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
|
|
ActionAlertingInstancesExternalWrite = "alert.instances.external:write"
|
|
ActionAlertingInstancesExternalRead = "alert.instances.external:read"
|
|
|
|
// External alerting notifications actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
|
|
ActionAlertingNotificationsExternalWrite = "alert.notifications.external:write"
|
|
ActionAlertingNotificationsExternalRead = "alert.notifications.external:read"
|
|
|
|
// Alerting provisioning actions
|
|
ActionAlertingProvisioningRead = "alert.provisioning:read"
|
|
ActionAlertingProvisioningWrite = "alert.provisioning:write"
|
|
)
|
|
|
|
var (
|
|
// Team scope
|
|
ScopeTeamsID = Scope("teams", "id", Parameter(":teamId"))
|
|
|
|
// Annotation scopes
|
|
ScopeAnnotationsRoot = "annotations"
|
|
ScopeAnnotationsProvider = NewScopeProvider(ScopeAnnotationsRoot)
|
|
ScopeAnnotationsAll = ScopeAnnotationsProvider.GetResourceAllScope()
|
|
ScopeAnnotationsID = Scope(ScopeAnnotationsRoot, "id", Parameter(":annotationId"))
|
|
ScopeAnnotationsTypeDashboard = ScopeAnnotationsProvider.GetResourceScopeType(annotations.Dashboard.String())
|
|
ScopeAnnotationsTypeOrganization = ScopeAnnotationsProvider.GetResourceScopeType(annotations.Organization.String())
|
|
)
|
|
|
|
func BuiltInRolesWithParents(builtInRoles []string) map[string]struct{} {
|
|
res := map[string]struct{}{}
|
|
|
|
for _, br := range builtInRoles {
|
|
res[br] = struct{}{}
|
|
if br != RoleGrafanaAdmin {
|
|
for _, parent := range models.RoleType(br).Parents() {
|
|
res[string(parent)] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|