diff --git a/docs/sources/developers/kinds/core/accesspolicy/schema-reference.md b/docs/sources/developers/kinds/core/accesspolicy/schema-reference.md new file mode 100644 index 00000000000..2382ac5361c --- /dev/null +++ b/docs/sources/developers/kinds/core/accesspolicy/schema-reference.md @@ -0,0 +1,126 @@ +--- +keywords: + - grafana + - schema +title: AccessPolicy kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## AccessPolicy + +#### Maturity: [merged](../../../maturity/#merged) +#### Version: 0.0 + +Access rules for a scope+role. NOTE there is a unique constraint on role+scope + +| Property | Type | Required | Default | Description | +|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. | +| `spec` | [object](#spec) | **Yes** | | | +| `status` | [object](#status) | **Yes** | | | + +### Metadata + +metadata contains embedded CommonMetadata and can be extended with custom string fields +TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +without external reference as using the CommonMetadata reference breaks thema codegen. + +It extends [_kubeObjectMetadata](#_kubeobjectmetadata). + +| Property | Type | Required | Default | Description | +|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `createdBy` | string | **Yes** | | | +| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata | +| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `updateTimestamp` | string | **Yes** | | | +| `updatedBy` | string | **Yes** | | | +| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | + +### _kubeObjectMetadata + +_kubeObjectMetadata is metadata found in a kubernetes object's metadata field. +It is not exhaustive and only includes fields which may be relevant to a kind's implementation, +As it is also intended to be generic enough to function with any API Server. + +| Property | Type | Required | Default | Description | +|---------------------|-------------------|----------|---------|-------------| +| `creationTimestamp` | string | **Yes** | | | +| `finalizers` | string[] | **Yes** | | | +| `labels` | map[string]string | **Yes** | | | +| `resourceVersion` | string | **Yes** | | | +| `uid` | string | **Yes** | | | +| `deletionTimestamp` | string | No | | | + +### ExtraFields + +extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + +### Spec + +| Property | Type | Required | Default | Description | +|----------|-----------------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------| +| `role` | [RoleRef](#roleref) | **Yes** | | | +| `rules` | [AccessRule](#accessrule)[] | **Yes** | | The set of rules to apply. Note that * is required to modify
access policy rules, and that "none" will reject all actions | +| `scope` | [ResourceRef](#resourceref) | **Yes** | | | + +### AccessRule + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **Yes** | | The kind this rule applies to (dashboars, alert, etc) | +| `verb` | string | **Yes** | | READ, WRITE, CREATE, DELETE, ...
should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete" | +| `target` | string | No | | Specific sub-elements like "alert.rules" or "dashboard.permissions"???? | + +### ResourceRef + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|-------------| +| `kind` | string | **Yes** | | | +| `name` | string | **Yes** | | | + +### RoleRef + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `kind` | string | **Yes** | | Policies can apply to roles, teams, or users
Applying policies to individual users is supported, but discouraged
Possible values are: `Role`, `BuiltinRole`, `Team`, `User`. | +| `name` | string | **Yes** | | | +| `xname` | string | **Yes** | | | + +### Status + +| Property | Type | Required | Default | Description | +|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use | +| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. | + +### AdditionalFields + +additionalFields is reserved for future use + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + +### Status.#OperatorState + +| Property | Type | Required | Default | Description | +|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated | +| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. | +| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format | +| `details` | [object](#details) | No | | details contains any extra information that is operator-specific | + +### Details + +details contains any extra information that is operator-specific + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + + diff --git a/docs/sources/developers/kinds/core/role/schema-reference.md b/docs/sources/developers/kinds/core/role/schema-reference.md new file mode 100644 index 00000000000..d37932fa9af --- /dev/null +++ b/docs/sources/developers/kinds/core/role/schema-reference.md @@ -0,0 +1,105 @@ +--- +keywords: + - grafana + - schema +title: Role kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## Role + +#### Maturity: [merged](../../../maturity/#merged) +#### Version: 0.0 + +Roles represent a set of users+teams that should share similar access + +| Property | Type | Required | Default | Description | +|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. | +| `spec` | [object](#spec) | **Yes** | | | +| `status` | [object](#status) | **Yes** | | | + +### Metadata + +metadata contains embedded CommonMetadata and can be extended with custom string fields +TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +without external reference as using the CommonMetadata reference breaks thema codegen. + +It extends [_kubeObjectMetadata](#_kubeobjectmetadata). + +| Property | Type | Required | Default | Description | +|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `createdBy` | string | **Yes** | | | +| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata | +| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `updateTimestamp` | string | **Yes** | | | +| `updatedBy` | string | **Yes** | | | +| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | + +### _kubeObjectMetadata + +_kubeObjectMetadata is metadata found in a kubernetes object's metadata field. +It is not exhaustive and only includes fields which may be relevant to a kind's implementation, +As it is also intended to be generic enough to function with any API Server. + +| Property | Type | Required | Default | Description | +|---------------------|-------------------|----------|---------|-------------| +| `creationTimestamp` | string | **Yes** | | | +| `finalizers` | string[] | **Yes** | | | +| `labels` | map[string]string | **Yes** | | | +| `resourceVersion` | string | **Yes** | | | +| `uid` | string | **Yes** | | | +| `deletionTimestamp` | string | No | | | + +### ExtraFields + +extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + +### Spec + +| Property | Type | Required | Default | Description | +|---------------|---------|----------|---------|-----------------------------------------------------------| +| `hidden` | boolean | **Yes** | | Do not show this role | +| `name` | string | **Yes** | | The role identifier `managed:builtins:editor:permissions` | +| `description` | string | No | | Role description | +| `displayName` | string | No | | Optional display | +| `groupName` | string | No | | Name of the team. | + +### Status + +| Property | Type | Required | Default | Description | +|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use | +| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. | + +### AdditionalFields + +additionalFields is reserved for future use + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + +### Status.#OperatorState + +| Property | Type | Required | Default | Description | +|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated | +| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. | +| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format | +| `details` | [object](#details) | No | | details contains any extra information that is operator-specific | + +### Details + +details contains any extra information that is operator-specific + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + + diff --git a/docs/sources/developers/kinds/core/rolebinding/schema-reference.md b/docs/sources/developers/kinds/core/rolebinding/schema-reference.md new file mode 100644 index 00000000000..9e99fa9eba6 --- /dev/null +++ b/docs/sources/developers/kinds/core/rolebinding/schema-reference.md @@ -0,0 +1,131 @@ +--- +keywords: + - grafana + - schema +title: RoleBinding kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## RoleBinding + +#### Maturity: [merged](../../../maturity/#merged) +#### Version: 0.0 + +Role bindings links a user|team to a configured role + +| Property | Type | Required | Default | Description | +|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. | +| `spec` | [object](#spec) | **Yes** | | | +| `status` | [object](#status) | **Yes** | | | + +### Metadata + +metadata contains embedded CommonMetadata and can be extended with custom string fields +TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +without external reference as using the CommonMetadata reference breaks thema codegen. + +It extends [_kubeObjectMetadata](#_kubeobjectmetadata). + +| Property | Type | Required | Default | Description | +|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `createdBy` | string | **Yes** | | | +| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata | +| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | +| `updateTimestamp` | string | **Yes** | | | +| `updatedBy` | string | **Yes** | | | +| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | + +### _kubeObjectMetadata + +_kubeObjectMetadata is metadata found in a kubernetes object's metadata field. +It is not exhaustive and only includes fields which may be relevant to a kind's implementation, +As it is also intended to be generic enough to function with any API Server. + +| Property | Type | Required | Default | Description | +|---------------------|-------------------|----------|---------|-------------| +| `creationTimestamp` | string | **Yes** | | | +| `finalizers` | string[] | **Yes** | | | +| `labels` | map[string]string | **Yes** | | | +| `resourceVersion` | string | **Yes** | | | +| `uid` | string | **Yes** | | | +| `deletionTimestamp` | string | No | | | + +### ExtraFields + +extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + +### Spec + +| Property | Type | Required | Default | Description | +|-----------|-------------------------------------------|----------|---------|----------------------------| +| `role` | [object](#role) | **Yes** | | The role we are discussing | +| `subject` | [RoleBindingSubject](#rolebindingsubject) | **Yes** | | | + +### RoleBindingSubject + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|--------------------------------------| +| `kind` | string | **Yes** | | Possible values are: `Team`, `User`. | +| `name` | string | **Yes** | | The team/user identifier name | + +### Role + +The role we are discussing + +| Property | Type | Required | Default | Description | +|----------|-----------------------------------------------------------------------------------------|----------|---------|-------------| +| `object` | Possible types are: [BuiltinRoleRef](#builtinroleref), [CustomRoleRef](#customroleref). | | | + +### BuiltinRoleRef + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|---------------------------------------------------| +| `kind` | string | **Yes** | | Possible values are: `BuiltinRole`. | +| `name` | string | **Yes** | | Possible values are: `viewer`, `editor`, `admin`. | + +### CustomRoleRef + +| Property | Type | Required | Default | Description | +|----------|--------|----------|---------|------------------------------| +| `kind` | string | **Yes** | | Possible values are: `Role`. | +| `name` | string | **Yes** | | | + +### Status + +| Property | Type | Required | Default | Description | +|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use | +| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. | + +### AdditionalFields + +additionalFields is reserved for future use + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + +### Status.#OperatorState + +| Property | Type | Required | Default | Description | +|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated | +| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. | +| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format | +| `details` | [object](#details) | No | | details contains any extra information that is operator-specific | + +### Details + +details contains any extra information that is operator-specific + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| + + diff --git a/kinds/accesspolicy/access_policy_kind.cue b/kinds/accesspolicy/access_policy_kind.cue new file mode 100644 index 00000000000..685efe2ec9f --- /dev/null +++ b/kinds/accesspolicy/access_policy_kind.cue @@ -0,0 +1,51 @@ +package kind + +name: "AccessPolicy" +maturity: "merged" +description: "Access rules for a scope+role. NOTE there is a unique constraint on role+scope" +pluralName: "AccessPolicies" +machineName: "accesspolicy" +pluralMachineName: "accesspolicies" + +lineage: schemas: [{ + version: [0, 0] + schema: { + spec: { + // The scope where these policies should apply + scope: #ResourceRef + + // The role that must apply this policy + role: #RoleRef + + // The set of rules to apply. Note that * is required to modify + // access policy rules, and that "none" will reject all actions + rules: [...#AccessRule] + } @cuetsy(kind="interface") + + #RoleRef: { + // Policies can apply to roles, teams, or users + // Applying policies to individual users is supported, but discouraged + kind: "Role" | "BuiltinRole" | "Team" | "User" + name: string + xname: string // temporary + } @cuetsy(kind="interface") + + #ResourceRef: { + kind: string // explicit resource or folder will cascade + name: string + } @cuetsy(kind="interface") + + #AccessRule: { + // The kind this rule applies to (dashboars, alert, etc) + kind: "*" | string + + // READ, WRITE, CREATE, DELETE, ... + // should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete" + verb: "*" | "none" | string + + // Specific sub-elements like "alert.rules" or "dashboard.permissions"???? + target?: string + } @cuetsy(kind="interface") + } +}, +] diff --git a/kinds/role/role_kind.cue b/kinds/role/role_kind.cue new file mode 100644 index 00000000000..6e80d2abbde --- /dev/null +++ b/kinds/role/role_kind.cue @@ -0,0 +1,25 @@ +package kind + +name: "Role" +maturity: "merged" +description: "Roles represent a set of users+teams that should share similar access" + +lineage: schemas: [{ + version: [0, 0] + schema: { + spec: { + // The role identifier `managed:builtins:editor:permissions` + name: string + // Optional display + displayName?: string + // Name of the team. + groupName?: string + // Role description + description?: string + + // Do not show this role + hidden: bool | false + } @cuetsy(kind="interface") + } +}, +] diff --git a/kinds/rolebinding/role_binding_kind.cue b/kinds/rolebinding/role_binding_kind.cue new file mode 100644 index 00000000000..c76a633a721 --- /dev/null +++ b/kinds/rolebinding/role_binding_kind.cue @@ -0,0 +1,36 @@ +package kind + +name: "RoleBinding" +maturity: "merged" +description: "Role bindings links a user|team to a configured role" + +lineage: schemas: [{ + version: [0, 0] + schema: { + spec: { + // The role we are discussing + role: #BuiltinRoleRef | #CustomRoleRef + + // The team or user that has the specified role + subject: #RoleBindingSubject + } @cuetsy(kind="interface") + + #CustomRoleRef: { + kind: "Role" + name: string + } @cuetsy(kind="interface") + + #BuiltinRoleRef: { + kind: "BuiltinRole" + name: "viewer" | "editor" | "admin" + } @cuetsy(kind="interface") + + #RoleBindingSubject: { + kind: "Team" | "User" + + // The team/user identifier name + name: string + } @cuetsy(kind="interface") + } +}, +] diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts index 8f3939960e5..638cc3daae0 100644 --- a/packages/grafana-schema/src/index.gen.ts +++ b/packages/grafana-schema/src/index.gen.ts @@ -7,6 +7,17 @@ // // Run 'make gen-cue' from repository root to regenerate. +// Raw generated types from AccessPolicy kind. +export type { + AccessPolicy, + RoleRef, + ResourceRef, + AccessRule +} from './raw/accesspolicy/x/accesspolicy_types.gen'; + +// Raw generated enums and default consts from accesspolicy kind. +export { defaultAccessPolicy } from './raw/accesspolicy/x/accesspolicy_types.gen'; + // Raw generated types from Dashboard kind. export type { AnnotationTarget, @@ -129,6 +140,17 @@ export type { // Raw generated types from PublicDashboard kind. export type { PublicDashboard } from './raw/publicdashboard/x/publicdashboard_types.gen'; +// Raw generated types from Role kind. +export type { Role } from './raw/role/x/role_types.gen'; + +// Raw generated types from RoleBinding kind. +export type { + RoleBinding, + CustomRoleRef, + BuiltinRoleRef, + RoleBindingSubject +} from './raw/rolebinding/x/rolebinding_types.gen'; + // Raw generated types from ServiceAccount kind. export type { ServiceAccount, diff --git a/packages/grafana-schema/src/raw/accesspolicy/x/accesspolicy_types.gen.ts b/packages/grafana-schema/src/raw/accesspolicy/x/accesspolicy_types.gen.ts new file mode 100644 index 00000000000..2d27585b992 --- /dev/null +++ b/packages/grafana-schema/src/raw/accesspolicy/x/accesspolicy_types.gen.ts @@ -0,0 +1,60 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// TSResourceJenny +// LatestMajorsOrXJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +export interface RoleRef { + /** + * Policies can apply to roles, teams, or users + * Applying policies to individual users is supported, but discouraged + */ + kind: ('Role' | 'BuiltinRole' | 'Team' | 'User'); + name: string; + xname: string; // temporary +} + +export interface ResourceRef { + kind: string; // explicit resource or folder will cascade + name: string; +} + +export interface AccessRule { + /** + * The kind this rule applies to (dashboars, alert, etc) + */ + kind: ('*' | string); + /** + * Specific sub-elements like "alert.rules" or "dashboard.permissions"???? + */ + target?: string; + /** + * READ, WRITE, CREATE, DELETE, ... + * should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete" + */ + verb: ('*' | 'none' | string); +} + +export interface AccessPolicy { + /** + * The role that must apply this policy + */ + role: RoleRef; + /** + * The set of rules to apply. Note that * is required to modify + * access policy rules, and that "none" will reject all actions + */ + rules: Array; + /** + * The scope where these policies should apply + */ + scope: ResourceRef; +} + +export const defaultAccessPolicy: Partial = { + rules: [], +}; diff --git a/packages/grafana-schema/src/raw/role/x/role_types.gen.ts b/packages/grafana-schema/src/raw/role/x/role_types.gen.ts new file mode 100644 index 00000000000..781665e9250 --- /dev/null +++ b/packages/grafana-schema/src/raw/role/x/role_types.gen.ts @@ -0,0 +1,32 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// TSResourceJenny +// LatestMajorsOrXJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +export interface Role { + /** + * Role description + */ + description?: string; + /** + * Optional display + */ + displayName?: string; + /** + * Name of the team. + */ + groupName?: string; + /** + * Do not show this role + */ + hidden: (boolean | false); + /** + * The role identifier `managed:builtins:editor:permissions` + */ + name: string; +} diff --git a/packages/grafana-schema/src/raw/rolebinding/x/rolebinding_types.gen.ts b/packages/grafana-schema/src/raw/rolebinding/x/rolebinding_types.gen.ts new file mode 100644 index 00000000000..a41535cc5df --- /dev/null +++ b/packages/grafana-schema/src/raw/rolebinding/x/rolebinding_types.gen.ts @@ -0,0 +1,38 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// TSResourceJenny +// LatestMajorsOrXJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +export interface CustomRoleRef { + kind: 'Role'; + name: string; +} + +export interface BuiltinRoleRef { + kind: 'BuiltinRole'; + name: ('viewer' | 'editor' | 'admin'); +} + +export interface RoleBindingSubject { + kind: ('Team' | 'User'); + /** + * The team/user identifier name + */ + name: string; +} + +export interface RoleBinding { + /** + * The role we are discussing + */ + role: (BuiltinRoleRef | CustomRoleRef); + /** + * The team or user that has the specified role + */ + subject: RoleBindingSubject; +} diff --git a/pkg/kinds/accesspolicy/accesspolicy_gen.go b/pkg/kinds/accesspolicy/accesspolicy_gen.go new file mode 100644 index 00000000000..b7cba028b33 --- /dev/null +++ b/pkg/kinds/accesspolicy/accesspolicy_gen.go @@ -0,0 +1,40 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package accesspolicy + +import ( + "github.com/grafana/grafana/pkg/kinds" +) + +// Resource is the kubernetes style representation of AccessPolicy. (TODO be better) +type K8sResource = kinds.GrafanaResource[Spec, Status] + +// NewResource creates a new instance of the resource with a given name (UID) +func NewK8sResource(name string, s *Spec) K8sResource { + return K8sResource{ + Kind: "AccessPolicy", + APIVersion: "v0.0-alpha", + Metadata: kinds.GrafanaResourceMetadata{ + Name: name, + Annotations: make(map[string]string), + Labels: make(map[string]string), + }, + Spec: s, + } +} + + +// Resource is the wire representation of AccessPolicy. +// It currently will soon be merged into the k8s flavor (TODO be better) +type Resource struct { + Metadata Metadata `json:"metadata"` + Spec Spec `json:"spec"` + Status Status `json:"status"` +} diff --git a/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go b/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go new file mode 100644 index 00000000000..200ad2653d0 --- /dev/null +++ b/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go @@ -0,0 +1,79 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CoreKindJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package accesspolicy + +import ( + "github.com/grafana/kindsys" + "github.com/grafana/thema" + "github.com/grafana/thema/vmux" + + "github.com/grafana/grafana/pkg/cuectx" +) + +// rootrel is the relative path from the grafana repository root to the +// directory containing the .cue files in which this kind is defined. Necessary +// for runtime errors related to the definition and/or lineage to provide +// a real path to the correct .cue file. +const rootrel string = "kinds/accesspolicy" + +// TODO standard generated docs +type Kind struct { + kindsys.Core + lin thema.ConvergentLineage[*Resource] + jcodec vmux.Codec + valmux vmux.ValueMux[*Resource] +} + +// type guard - ensure generated Kind type satisfies the kindsys.Core interface +var _ kindsys.Core = &Kind{} + +// TODO standard generated docs +func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { + def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) + if err != nil { + return nil, err + } + + k := &Kind{} + k.Core, err = kindsys.BindCore(rt, def, opts...) + if err != nil { + return nil, err + } + // Get the thema.Schema that the meta says is in the current version (which + // codegen ensures is always the latest) + cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) + tsch, err := thema.BindType(cursch, &Resource{}) + if err != nil { + // Should be unreachable, modulo bugs in the Thema->Go code generator + return nil, err + } + + k.jcodec = vmux.NewJSONCodec("accesspolicy.json") + k.lin = tsch.ConvergentLineage() + k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) + return k, nil +} + +// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) +// to the the AccessPolicy [Resource] type generated from the current schema, v0.0. +func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { + return k.lin +} + +// JSONValueMux is a version multiplexer that maps a []byte containing JSON data +// at any schematized dashboard version to an instance of AccessPolicy [Resource]. +// +// Validation and translation errors emitted from this func will identify the +// input bytes as "dashboard.json". +// +// This is a thin wrapper around Thema's [vmux.ValueMux]. +func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { + return k.valmux(b) +} diff --git a/pkg/kinds/accesspolicy/accesspolicy_metadata_gen.go b/pkg/kinds/accesspolicy/accesspolicy_metadata_gen.go new file mode 100644 index 00000000000..42ed53ded9d --- /dev/null +++ b/pkg/kinds/accesspolicy/accesspolicy_metadata_gen.go @@ -0,0 +1,42 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package accesspolicy + +import ( + "time" +) + +// Metadata defines model for Metadata. +type Metadata struct { + CreatedBy string `json:"createdBy"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + + // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata + ExtraFields map[string]interface{} `json:"extraFields"` + Finalizers []string `json:"finalizers"` + Labels map[string]string `json:"labels"` + ResourceVersion string `json:"resourceVersion"` + Uid string `json:"uid"` + UpdateTimestamp time.Time `json:"updateTimestamp"` + UpdatedBy string `json:"updatedBy"` +} + +// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field. +// It is not exhaustive and only includes fields which may be relevant to a kind's implementation, +// As it is also intended to be generic enough to function with any API Server. +type KubeObjectMetadata struct { + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + Labels map[string]string `json:"labels"` + ResourceVersion string `json:"resourceVersion"` + Uid string `json:"uid"` +} diff --git a/pkg/kinds/accesspolicy/accesspolicy_spec_gen.go b/pkg/kinds/accesspolicy/accesspolicy_spec_gen.go new file mode 100644 index 00000000000..4aaafebe939 --- /dev/null +++ b/pkg/kinds/accesspolicy/accesspolicy_spec_gen.go @@ -0,0 +1,60 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package accesspolicy + +// Defines values for RoleRefKind. +const ( + RoleRefKindBuiltinRole RoleRefKind = "BuiltinRole" + RoleRefKindRole RoleRefKind = "Role" + RoleRefKindTeam RoleRefKind = "Team" + RoleRefKindUser RoleRefKind = "User" +) + +// AccessRule defines model for AccessRule. +type AccessRule struct { + // The kind this rule applies to (dashboars, alert, etc) + Kind string `json:"kind"` + + // Specific sub-elements like "alert.rules" or "dashboard.permissions"???? + Target *string `json:"target,omitempty"` + + // READ, WRITE, CREATE, DELETE, ... + // should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete" + Verb string `json:"verb"` +} + +// ResourceRef defines model for ResourceRef. +type ResourceRef struct { + Kind string `json:"kind"` + Name string `json:"name"` +} + +// RoleRef defines model for RoleRef. +type RoleRef struct { + // Policies can apply to roles, teams, or users + // Applying policies to individual users is supported, but discouraged + Kind RoleRefKind `json:"kind"` + Name string `json:"name"` + Xname string `json:"xname"` +} + +// Policies can apply to roles, teams, or users +// Applying policies to individual users is supported, but discouraged +type RoleRefKind string + +// Spec defines model for Spec. +type Spec struct { + Role RoleRef `json:"role"` + + // The set of rules to apply. Note that * is required to modify + // access policy rules, and that "none" will reject all actions + Rules []AccessRule `json:"rules"` + Scope ResourceRef `json:"scope"` +} diff --git a/pkg/kinds/accesspolicy/accesspolicy_status_gen.go b/pkg/kinds/accesspolicy/accesspolicy_status_gen.go new file mode 100644 index 00000000000..90860518a49 --- /dev/null +++ b/pkg/kinds/accesspolicy/accesspolicy_status_gen.go @@ -0,0 +1,74 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package accesspolicy + +// Defines values for OperatorStateState. +const ( + OperatorStateStateFailed OperatorStateState = "failed" + OperatorStateStateInProgress OperatorStateState = "in_progress" + OperatorStateStateSuccess OperatorStateState = "success" +) + +// Defines values for StatusOperatorStateState. +const ( + StatusOperatorStateStateFailed StatusOperatorStateState = "failed" + StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress" + StatusOperatorStateStateSuccess StatusOperatorStateState = "success" +) + +// OperatorState defines model for OperatorState. +type OperatorState struct { + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` + + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State OperatorStateState `json:"state"` +} + +// OperatorStateState state describes the state of the lastEvaluation. +// It is limited to three possible states for machine evaluation. +type OperatorStateState string + +// Status defines model for Status. +type Status struct { + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` + + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"` +} + +// StatusOperatorState defines model for status.#OperatorState. +type StatusOperatorState struct { + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` + + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State StatusOperatorStateState `json:"state"` +} + +// StatusOperatorStateState state describes the state of the lastEvaluation. +// It is limited to three possible states for machine evaluation. +type StatusOperatorStateState string diff --git a/pkg/kinds/accesspolicy/utils.go b/pkg/kinds/accesspolicy/utils.go new file mode 100644 index 00000000000..c043af90761 --- /dev/null +++ b/pkg/kinds/accesspolicy/utils.go @@ -0,0 +1,99 @@ +package accesspolicy + +import ( + "sort" + + "github.com/grafana/grafana/pkg/util" +) + +const PermissionsTarget = "permissions" +const AllowAll = "*" +const AllowNone = "none" + +func ReduceRules(rules []AccessRule) []AccessRule { + type verbs struct { + Verb map[string][]string + Terminal string + } + + kinds := make(map[string]*verbs) + for _, rule := range rules { + if rule.Kind == "" || rule.Verb == "" { + continue // invalid + } + + // flip write permission to * + if rule.Target != nil && *rule.Target == PermissionsTarget { + if rule.Verb == "write" { + rule.Verb = AllowAll + } + } + kind, ok := kinds[rule.Kind] + if !ok { + kind = &verbs{ + Verb: make(map[string][]string), + } + kinds[rule.Kind] = kind + } + + terminal := rule.Verb == AllowAll || rule.Verb == AllowNone + if terminal { + if rule.Kind == AllowAll { + return []AccessRule{rule} + } + kind.Terminal = rule.Verb + } else if kind.Terminal == "" { + targets, ok := kind.Verb[rule.Verb] + if !ok { + targets = []string{} + } + if rule.Target != nil && !contains(targets, *rule.Target) { + targets = append(targets, *rule.Target) + sort.Strings(targets) + } + kind.Verb[rule.Verb] = targets + } + } + + results := make([]AccessRule, 0) + for _, kind := range getSortedKeys(kinds) { + verb := kinds[kind] + if verb.Terminal != "" { + results = append(results, AccessRule{Kind: kind, Verb: verb.Terminal}) + } else { + for _, v := range getSortedKeys(verb.Verb) { + targets := verb.Verb[v] + if len(targets) == 0 { + results = append(results, AccessRule{Kind: kind, Verb: v}) + } else { + for _, t := range targets { + results = append(results, AccessRule{ + Kind: kind, + Verb: v, + Target: util.Pointer(t), + }) + } + } + } + } + } + return results +} + +func getSortedKeys[T any](vals map[string]T) []string { + keys := make([]string, 0, len(vals)) + for k := range vals { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +func contains[T comparable](s []T, e T) bool { + for _, v := range s { + if v == e { + return true + } + } + return false +} diff --git a/pkg/kinds/accesspolicy/utils_test.go b/pkg/kinds/accesspolicy/utils_test.go new file mode 100644 index 00000000000..314a8229346 --- /dev/null +++ b/pkg/kinds/accesspolicy/utils_test.go @@ -0,0 +1,68 @@ +package accesspolicy + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/grafana/grafana/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestRuleReducer(t *testing.T) { + t.Run("Check write pointer becomes star", func(t *testing.T) { + rules := ReduceRules([]AccessRule{ + {Kind: "dashboard", Verb: "read"}, + {Kind: "dashboard", Verb: "write", Target: util.Pointer("permissions")}, + {Kind: "dashboard", Verb: "read"}, + }) + require.Len(t, rules, 1) + require.Equal(t, rules[0], AccessRule{Kind: "dashboard", Verb: "*"}) + }) + + t.Run("Check sort", func(t *testing.T) { + rules := ReduceRules([]AccessRule{ + {Kind: "x", Verb: "b"}, + {Kind: "x", Verb: "a"}, + {Kind: "x", Verb: "a"}, // ignore duplicates + {Kind: "x", Verb: "a"}, // ignore duplicates + {Kind: "x", Verb: "a"}, // ignore duplicates + {Kind: "x", Verb: "a"}, + {Kind: "z", Verb: "b"}, + {Kind: "AAA", Verb: ""}, // ignore + {Kind: "", Verb: "XXX"}, // ignore + {Kind: "z", Verb: "a"}, + {Kind: "y", Verb: "b"}, + {Kind: "y", Verb: "a"}, + }) + out, err := json.MarshalIndent(rules, "", " ") + fmt.Printf("%s", string(out)) + require.NoError(t, err) + require.JSONEq(t, `[ + { + "kind": "x", + "verb": "a" + }, + { + "kind": "x", + "verb": "b" + }, + { + "kind": "y", + "verb": "a" + }, + { + "kind": "y", + "verb": "b" + }, + { + "kind": "z", + "verb": "a" + }, + { + "kind": "z", + "verb": "b" + } + ]`, string(out)) + }) +} diff --git a/pkg/kinds/role/role_gen.go b/pkg/kinds/role/role_gen.go new file mode 100644 index 00000000000..20d99cabb1c --- /dev/null +++ b/pkg/kinds/role/role_gen.go @@ -0,0 +1,40 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package role + +import ( + "github.com/grafana/grafana/pkg/kinds" +) + +// Resource is the kubernetes style representation of Role. (TODO be better) +type K8sResource = kinds.GrafanaResource[Spec, Status] + +// NewResource creates a new instance of the resource with a given name (UID) +func NewK8sResource(name string, s *Spec) K8sResource { + return K8sResource{ + Kind: "Role", + APIVersion: "v0.0-alpha", + Metadata: kinds.GrafanaResourceMetadata{ + Name: name, + Annotations: make(map[string]string), + Labels: make(map[string]string), + }, + Spec: s, + } +} + + +// Resource is the wire representation of Role. +// It currently will soon be merged into the k8s flavor (TODO be better) +type Resource struct { + Metadata Metadata `json:"metadata"` + Spec Spec `json:"spec"` + Status Status `json:"status"` +} diff --git a/pkg/kinds/role/role_kind_gen.go b/pkg/kinds/role/role_kind_gen.go new file mode 100644 index 00000000000..cc297cb0807 --- /dev/null +++ b/pkg/kinds/role/role_kind_gen.go @@ -0,0 +1,79 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CoreKindJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package role + +import ( + "github.com/grafana/kindsys" + "github.com/grafana/thema" + "github.com/grafana/thema/vmux" + + "github.com/grafana/grafana/pkg/cuectx" +) + +// rootrel is the relative path from the grafana repository root to the +// directory containing the .cue files in which this kind is defined. Necessary +// for runtime errors related to the definition and/or lineage to provide +// a real path to the correct .cue file. +const rootrel string = "kinds/role" + +// TODO standard generated docs +type Kind struct { + kindsys.Core + lin thema.ConvergentLineage[*Resource] + jcodec vmux.Codec + valmux vmux.ValueMux[*Resource] +} + +// type guard - ensure generated Kind type satisfies the kindsys.Core interface +var _ kindsys.Core = &Kind{} + +// TODO standard generated docs +func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { + def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) + if err != nil { + return nil, err + } + + k := &Kind{} + k.Core, err = kindsys.BindCore(rt, def, opts...) + if err != nil { + return nil, err + } + // Get the thema.Schema that the meta says is in the current version (which + // codegen ensures is always the latest) + cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) + tsch, err := thema.BindType(cursch, &Resource{}) + if err != nil { + // Should be unreachable, modulo bugs in the Thema->Go code generator + return nil, err + } + + k.jcodec = vmux.NewJSONCodec("role.json") + k.lin = tsch.ConvergentLineage() + k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) + return k, nil +} + +// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) +// to the the Role [Resource] type generated from the current schema, v0.0. +func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { + return k.lin +} + +// JSONValueMux is a version multiplexer that maps a []byte containing JSON data +// at any schematized dashboard version to an instance of Role [Resource]. +// +// Validation and translation errors emitted from this func will identify the +// input bytes as "dashboard.json". +// +// This is a thin wrapper around Thema's [vmux.ValueMux]. +func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { + return k.valmux(b) +} diff --git a/pkg/kinds/role/role_metadata_gen.go b/pkg/kinds/role/role_metadata_gen.go new file mode 100644 index 00000000000..b75ecfdea96 --- /dev/null +++ b/pkg/kinds/role/role_metadata_gen.go @@ -0,0 +1,42 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package role + +import ( + "time" +) + +// Metadata defines model for Metadata. +type Metadata struct { + CreatedBy string `json:"createdBy"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + + // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata + ExtraFields map[string]interface{} `json:"extraFields"` + Finalizers []string `json:"finalizers"` + Labels map[string]string `json:"labels"` + ResourceVersion string `json:"resourceVersion"` + Uid string `json:"uid"` + UpdateTimestamp time.Time `json:"updateTimestamp"` + UpdatedBy string `json:"updatedBy"` +} + +// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field. +// It is not exhaustive and only includes fields which may be relevant to a kind's implementation, +// As it is also intended to be generic enough to function with any API Server. +type KubeObjectMetadata struct { + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + Labels map[string]string `json:"labels"` + ResourceVersion string `json:"resourceVersion"` + Uid string `json:"uid"` +} diff --git a/pkg/kinds/role/role_spec_gen.go b/pkg/kinds/role/role_spec_gen.go new file mode 100644 index 00000000000..ca4c2329268 --- /dev/null +++ b/pkg/kinds/role/role_spec_gen.go @@ -0,0 +1,28 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package role + +// Spec defines model for Spec. +type Spec struct { + // Role description + Description *string `json:"description,omitempty"` + + // Optional display + DisplayName *string `json:"displayName,omitempty"` + + // Name of the team. + GroupName *string `json:"groupName,omitempty"` + + // Do not show this role + Hidden bool `json:"hidden"` + + // The role identifier `managed:builtins:editor:permissions` + Name string `json:"name"` +} diff --git a/pkg/kinds/role/role_status_gen.go b/pkg/kinds/role/role_status_gen.go new file mode 100644 index 00000000000..8ffe1202dd5 --- /dev/null +++ b/pkg/kinds/role/role_status_gen.go @@ -0,0 +1,74 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package role + +// Defines values for OperatorStateState. +const ( + OperatorStateStateFailed OperatorStateState = "failed" + OperatorStateStateInProgress OperatorStateState = "in_progress" + OperatorStateStateSuccess OperatorStateState = "success" +) + +// Defines values for StatusOperatorStateState. +const ( + StatusOperatorStateStateFailed StatusOperatorStateState = "failed" + StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress" + StatusOperatorStateStateSuccess StatusOperatorStateState = "success" +) + +// OperatorState defines model for OperatorState. +type OperatorState struct { + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` + + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State OperatorStateState `json:"state"` +} + +// OperatorStateState state describes the state of the lastEvaluation. +// It is limited to three possible states for machine evaluation. +type OperatorStateState string + +// Status defines model for Status. +type Status struct { + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` + + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"` +} + +// StatusOperatorState defines model for status.#OperatorState. +type StatusOperatorState struct { + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` + + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State StatusOperatorStateState `json:"state"` +} + +// StatusOperatorStateState state describes the state of the lastEvaluation. +// It is limited to three possible states for machine evaluation. +type StatusOperatorStateState string diff --git a/pkg/kinds/rolebinding/rolebinding_gen.go b/pkg/kinds/rolebinding/rolebinding_gen.go new file mode 100644 index 00000000000..ae30f61ade3 --- /dev/null +++ b/pkg/kinds/rolebinding/rolebinding_gen.go @@ -0,0 +1,40 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package rolebinding + +import ( + "github.com/grafana/grafana/pkg/kinds" +) + +// Resource is the kubernetes style representation of RoleBinding. (TODO be better) +type K8sResource = kinds.GrafanaResource[Spec, Status] + +// NewResource creates a new instance of the resource with a given name (UID) +func NewK8sResource(name string, s *Spec) K8sResource { + return K8sResource{ + Kind: "RoleBinding", + APIVersion: "v0.0-alpha", + Metadata: kinds.GrafanaResourceMetadata{ + Name: name, + Annotations: make(map[string]string), + Labels: make(map[string]string), + }, + Spec: s, + } +} + + +// Resource is the wire representation of RoleBinding. +// It currently will soon be merged into the k8s flavor (TODO be better) +type Resource struct { + Metadata Metadata `json:"metadata"` + Spec Spec `json:"spec"` + Status Status `json:"status"` +} diff --git a/pkg/kinds/rolebinding/rolebinding_kind_gen.go b/pkg/kinds/rolebinding/rolebinding_kind_gen.go new file mode 100644 index 00000000000..603b1d96747 --- /dev/null +++ b/pkg/kinds/rolebinding/rolebinding_kind_gen.go @@ -0,0 +1,79 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CoreKindJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package rolebinding + +import ( + "github.com/grafana/kindsys" + "github.com/grafana/thema" + "github.com/grafana/thema/vmux" + + "github.com/grafana/grafana/pkg/cuectx" +) + +// rootrel is the relative path from the grafana repository root to the +// directory containing the .cue files in which this kind is defined. Necessary +// for runtime errors related to the definition and/or lineage to provide +// a real path to the correct .cue file. +const rootrel string = "kinds/rolebinding" + +// TODO standard generated docs +type Kind struct { + kindsys.Core + lin thema.ConvergentLineage[*Resource] + jcodec vmux.Codec + valmux vmux.ValueMux[*Resource] +} + +// type guard - ensure generated Kind type satisfies the kindsys.Core interface +var _ kindsys.Core = &Kind{} + +// TODO standard generated docs +func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { + def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) + if err != nil { + return nil, err + } + + k := &Kind{} + k.Core, err = kindsys.BindCore(rt, def, opts...) + if err != nil { + return nil, err + } + // Get the thema.Schema that the meta says is in the current version (which + // codegen ensures is always the latest) + cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) + tsch, err := thema.BindType(cursch, &Resource{}) + if err != nil { + // Should be unreachable, modulo bugs in the Thema->Go code generator + return nil, err + } + + k.jcodec = vmux.NewJSONCodec("rolebinding.json") + k.lin = tsch.ConvergentLineage() + k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) + return k, nil +} + +// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) +// to the the RoleBinding [Resource] type generated from the current schema, v0.0. +func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { + return k.lin +} + +// JSONValueMux is a version multiplexer that maps a []byte containing JSON data +// at any schematized dashboard version to an instance of RoleBinding [Resource]. +// +// Validation and translation errors emitted from this func will identify the +// input bytes as "dashboard.json". +// +// This is a thin wrapper around Thema's [vmux.ValueMux]. +func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { + return k.valmux(b) +} diff --git a/pkg/kinds/rolebinding/rolebinding_metadata_gen.go b/pkg/kinds/rolebinding/rolebinding_metadata_gen.go new file mode 100644 index 00000000000..fcdc39659c5 --- /dev/null +++ b/pkg/kinds/rolebinding/rolebinding_metadata_gen.go @@ -0,0 +1,42 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package rolebinding + +import ( + "time" +) + +// Metadata defines model for Metadata. +type Metadata struct { + CreatedBy string `json:"createdBy"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + + // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata + ExtraFields map[string]interface{} `json:"extraFields"` + Finalizers []string `json:"finalizers"` + Labels map[string]string `json:"labels"` + ResourceVersion string `json:"resourceVersion"` + Uid string `json:"uid"` + UpdateTimestamp time.Time `json:"updateTimestamp"` + UpdatedBy string `json:"updatedBy"` +} + +// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field. +// It is not exhaustive and only includes fields which may be relevant to a kind's implementation, +// As it is also intended to be generic enough to function with any API Server. +type KubeObjectMetadata struct { + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + Labels map[string]string `json:"labels"` + ResourceVersion string `json:"resourceVersion"` + Uid string `json:"uid"` +} diff --git a/pkg/kinds/rolebinding/rolebinding_spec_gen.go b/pkg/kinds/rolebinding/rolebinding_spec_gen.go new file mode 100644 index 00000000000..75155bd7e50 --- /dev/null +++ b/pkg/kinds/rolebinding/rolebinding_spec_gen.go @@ -0,0 +1,72 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package rolebinding + +// Defines values for BuiltinRoleRefKind. +const ( + BuiltinRoleRefKindBuiltinRole BuiltinRoleRefKind = "BuiltinRole" +) + +// Defines values for BuiltinRoleRefName. +const ( + BuiltinRoleRefNameAdmin BuiltinRoleRefName = "admin" + BuiltinRoleRefNameEditor BuiltinRoleRefName = "editor" + BuiltinRoleRefNameViewer BuiltinRoleRefName = "viewer" +) + +// Defines values for CustomRoleRefKind. +const ( + CustomRoleRefKindRole CustomRoleRefKind = "Role" +) + +// Defines values for SubjectKind. +const ( + SubjectKindTeam SubjectKind = "Team" + SubjectKindUser SubjectKind = "User" +) + +// BuiltinRoleRef defines model for BuiltinRoleRef. +type BuiltinRoleRef struct { + Kind BuiltinRoleRefKind `json:"kind"` + Name BuiltinRoleRefName `json:"name"` +} + +// BuiltinRoleRefKind defines model for BuiltinRoleRef.Kind. +type BuiltinRoleRefKind string + +// BuiltinRoleRefName defines model for BuiltinRoleRef.Name. +type BuiltinRoleRefName string + +// CustomRoleRef defines model for CustomRoleRef. +type CustomRoleRef struct { + Kind CustomRoleRefKind `json:"kind"` + Name string `json:"name"` +} + +// CustomRoleRefKind defines model for CustomRoleRef.Kind. +type CustomRoleRefKind string + +// Subject defines model for Subject. +type Subject struct { + Kind SubjectKind `json:"kind"` + + // The team/user identifier name + Name string `json:"name"` +} + +// SubjectKind defines model for Subject.Kind. +type SubjectKind string + +// Spec defines model for Spec. +type Spec struct { + // The role we are discussing + Role interface{} `json:"role"` + Subject Subject `json:"subject"` +} diff --git a/pkg/kinds/rolebinding/rolebinding_status_gen.go b/pkg/kinds/rolebinding/rolebinding_status_gen.go new file mode 100644 index 00000000000..b63f86e6883 --- /dev/null +++ b/pkg/kinds/rolebinding/rolebinding_status_gen.go @@ -0,0 +1,74 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// GoResourceTypes +// +// Run 'make gen-cue' from repository root to regenerate. + +package rolebinding + +// Defines values for OperatorStateState. +const ( + OperatorStateStateFailed OperatorStateState = "failed" + OperatorStateStateInProgress OperatorStateState = "in_progress" + OperatorStateStateSuccess OperatorStateState = "success" +) + +// Defines values for StatusOperatorStateState. +const ( + StatusOperatorStateStateFailed StatusOperatorStateState = "failed" + StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress" + StatusOperatorStateStateSuccess StatusOperatorStateState = "success" +) + +// OperatorState defines model for OperatorState. +type OperatorState struct { + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` + + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State OperatorStateState `json:"state"` +} + +// OperatorStateState state describes the state of the lastEvaluation. +// It is limited to three possible states for machine evaluation. +type OperatorStateState string + +// Status defines model for Status. +type Status struct { + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` + + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"` +} + +// StatusOperatorState defines model for status.#OperatorState. +type StatusOperatorState struct { + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` + + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State StatusOperatorStateState `json:"state"` +} + +// StatusOperatorStateState state describes the state of the lastEvaluation. +// It is limited to three possible states for machine evaluation. +type StatusOperatorStateState string diff --git a/pkg/registry/corekind/base_gen.go b/pkg/registry/corekind/base_gen.go index 95fc4a49b75..f8feea1a13a 100644 --- a/pkg/registry/corekind/base_gen.go +++ b/pkg/registry/corekind/base_gen.go @@ -12,12 +12,15 @@ package corekind import ( "fmt" + "github.com/grafana/grafana/pkg/kinds/accesspolicy" "github.com/grafana/grafana/pkg/kinds/dashboard" "github.com/grafana/grafana/pkg/kinds/folder" "github.com/grafana/grafana/pkg/kinds/librarypanel" "github.com/grafana/grafana/pkg/kinds/playlist" "github.com/grafana/grafana/pkg/kinds/preferences" "github.com/grafana/grafana/pkg/kinds/publicdashboard" + "github.com/grafana/grafana/pkg/kinds/role" + "github.com/grafana/grafana/pkg/kinds/rolebinding" "github.com/grafana/grafana/pkg/kinds/serviceaccount" "github.com/grafana/grafana/pkg/kinds/team" "github.com/grafana/kindsys" @@ -41,28 +44,39 @@ import ( // kind-schematized type. type Base struct { all []kindsys.Core + accesspolicy *accesspolicy.Kind dashboard *dashboard.Kind folder *folder.Kind librarypanel *librarypanel.Kind playlist *playlist.Kind preferences *preferences.Kind publicdashboard *publicdashboard.Kind + role *role.Kind + rolebinding *rolebinding.Kind serviceaccount *serviceaccount.Kind team *team.Kind } // type guards var ( + _ kindsys.Core = &accesspolicy.Kind{} _ kindsys.Core = &dashboard.Kind{} _ kindsys.Core = &folder.Kind{} _ kindsys.Core = &librarypanel.Kind{} _ kindsys.Core = &playlist.Kind{} _ kindsys.Core = &preferences.Kind{} _ kindsys.Core = &publicdashboard.Kind{} + _ kindsys.Core = &role.Kind{} + _ kindsys.Core = &rolebinding.Kind{} _ kindsys.Core = &serviceaccount.Kind{} _ kindsys.Core = &team.Kind{} ) +// AccessPolicy returns the [kindsys.Interface] implementation for the accesspolicy kind. +func (b *Base) AccessPolicy() *accesspolicy.Kind { + return b.accesspolicy +} + // Dashboard returns the [kindsys.Interface] implementation for the dashboard kind. func (b *Base) Dashboard() *dashboard.Kind { return b.dashboard @@ -93,6 +107,16 @@ func (b *Base) PublicDashboard() *publicdashboard.Kind { return b.publicdashboard } +// Role returns the [kindsys.Interface] implementation for the role kind. +func (b *Base) Role() *role.Kind { + return b.role +} + +// RoleBinding returns the [kindsys.Interface] implementation for the rolebinding kind. +func (b *Base) RoleBinding() *rolebinding.Kind { + return b.rolebinding +} + // ServiceAccount returns the [kindsys.Interface] implementation for the serviceaccount kind. func (b *Base) ServiceAccount() *serviceaccount.Kind { return b.serviceaccount @@ -107,6 +131,12 @@ func doNewBase(rt *thema.Runtime) *Base { var err error reg := &Base{} + reg.accesspolicy, err = accesspolicy.NewKind(rt) + if err != nil { + panic(fmt.Sprintf("error while initializing the accesspolicy Kind: %s", err)) + } + reg.all = append(reg.all, reg.accesspolicy) + reg.dashboard, err = dashboard.NewKind(rt) if err != nil { panic(fmt.Sprintf("error while initializing the dashboard Kind: %s", err)) @@ -143,6 +173,18 @@ func doNewBase(rt *thema.Runtime) *Base { } reg.all = append(reg.all, reg.publicdashboard) + reg.role, err = role.NewKind(rt) + if err != nil { + panic(fmt.Sprintf("error while initializing the role Kind: %s", err)) + } + reg.all = append(reg.all, reg.role) + + reg.rolebinding, err = rolebinding.NewKind(rt) + if err != nil { + panic(fmt.Sprintf("error while initializing the rolebinding Kind: %s", err)) + } + reg.all = append(reg.all, reg.rolebinding) + reg.serviceaccount, err = serviceaccount.NewKind(rt) if err != nil { panic(fmt.Sprintf("error while initializing the serviceaccount Kind: %s", err)) diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 29a693a1fdd..45a81415a0d 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -3,6 +3,7 @@ package database import ( "context" "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -32,6 +33,7 @@ type getUserPermissionsTestCase struct { teamPermissions []string builtinPermissions []string expected int + policyCount int } func TestAccessControlStore_GetUserPermissions(t *testing.T) { @@ -44,6 +46,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { teamPermissions: []string{"100", "2"}, builtinPermissions: []string{"5", "6"}, expected: 7, + policyCount: 7, }, { desc: "Should not get admin roles", @@ -53,6 +56,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { teamPermissions: []string{"100", "2"}, builtinPermissions: []string{"5", "6"}, expected: 5, + policyCount: 7, }, { desc: "Should work without org role", @@ -62,6 +66,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { teamPermissions: []string{"100", "2"}, builtinPermissions: []string{"5", "6"}, expected: 5, + policyCount: 7, }, { desc: "should only get br permissions for anonymous user", @@ -72,6 +77,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { teamPermissions: []string{"100", "2"}, builtinPermissions: []string{"5", "6"}, expected: 2, + policyCount: 7, }, } for _, tt := range tests { @@ -132,6 +138,17 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { require.NoError(t, err) assert.Len(t, permissions, tt.expected) + + policies, err := GetAccessPolicies(context.Background(), user.OrgID, store.sql.GetSqlxSession(), + func(ctx context.Context, orgID int64, scope string) ([]string, error) { + return strings.Split(scope, ":"), nil + }) + require.NoError(t, err) + assert.Len(t, policies, tt.policyCount) + + for idx, p := range policies { + fmt.Printf("POLICIES[%d] %+v\n", idx, p.Spec) + } }) } } diff --git a/pkg/services/accesscontrol/database/policies.go b/pkg/services/accesscontrol/database/policies.go new file mode 100644 index 00000000000..b31329886e5 --- /dev/null +++ b/pkg/services/accesscontrol/database/policies.go @@ -0,0 +1,142 @@ +package database + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/grafana/grafana/pkg/kinds/accesspolicy" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/sqlstore/session" + "github.com/grafana/grafana/pkg/util" +) + +func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB, resolver accesscontrol.ScopeAttributeResolverFunc) ([]accesspolicy.Resource, error) { + type permissionInfo struct { + RoleUID string + RoleName string + Scope string + Action string + Created time.Time + Updated time.Time + } + info := &permissionInfo{} + policies := make([]accesspolicy.Resource, 0) + current := &accesspolicy.Resource{} + prevKey := "" + rows, err := sql.Query(ctx, `SELECT + role.uid as role_uid, + role.name as role_name, + scope, + action, + permission.created, + permission.updated + FROM permission + JOIN role ON permission.role_id = role.id + WHERE org_id=? + ORDER BY role.id ASC, scope ASC, action ASC`, orgID) + if err != nil { + return nil, err + } + + created := time.Now() + updated := time.Now() + + for rows.Next() { + err = rows.Scan( + &info.RoleUID, + &info.RoleName, + &info.Scope, + &info.Action, + &info.Created, + &info.Updated, + ) + if err != nil { + return policies, err + } + + key := info.RoleUID + "/" + info.Scope + if key != prevKey { + created = info.Created + updated = info.Updated + if len(current.Spec.Rules) > 0 { + current.Spec.Rules = accesspolicy.ReduceRules(current.Spec.Rules) + policies = append(policies, *current) + } + scope, err := resolver(ctx, orgID, info.Scope) + if err != nil { + return policies, err + } + if len(scope) != 3 { + return policies, fmt.Errorf("expected three part scope") + } + + current = &accesspolicy.Resource{ + Metadata: accesspolicy.Metadata{ + CreationTimestamp: created, + UpdateTimestamp: updated, + }, + Spec: accesspolicy.Spec{ + Role: accesspolicy.RoleRef{ + Kind: accesspolicy.RoleRefKindRole, + Name: info.RoleUID, + Xname: info.RoleName, + }, + Scope: accesspolicy.ResourceRef{ + Kind: scope[0], + Name: scope[2], + }, + Rules: make([]accesspolicy.AccessRule, 0), + }, + } + // When the value is not a UID, set the prefix to $ -- an invalid name + if scope[1] != "uid" { + current.Spec.Scope.Name = fmt.Sprintf("$%s:%s", scope[1], scope[2]) + } + + // Skip role+role binding for direct users + if strings.HasPrefix(info.RoleName, "managed:users:") { + current.Spec.Role.Kind = accesspolicy.RoleRefKindUser + current.Spec.Role.Name = "$TODO:" + info.RoleName + } + + prevKey = key + } + + if info.Created.Before(created) { + created = info.Created + current.Metadata.CreationTimestamp = created + } + if info.Updated.After(updated) { + updated = info.Updated + current.Metadata.UpdateTimestamp = updated + } + + action := strings.Split(info.Action, ":") + if len(action) != 2 { + return policies, fmt.Errorf("expected two part action") + } + parts := strings.SplitN(action[0], ".", 2) + rule := accesspolicy.AccessRule{ + Verb: action[1], + Kind: parts[0], + } + if len(parts) > 1 { + rule.Target = util.Pointer(parts[1]) + } + + // // When the scope is dashboards or teams + // // ... hymmm ... this would imply permissions + // if rule.Kind == current.Spec.Scope.Kind { + // rule.Kind = "*" + // } + + current.Spec.Rules = append(current.Spec.Rules, rule) + } + if current.Spec.Scope.Name != "" { + current.Spec.Rules = accesspolicy.ReduceRules(current.Spec.Rules) + policies = append(policies, *current) + } + return policies, err +} diff --git a/public/app/core/components/AppChrome/AppChromeUpdate.tsx b/public/app/core/components/AppChrome/AppChromeUpdate.tsx index 7a654d5fc74..2f1596debd5 100644 --- a/public/app/core/components/AppChrome/AppChromeUpdate.tsx +++ b/public/app/core/components/AppChrome/AppChromeUpdate.tsx @@ -13,7 +13,7 @@ export const AppChromeUpdate = React.memo(({ actions }: Ap const { chrome } = useGrafana(); // We use useLayoutEffect here to make sure that the chrome is updated before the page is rendered - // This prevents flickering actions when going from one dashbaord to another for example + // This prevents flickering actions when going from one dashboard to another for example useLayoutEffect(() => { chrome.update({ actions }); }); diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 67cb493f3da..3f080d90bef 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -37,7 +37,7 @@ export const Page: PageType = ({ const pageHeaderNav = pageNav ?? navModel?.node; // We use useLayoutEffect here to make sure that the chrome is updated before the page is rendered - // This prevents flickering sectionNav when going from dashbaord to settings for example + // This prevents flickering sectionNav when going from dashboard to settings for example useLayoutEffect(() => { if (navModel) { chrome.update({ diff --git a/public/app/features/dashboard/utils/panelMerge.test.ts b/public/app/features/dashboard/utils/panelMerge.test.ts index 45a8195a12a..d20092310dc 100644 --- a/public/app/features/dashboard/utils/panelMerge.test.ts +++ b/public/app/features/dashboard/utils/panelMerge.test.ts @@ -4,7 +4,7 @@ import { FieldColorModeId, ThresholdsMode } from '@grafana/schema/src'; import { DashboardModel } from '../state/DashboardModel'; import { createDashboardModelFixture, createPanelJSONFixture } from '../state/__fixtures__/dashboardFixtures'; -describe('Merge dashbaord panels', () => { +describe('Merge dashboard panels', () => { describe('simple changes', () => { let dashboard: DashboardModel; let rawPanels: PanelModel[]; diff --git a/public/app/features/storage/storage.ts b/public/app/features/storage/storage.ts index f527f7c7091..d164577d4a8 100644 --- a/public/app/features/storage/storage.ts +++ b/public/app/features/storage/storage.ts @@ -18,7 +18,7 @@ export interface GrafanaStorage { /** Called before save */ getOptions: (path: string) => Promise; - /** Saves dashbaords */ + /** Saves dashboards */ write: (path: string, options: WriteValueRequest) => Promise; }