diff --git a/packages/grafana-data/src/types/orgs.ts b/packages/grafana-data/src/types/orgs.ts index e4e83a037777..e74f972485b9 100644 --- a/packages/grafana-data/src/types/orgs.ts +++ b/packages/grafana-data/src/types/orgs.ts @@ -5,7 +5,8 @@ export interface UserOrgDTO { } export enum OrgRole { - Admin = 'Admin', - Editor = 'Editor', + None = 'None', Viewer = 'Viewer', + Editor = 'Editor', + Admin = 'Admin', } diff --git a/pkg/login/social/azuread_oauth.go b/pkg/login/social/azuread_oauth.go index 12a7c00787fe..ace3c546abd7 100644 --- a/pkg/login/social/azuread_oauth.go +++ b/pkg/login/social/azuread_oauth.go @@ -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 { diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index 59df79f3c658..b8f461441a9e 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -20,8 +20,8 @@ import ( type AuthOptions struct { ReqGrafanaAdmin bool - ReqSignedIn bool ReqNoAnonynmous bool + ReqSignedIn bool } func accessForbidden(c *contextmodel.ReqContext) { diff --git a/pkg/models/roletype/role_type.go b/pkg/models/roletype/role_type.go index bcb3f20015fe..b119eb58daa7 100644 --- a/pkg/models/roletype/role_type.go +++ b/pkg/models/roletype/role_type.go @@ -9,47 +9,58 @@ 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 +var rolePrecedence = map[RoleType]int{ + RoleNone: 10, + RoleViewer: 20, + RoleEditor: 30, + RoleAdmin: 40, } -func (r RoleType) Includes(other RoleType) bool { - if r == RoleAdmin { - return true - } +// Needed to keep stable order +var roleOrder = [...]RoleType{ + RoleNone, + RoleViewer, + RoleEditor, + RoleAdmin, +} - if r == RoleEditor { - return other != RoleAdmin - } +func (r RoleType) IsValid() bool { + _, ok := rolePrecedence[r] + return ok +} - return r == other +func (r RoleType) Includes(other RoleType) bool { + 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 { diff --git a/pkg/services/accesscontrol/errors.go b/pkg/services/accesscontrol/errors.go index 5616b8583abe..73394a67f51e 100644 --- a/pkg/services/accesscontrol/errors.go +++ b/pkg/services/accesscontrol/errors.go @@ -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") diff --git a/pkg/services/accesscontrol/roles.go b/pkg/services/accesscontrol/roles.go index 03c6e25b69dd..198b45837903 100644 --- a/pkg/services/accesscontrol/roles.go +++ b/pkg/services/accesscontrol/roles.go @@ -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", diff --git a/pkg/services/org/model.go b/pkg/services/org/model.go index ba3379ee4ad2..4f6a327334e8 100644 --- a/pkg/services/org/model.go +++ b/pkg/services/org/model.go @@ -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 { diff --git a/pkg/services/serviceaccounts/database/store_test.go b/pkg/services/serviceaccounts/database/store_test.go index 88c13fe66d95..11bf66caa726 100644 --- a/pkg/services/serviceaccounts/database/store_test.go +++ b/pkg/services/serviceaccounts/database/store_test.go @@ -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 diff --git a/pkg/services/sqlstore/migrations/ualert/permissions.go b/pkg/services/sqlstore/migrations/ualert/permissions.go index 7d4789ae9752..8862a663fbe1 100644 --- a/pkg/services/sqlstore/migrations/ualert/permissions.go +++ b/pkg/services/sqlstore/migrations/ualert/permissions.go @@ -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 diff --git a/public/app/core/components/AccessControl/AddPermission.tsx b/public/app/core/components/AccessControl/AddPermission.tsx index cd08bd05025e..b6e8527f22cf 100644 --- a/public/app/core/components/AccessControl/AddPermission.tsx +++ b/public/app/core/components/AccessControl/AddPermission.tsx @@ -87,7 +87,9 @@ export const AddPermission = ({ {target === PermissionTarget.BuiltInRole && (