Auth: Add empty role definition (#64694)

* Allow setting role as None

Co-authored-by: gamab <gabi.mabs@gmail.com>

Seeking for places where role.None would be used

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

Adding None role to the frontend

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

unify org role declaration and remove from add permission

fix backend test

fix backend lint

* remove role none from frontend

* Simplify checks

Co-authored-by: Kalle Persson <kalle.persson@grafana.com>

* nits

---------

Co-authored-by: Kalle Persson <kalle.persson@grafana.com>
This commit is contained in:
Jo
2023-07-06 15:40:06 +02:00
committed by GitHub
parent b6fbf307d9
commit d6c468c1c2
12 changed files with 103 additions and 39 deletions

View File

@ -5,7 +5,8 @@ export interface UserOrgDTO {
} }
export enum OrgRole { export enum OrgRole {
Admin = 'Admin', None = 'None',
Editor = 'Editor',
Viewer = 'Viewer', Viewer = 'Viewer',
Editor = 'Editor',
Admin = 'Admin',
} }

View File

@ -192,7 +192,8 @@ func (s *SocialAzureAD) extractRoleAndAdmin(claims *azureClaims) (org.RoleType,
return s.defaultRole(false), false return s.defaultRole(false), false
} }
roleOrder := []org.RoleType{RoleGrafanaAdmin, org.RoleAdmin, org.RoleEditor, org.RoleViewer} roleOrder := []org.RoleType{RoleGrafanaAdmin, org.RoleAdmin, org.RoleEditor,
org.RoleViewer, org.RoleNone}
for _, role := range roleOrder { for _, role := range roleOrder {
if found := hasRole(claims.Roles, role); found { if found := hasRole(claims.Roles, role); found {
if role == RoleGrafanaAdmin { if role == RoleGrafanaAdmin {

View File

@ -20,8 +20,8 @@ import (
type AuthOptions struct { type AuthOptions struct {
ReqGrafanaAdmin bool ReqGrafanaAdmin bool
ReqSignedIn bool
ReqNoAnonynmous bool ReqNoAnonynmous bool
ReqSignedIn bool
} }
func accessForbidden(c *contextmodel.ReqContext) { func accessForbidden(c *contextmodel.ReqContext) {

View File

@ -9,47 +9,58 @@ import (
type RoleType string type RoleType string
const ( const (
RoleNone RoleType = "None"
RoleViewer RoleType = "Viewer" RoleViewer RoleType = "Viewer"
RoleEditor RoleType = "Editor" RoleEditor RoleType = "Editor"
RoleAdmin RoleType = "Admin" RoleAdmin RoleType = "Admin"
) )
var rolePrecedence = map[RoleType]int{
RoleNone: 10,
RoleViewer: 20,
RoleEditor: 30,
RoleAdmin: 40,
}
// Needed to keep stable order
var roleOrder = [...]RoleType{
RoleNone,
RoleViewer,
RoleEditor,
RoleAdmin,
}
func (r RoleType) IsValid() bool { func (r RoleType) IsValid() bool {
return r == RoleViewer || r == RoleAdmin || r == RoleEditor _, ok := rolePrecedence[r]
return ok
} }
func (r RoleType) Includes(other RoleType) bool { func (r RoleType) Includes(other RoleType) bool {
if r == RoleAdmin { return rolePrecedence[r] >= rolePrecedence[other]
return true
}
if r == RoleEditor {
return other != RoleAdmin
}
return r == other
} }
func (r RoleType) Children() []RoleType { func (r RoleType) Children() []RoleType {
switch r { children := make([]RoleType, 0, 3)
case RoleAdmin:
return []RoleType{RoleEditor, RoleViewer} for _, role := range roleOrder {
case RoleEditor: if rolePrecedence[r] > rolePrecedence[role] {
return []RoleType{RoleViewer} children = append(children, role)
default: }
return nil
} }
return children
} }
func (r RoleType) Parents() []RoleType { func (r RoleType) Parents() []RoleType {
switch r { parents := make([]RoleType, 0, 3)
case RoleEditor:
return []RoleType{RoleAdmin} for _, role := range roleOrder {
case RoleViewer: if rolePrecedence[r] < rolePrecedence[role] {
return []RoleType{RoleEditor, RoleAdmin} parents = append(parents, role)
default: }
return nil
} }
return parents
} }
func (r *RoleType) UnmarshalText(data []byte) error { func (r *RoleType) UnmarshalText(data []byte) error {

View File

@ -8,6 +8,7 @@ import (
var ( var (
ErrFixedRolePrefixMissing = errors.New("fixed role should be prefixed with '" + FixedRolePrefix + "'") ErrFixedRolePrefixMissing = errors.New("fixed role should be prefixed with '" + FixedRolePrefix + "'")
ErrInvalidBuiltinRole = errors.New("built-in role is not valid") ErrInvalidBuiltinRole = errors.New("built-in role is not valid")
ErrNoneRoleAssignment = errors.New("none role cannot receive permissions")
ErrInvalidScope = errors.New("invalid scope") ErrInvalidScope = errors.New("invalid scope")
ErrResolverNotFound = errors.New("no resolver found") ErrResolverNotFound = errors.New("no resolver found")
ErrPluginIDRequired = errors.New("plugin ID is required") ErrPluginIDRequired = errors.New("plugin ID is required")

View File

@ -264,6 +264,9 @@ func ValidateFixedRole(role RoleDTO) error {
// ValidateBuiltInRoles errors when a built-in role does not match expected pattern // ValidateBuiltInRoles errors when a built-in role does not match expected pattern
func ValidateBuiltInRoles(builtInRoles []string) error { func ValidateBuiltInRoles(builtInRoles []string) error {
for _, br := range builtInRoles { for _, br := range builtInRoles {
if org.RoleType(br) == org.RoleNone {
return ErrNoneRoleAssignment
}
if !org.RoleType(br).IsValid() && br != RoleGrafanaAdmin { if !org.RoleType(br).IsValid() && br != RoleGrafanaAdmin {
return fmt.Errorf("'%s' %w", br, ErrInvalidBuiltinRole) return fmt.Errorf("'%s' %w", br, ErrInvalidBuiltinRole)
} }
@ -327,6 +330,17 @@ func BuildBasicRoleDefinitions() map[string]*RoleDTO {
Permissions: []Permission{}, Permissions: []Permission{},
Hidden: true, Hidden: true,
}, },
string(org.RoleNone): {
Name: BasicRolePrefix + "none",
UID: BasicRoleUIDPrefix + "none",
OrgID: GlobalOrgID,
Version: 1,
DisplayName: string(org.RoleNone),
Description: "None role",
Group: "Basic",
Permissions: []Permission{},
Hidden: true,
},
RoleGrafanaAdmin: { RoleGrafanaAdmin: {
Name: BasicRolePrefix + "grafana_admin", Name: BasicRolePrefix + "grafana_admin",
UID: BasicRoleUIDPrefix + "grafana_admin", UID: BasicRoleUIDPrefix + "grafana_admin",

View File

@ -47,9 +47,10 @@ type OrgUser struct {
type RoleType = roletype.RoleType type RoleType = roletype.RoleType
const ( const (
RoleViewer RoleType = "Viewer" RoleNone RoleType = roletype.RoleNone
RoleEditor RoleType = "Editor" RoleViewer RoleType = roletype.RoleViewer
RoleAdmin RoleType = "Admin" RoleEditor RoleType = roletype.RoleEditor
RoleAdmin RoleType = roletype.RoleAdmin
) )
type CreateOrgCommand struct { type CreateOrgCommand struct {

View File

@ -77,6 +77,40 @@ func TestStore_CreateServiceAccount(t *testing.T) {
}) })
} }
func TestStore_CreateServiceAccountRoleNone(t *testing.T) {
_, store := setupTestDatabase(t)
orgQuery := &org.CreateOrgCommand{Name: orgimpl.MainOrgName}
orgResult, err := store.orgService.CreateWithMember(context.Background(), orgQuery)
require.NoError(t, err)
serviceAccountName := "new Service Account"
serviceAccountOrgId := orgResult.ID
serviceAccountRole := org.RoleNone
saForm := serviceaccounts.CreateServiceAccountForm{
Name: serviceAccountName,
Role: &serviceAccountRole,
IsDisabled: nil,
}
saDTO, err := store.CreateServiceAccount(context.Background(), serviceAccountOrgId, &saForm)
require.NoError(t, err)
assert.Equal(t, "sa-new-service-account", saDTO.Login)
assert.Equal(t, serviceAccountName, saDTO.Name)
assert.Equal(t, 0, int(saDTO.Tokens))
retrieved, err := store.RetrieveServiceAccount(context.Background(), serviceAccountOrgId, saDTO.Id)
require.NoError(t, err)
assert.Equal(t, "sa-new-service-account", retrieved.Login)
assert.Equal(t, serviceAccountName, retrieved.Name)
assert.Equal(t, serviceAccountOrgId, retrieved.OrgId)
assert.Equal(t, string(serviceAccountRole), retrieved.Role)
retrievedId, err := store.RetrieveServiceAccountIdByName(context.Background(), serviceAccountOrgId, serviceAccountName)
require.NoError(t, err)
assert.Equal(t, saDTO.Id, retrievedId)
assert.Equal(t, saDTO.Role, string(org.RoleNone))
}
func TestStore_DeleteServiceAccount(t *testing.T) { func TestStore_DeleteServiceAccount(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string

View File

@ -17,13 +17,14 @@ import (
type roleType string type roleType string
const ( const (
RoleNone roleType = "None"
RoleViewer roleType = "Viewer" RoleViewer roleType = "Viewer"
RoleEditor roleType = "Editor" RoleEditor roleType = "Editor"
RoleAdmin roleType = "Admin" RoleAdmin roleType = "Admin"
) )
func (r roleType) IsValid() bool { func (r roleType) IsValid() bool {
return r == RoleViewer || r == RoleAdmin || r == RoleEditor return r == RoleViewer || r == RoleAdmin || r == RoleEditor || r == RoleNone
} }
type permissionType int type permissionType int

View File

@ -87,7 +87,9 @@ export const AddPermission = ({
{target === PermissionTarget.BuiltInRole && ( {target === PermissionTarget.BuiltInRole && (
<Select <Select
aria-label={'Built-in role picker'} aria-label={'Built-in role picker'}
options={Object.values(OrgRole).map((r) => ({ value: r, label: r }))} options={Object.values(OrgRole)
.filter((r) => r !== OrgRole.None)
.map((r) => ({ value: r, label: r }))}
onChange={(r) => setBuiltinRole(r.value || '')} onChange={(r) => setBuiltinRole(r.value || '')}
width="auto" width="auto"
/> />

View File

@ -29,7 +29,7 @@ interface RolesCollectionEntry {
roles: Role[]; roles: Role[];
} }
const BasicRoles = Object.values(OrgRole); const BasicRoles = Object.values(OrgRole).filter((r) => r !== OrgRole.None);
const BasicRoleOption: Array<SelectableValue<OrgRole>> = BasicRoles.map((r) => ({ const BasicRoleOption: Array<SelectableValue<OrgRole>> = BasicRoles.map((r) => ({
label: r, label: r,
value: r, value: r,

View File

@ -1,3 +1,5 @@
import { OrgRole } from '@grafana/data';
export enum TeamPermissionLevel { export enum TeamPermissionLevel {
Admin = 4, Admin = 4,
Editor = 2, Editor = 2,
@ -5,11 +7,7 @@ export enum TeamPermissionLevel {
Viewer = 1, Viewer = 1,
} }
export enum OrgRole { export { OrgRole as OrgRole };
Viewer = 'Viewer',
Editor = 'Editor',
Admin = 'Admin',
}
export interface DashboardAclDTO { export interface DashboardAclDTO {
id?: number; id?: number;