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 {
Admin = 'Admin',
Editor = 'Editor',
None = 'None',
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
}
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 {
if found := hasRole(claims.Roles, role); found {
if role == RoleGrafanaAdmin {

View File

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

View File

@ -9,47 +9,58 @@ import (
type RoleType string
const (
RoleNone RoleType = "None"
RoleViewer RoleType = "Viewer"
RoleEditor RoleType = "Editor"
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 {
return r == RoleViewer || r == RoleAdmin || r == RoleEditor
_, ok := rolePrecedence[r]
return ok
}
func (r RoleType) Includes(other RoleType) bool {
if r == RoleAdmin {
return true
}
if r == RoleEditor {
return other != RoleAdmin
}
return r == other
return rolePrecedence[r] >= rolePrecedence[other]
}
func (r RoleType) Children() []RoleType {
switch r {
case RoleAdmin:
return []RoleType{RoleEditor, RoleViewer}
case RoleEditor:
return []RoleType{RoleViewer}
default:
return nil
children := make([]RoleType, 0, 3)
for _, role := range roleOrder {
if rolePrecedence[r] > rolePrecedence[role] {
children = append(children, role)
}
}
return children
}
func (r RoleType) Parents() []RoleType {
switch r {
case RoleEditor:
return []RoleType{RoleAdmin}
case RoleViewer:
return []RoleType{RoleEditor, RoleAdmin}
default:
return nil
parents := make([]RoleType, 0, 3)
for _, role := range roleOrder {
if rolePrecedence[r] < rolePrecedence[role] {
parents = append(parents, role)
}
}
return parents
}
func (r *RoleType) UnmarshalText(data []byte) error {

View File

@ -8,6 +8,7 @@ import (
var (
ErrFixedRolePrefixMissing = errors.New("fixed role should be prefixed with '" + FixedRolePrefix + "'")
ErrInvalidBuiltinRole = errors.New("built-in role is not valid")
ErrNoneRoleAssignment = errors.New("none role cannot receive permissions")
ErrInvalidScope = errors.New("invalid scope")
ErrResolverNotFound = errors.New("no resolver found")
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
func ValidateBuiltInRoles(builtInRoles []string) error {
for _, br := range builtInRoles {
if org.RoleType(br) == org.RoleNone {
return ErrNoneRoleAssignment
}
if !org.RoleType(br).IsValid() && br != RoleGrafanaAdmin {
return fmt.Errorf("'%s' %w", br, ErrInvalidBuiltinRole)
}
@ -327,6 +330,17 @@ func BuildBasicRoleDefinitions() map[string]*RoleDTO {
Permissions: []Permission{},
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: {
Name: BasicRolePrefix + "grafana_admin",
UID: BasicRoleUIDPrefix + "grafana_admin",

View File

@ -47,9 +47,10 @@ type OrgUser struct {
type RoleType = roletype.RoleType
const (
RoleViewer RoleType = "Viewer"
RoleEditor RoleType = "Editor"
RoleAdmin RoleType = "Admin"
RoleNone RoleType = roletype.RoleNone
RoleViewer RoleType = roletype.RoleViewer
RoleEditor RoleType = roletype.RoleEditor
RoleAdmin RoleType = roletype.RoleAdmin
)
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) {
cases := []struct {
desc string

View File

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

View File

@ -87,7 +87,9 @@ export const AddPermission = ({
{target === PermissionTarget.BuiltInRole && (
<Select
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 || '')}
width="auto"
/>

View File

@ -29,7 +29,7 @@ interface RolesCollectionEntry {
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) => ({
label: r,
value: r,

View File

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