From 69f6650d51dcf78a0bf31dc34b987d87cd34badf Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Mon, 4 Nov 2024 17:43:22 -0300 Subject: [PATCH 01/30] feat: define new roles --- src/plugin.json | 156 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/src/plugin.json b/src/plugin.json index b46b2ef57..9d5bb38c6 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -73,6 +73,162 @@ "path": "datasource/plugin.json" } ], + "roles": [ + { + "role": { + "name": "Checks reader", + "description": "Read checks in the Synthetic Monitoring app", + "permissions": [{ "action": "grafana-synthetic-monitoring-app.checks:read" }] + }, + "grants": ["Admin", "Editor", "Viewer"] + }, + { + "role": { + "name": "Checks writer", + "description": "Create, edit and delete checks in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.checks:edit" }, + { "action": "grafana-synthetic-monitoring-app.checks:read" }, + { "action": "grafana-synthetic-monitoring-app.checks:delete" } + ] + }, + "grants": ["Admin", "Editor"] + }, + { + "role": { + "name": "Probes reader", + "description": "Read probes in the Synthetic Monitoring app", + "permissions": [{ "action": "grafana-synthetic-monitoring-app.probes:read" }] + }, + "grants": ["Admin", "Editor", "Viewer"] + }, + { + "role": { + "name": "Probes writer", + "description": "Create, edit and delete probes in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.probes:edit" }, + { "action": "grafana-synthetic-monitoring-app.probes:read" }, + { "action": "grafana-synthetic-monitoring-app.probes:delete" } + ] + }, + "grants": ["Admin", "Editor"] + }, + { + "role": { + "name": "Alerts reader", + "description": "Read alerts in the Synthetic Monitoring app", + "permissions": [{ "action": "grafana-synthetic-monitoring-app.alerts:read" }] + }, + "grants": ["Admin", "Editor", "Viewer"] + }, + { + "role": { + "name": "Alerts writer", + "description": "Create, edit and delete alerts in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.alerts:edit" }, + { "action": "grafana-synthetic-monitoring-app.alerts:read" }, + { "action": "grafana-synthetic-monitoring-app.alerts:delete" } + ] + }, + "grants": ["Admin", "Editor"] + }, + { + "role": { + "name": "Thresholds reader", + "description": "Read thresholds in the Synthetic Monitoring app", + "permissions": [{ "action": "grafana-synthetic-monitoring-app.thresholds:read" }] + }, + "grants": ["Admin", "Editor", "Viewer"] + }, + { + "role": { + "name": "Thresholds writer", + "description": "Read and edit thresholds in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.thresholds:edit" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:read" } + ] + }, + "grants": ["Admin", "Editor"] + }, + { + "role": { + "name": "Access tokens writer", + "description": "Create and delete access tokens in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.access-tokens:create" }, + { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } + ] + }, + "grants": ["Admin", "Editor"] + }, + { + "role": { + "name": "Full admin access", + "description": "Full access to write and manage checks, probes, alerts, thresholds, and access tokens as well as enabling/disabling the Synthetic Monitoring plugin", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.plugin:enable" }, + { "action": "grafana-synthetic-monitoring-app.plugin:disable" }, + { "action": "grafana-synthetic-monitoring-app.checks:write" }, + { "action": "grafana-synthetic-monitoring-app.probes:write" }, + { "action": "grafana-synthetic-monitoring-app.alerts:write" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:write" }, + { "action": "grafana-synthetic-monitoring-app.tokens:write" }, + { "action": "grafana-synthetic-monitoring-app.checks:read" }, + { "action": "grafana-synthetic-monitoring-app.probes:read" }, + { "action": "grafana-synthetic-monitoring-app.alerts:read" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, + { "action": "grafana-synthetic-monitoring-app.tokens:read" }, + { "action": "grafana-synthetic-monitoring-app.checks:delete" }, + { "action": "grafana-synthetic-monitoring-app.probes:delete" }, + { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:delete" }, + { "action": "grafana-synthetic-monitoring-app.tokens:delete" } + ] + }, + "grants": ["Admin"] + }, + { + "role": { + "name": "Full write access", + "description": "Add, update and delete checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.checks:write" }, + { "action": "grafana-synthetic-monitoring-app.probes:write" }, + { "action": "grafana-synthetic-monitoring-app.alerts:write" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:write" }, + { "action": "grafana-synthetic-monitoring-app.tokens:write" }, + { "action": "grafana-synthetic-monitoring-app.checks:read" }, + { "action": "grafana-synthetic-monitoring-app.probes:read" }, + { "action": "grafana-synthetic-monitoring-app.alerts:read" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, + { "action": "grafana-synthetic-monitoring-app.tokens:read" }, + { "action": "grafana-synthetic-monitoring-app.checks:delete" }, + { "action": "grafana-synthetic-monitoring-app.probes:delete" }, + { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:delete" }, + { "action": "grafana-synthetic-monitoring-app.tokens:delete" } + ] + }, + "grants": ["Admin", "Editor"] + }, + { + "role": { + "name": "Full read-only access", + "description": "Read checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", + "permissions": [ + { "action": "grafana-synthetic-monitoring-app.checks:read" }, + { "action": "grafana-synthetic-monitoring-app.probes:read" }, + { "action": "grafana-synthetic-monitoring-app.alerts:read" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, + { "action": "grafana-synthetic-monitoring-app.tokens:read" } + ] + }, + "grants": ["Admin", "Editor", "Viewer"] + } + ], "dependencies": { "grafanaDependency": ">=11.3.0", "grafanaVersion": "11.3" From b68d7a8f1f76998eef3cdfa905e6b13f4ae1bf35 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Mon, 4 Nov 2024 17:55:16 -0300 Subject: [PATCH 02/30] feat: add role requirements for routes and pages --- src/plugin.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugin.json b/src/plugin.json index 9d5bb38c6..114852917 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -26,7 +26,7 @@ "path": "install", "method": "*", "url": "{{ .JsonData.apiHost }}/api/v1/register/install", - "reqRole": "Editor", + "reqAction": "grafana-synthetic-monitoring-app.plugin:enable", "headers": [ { "name": "Authorization", @@ -40,6 +40,7 @@ "type": "page", "name": "Home", "path": "/a/grafana-synthetic-monitoring-app/home", + "action": "grafana-synthetic-monitoring-app.checks:read", "addToNav": true, "defaultNav": true }, @@ -47,18 +48,21 @@ "type": "page", "name": "Checks", "path": "/a/grafana-synthetic-monitoring-app/checks", + "action": "grafana-synthetic-monitoring-app.checks:read", "addToNav": true }, { "type": "page", "name": "Probes", "path": "/a/grafana-synthetic-monitoring-app/probes", + "action": "grafana-synthetic-monitoring-app.probes:read", "addToNav": true }, { "type": "page", "name": "Alerts", "path": "/a/grafana-synthetic-monitoring-app/alerts", + "action": "grafana-synthetic-monitoring-app.alerts:read", "addToNav": true }, { From 493f2cb0d0f6c9e7204f3f19aaed146a1b0ef47d Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 12 Nov 2024 16:42:29 -0300 Subject: [PATCH 03/30] fix: add plugin access permission for all roles --- src/plugin.json | 52 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 114852917..1a94637fb 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -82,7 +82,11 @@ "role": { "name": "Checks reader", "description": "Read checks in the Synthetic Monitoring app", - "permissions": [{ "action": "grafana-synthetic-monitoring-app.checks:read" }] + "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + + { "action": "grafana-synthetic-monitoring-app.checks:read" } + ] }, "grants": ["Admin", "Editor", "Viewer"] }, @@ -91,6 +95,8 @@ "name": "Checks writer", "description": "Create, edit and delete checks in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.checks:edit" }, { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.checks:delete" } @@ -102,7 +108,11 @@ "role": { "name": "Probes reader", "description": "Read probes in the Synthetic Monitoring app", - "permissions": [{ "action": "grafana-synthetic-monitoring-app.probes:read" }] + "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + + { "action": "grafana-synthetic-monitoring-app.probes:read" } + ] }, "grants": ["Admin", "Editor", "Viewer"] }, @@ -111,6 +121,8 @@ "name": "Probes writer", "description": "Create, edit and delete probes in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.probes:edit" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" } @@ -122,7 +134,11 @@ "role": { "name": "Alerts reader", "description": "Read alerts in the Synthetic Monitoring app", - "permissions": [{ "action": "grafana-synthetic-monitoring-app.alerts:read" }] + "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + + { "action": "grafana-synthetic-monitoring-app.alerts:read" } + ] }, "grants": ["Admin", "Editor", "Viewer"] }, @@ -131,6 +147,8 @@ "name": "Alerts writer", "description": "Create, edit and delete alerts in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.alerts:edit" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" } @@ -142,7 +160,11 @@ "role": { "name": "Thresholds reader", "description": "Read thresholds in the Synthetic Monitoring app", - "permissions": [{ "action": "grafana-synthetic-monitoring-app.thresholds:read" }] + "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + + { "action": "grafana-synthetic-monitoring-app.thresholds:read" } + ] }, "grants": ["Admin", "Editor", "Viewer"] }, @@ -151,6 +173,8 @@ "name": "Thresholds writer", "description": "Read and edit thresholds in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:edit" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" } ] @@ -162,17 +186,33 @@ "name": "Access tokens writer", "description": "Create and delete access tokens in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.access-tokens:create" }, { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } ] }, "grants": ["Admin", "Editor"] }, + { + "role": { + "name": "Access tokens reader", + "description": "Read access tokens in the Synthetic Monitoring app", + "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + + { "action": "grafana-synthetic-monitoring-app.access-tokens:read" } + ] + }, + "grants": ["Admin", "Editor"] + }, { "role": { "name": "Full admin access", "description": "Full access to write and manage checks, probes, alerts, thresholds, and access tokens as well as enabling/disabling the Synthetic Monitoring plugin", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.plugin:enable" }, { "action": "grafana-synthetic-monitoring-app.plugin:disable" }, { "action": "grafana-synthetic-monitoring-app.checks:write" }, @@ -199,6 +239,8 @@ "name": "Full write access", "description": "Add, update and delete checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.checks:write" }, { "action": "grafana-synthetic-monitoring-app.probes:write" }, { "action": "grafana-synthetic-monitoring-app.alerts:write" }, @@ -223,6 +265,8 @@ "name": "Full read-only access", "description": "Read checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", "permissions": [ + { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, From db6353c34c13a1d571334500042cd468db7cca77 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 12 Nov 2024 16:48:21 -0300 Subject: [PATCH 04/30] fix: configure general read/write permissions for ds plugin.json config --- src/datasource/plugin.json | 10 +++++----- src/plugin.json | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/datasource/plugin.json b/src/datasource/plugin.json index a0a4a31e4..8e4975d36 100644 --- a/src/datasource/plugin.json +++ b/src/datasource/plugin.json @@ -9,7 +9,7 @@ "path": "viewer-token", "method": "*", "url": "{{.JsonData.apiHost}}/api/v1/register/viewer-token", - "reqRole": "Editor", + "reqAction": "grafana-synthetic-monitoring-app:write", "headers": [ { "name": "Authorization", @@ -21,7 +21,7 @@ "path": "save", "method": "*", "url": "{{.JsonData.apiHost}}/api/v1/register/save", - "reqRole": "Editor", + "reqAction": "grafana-synthetic-monitoring-app:write", "headers": [ { "name": "Authorization", @@ -33,7 +33,7 @@ "path": "sm", "method": "GET", "url": "{{.JsonData.apiHost}}/api/v1/", - "reqRole": "Viewer", + "reqAction": "grafana-synthetic-monitoring-app:read", "headers": [ { "name": "Authorization", @@ -45,7 +45,7 @@ "path": "sm", "method": "POST", "url": "{{.JsonData.apiHost}}/api/v1/", - "reqRole": "Editor", + "reqAction": "grafana-synthetic-monitoring-app:write", "headers": [ { "name": "Authorization", @@ -57,7 +57,7 @@ "path": "sm", "method": "DELETE", "url": "{{.JsonData.apiHost}}/api/v1/", - "reqRole": "Editor", + "reqAction": "grafana-synthetic-monitoring-app:write", "headers": [ { "name": "Authorization", diff --git a/src/plugin.json b/src/plugin.json index 1a94637fb..8a873c77e 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -84,6 +84,7 @@ "description": "Read checks in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app.checks:read" } ] @@ -96,6 +97,8 @@ "description": "Create, edit and delete checks in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.checks:edit" }, { "action": "grafana-synthetic-monitoring-app.checks:read" }, @@ -110,6 +113,7 @@ "description": "Read probes in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app.probes:read" } ] @@ -122,6 +126,8 @@ "description": "Create, edit and delete probes in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.probes:edit" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, @@ -136,6 +142,7 @@ "description": "Read alerts in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" } ] @@ -148,6 +155,8 @@ "description": "Create, edit and delete alerts in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.alerts:edit" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, @@ -162,6 +171,7 @@ "description": "Read thresholds in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" } ] @@ -174,6 +184,8 @@ "description": "Read and edit thresholds in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.thresholds:edit" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" } @@ -187,6 +199,8 @@ "description": "Create and delete access tokens in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.access-tokens:create" }, { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } @@ -200,6 +214,7 @@ "description": "Read access tokens in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app.access-tokens:read" } ] @@ -212,6 +227,8 @@ "description": "Full access to write and manage checks, probes, alerts, thresholds, and access tokens as well as enabling/disabling the Synthetic Monitoring plugin", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.plugin:enable" }, { "action": "grafana-synthetic-monitoring-app.plugin:disable" }, @@ -240,6 +257,8 @@ "description": "Add, update and delete checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, + { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.checks:write" }, { "action": "grafana-synthetic-monitoring-app.probes:write" }, @@ -266,6 +285,7 @@ "description": "Read checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, + { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, From 372cd17cacb70cba49d3ccbd11dbab7874546487 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Wed, 13 Nov 2024 11:18:50 -0300 Subject: [PATCH 05/30] fix: remove redundant granted roles --- src/plugin.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 8a873c77e..f8c2d7f57 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -89,7 +89,7 @@ { "action": "grafana-synthetic-monitoring-app.checks:read" } ] }, - "grants": ["Admin", "Editor", "Viewer"] + "grants": ["Viewer"] }, { "role": { @@ -118,7 +118,7 @@ { "action": "grafana-synthetic-monitoring-app.probes:read" } ] }, - "grants": ["Admin", "Editor", "Viewer"] + "grants": ["Viewer"] }, { "role": { @@ -147,7 +147,7 @@ { "action": "grafana-synthetic-monitoring-app.alerts:read" } ] }, - "grants": ["Admin", "Editor", "Viewer"] + "grants": ["Viewer"] }, { "role": { @@ -176,7 +176,7 @@ { "action": "grafana-synthetic-monitoring-app.thresholds:read" } ] }, - "grants": ["Admin", "Editor", "Viewer"] + "grants": ["Viewer"] }, { "role": { @@ -294,7 +294,7 @@ { "action": "grafana-synthetic-monitoring-app.tokens:read" } ] }, - "grants": ["Admin", "Editor", "Viewer"] + "grants": ["Viewer"] } ], "dependencies": { From 0c84cb6e591fd54c583fc460c12653456a3e54f9 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Wed, 13 Nov 2024 11:26:02 -0300 Subject: [PATCH 06/30] fix: improve names and remove token writer access for editors --- src/plugin.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index f8c2d7f57..38b066664 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -206,7 +206,7 @@ { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } ] }, - "grants": ["Admin", "Editor"] + "grants": ["Admin"] }, { "role": { @@ -223,7 +223,7 @@ }, { "role": { - "name": "Full admin access", + "name": "Admin", "description": "Full access to write and manage checks, probes, alerts, thresholds, and access tokens as well as enabling/disabling the Synthetic Monitoring plugin", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, @@ -253,7 +253,7 @@ }, { "role": { - "name": "Full write access", + "name": "Editor", "description": "Add, update and delete checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, @@ -264,7 +264,6 @@ { "action": "grafana-synthetic-monitoring-app.probes:write" }, { "action": "grafana-synthetic-monitoring-app.alerts:write" }, { "action": "grafana-synthetic-monitoring-app.thresholds:write" }, - { "action": "grafana-synthetic-monitoring-app.tokens:write" }, { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, @@ -273,15 +272,14 @@ { "action": "grafana-synthetic-monitoring-app.checks:delete" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, - { "action": "grafana-synthetic-monitoring-app.thresholds:delete" }, - { "action": "grafana-synthetic-monitoring-app.tokens:delete" } + { "action": "grafana-synthetic-monitoring-app.thresholds:delete" } ] }, "grants": ["Admin", "Editor"] }, { "role": { - "name": "Full read-only access", + "name": "Reader", "description": "Read checks, probes, alerts, thresholds, and access tokens in the Synthetic Monitoring app", "permissions": [ { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, From f7a46b662e2ebd225ef4b43bb6e4555fdd1a674f Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Wed, 13 Nov 2024 17:22:49 -0300 Subject: [PATCH 07/30] fix: rename edit permission --- src/plugin.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 38b066664..12c29c7f5 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -100,7 +100,7 @@ { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app:write" }, - { "action": "grafana-synthetic-monitoring-app.checks:edit" }, + { "action": "grafana-synthetic-monitoring-app.checks:write" }, { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.checks:delete" } ] @@ -129,7 +129,7 @@ { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app:write" }, - { "action": "grafana-synthetic-monitoring-app.probes:edit" }, + { "action": "grafana-synthetic-monitoring-app.probes:write" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" } ] @@ -158,7 +158,7 @@ { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app:write" }, - { "action": "grafana-synthetic-monitoring-app.alerts:edit" }, + { "action": "grafana-synthetic-monitoring-app.alerts:write" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" } ] @@ -187,7 +187,7 @@ { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app:write" }, - { "action": "grafana-synthetic-monitoring-app.thresholds:edit" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:write" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" } ] }, From 737c03cbafd5d383eb5d2958088bcd3e53531b47 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 19 Nov 2024 11:48:57 -0300 Subject: [PATCH 08/30] fix: rename tokens to access-tokens --- src/plugin.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 12c29c7f5..c8ecd6bf0 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -236,17 +236,17 @@ { "action": "grafana-synthetic-monitoring-app.probes:write" }, { "action": "grafana-synthetic-monitoring-app.alerts:write" }, { "action": "grafana-synthetic-monitoring-app.thresholds:write" }, - { "action": "grafana-synthetic-monitoring-app.tokens:write" }, + { "action": "grafana-synthetic-monitoring-app.access-tokens:write" }, { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, - { "action": "grafana-synthetic-monitoring-app.tokens:read" }, + { "action": "grafana-synthetic-monitoring-app.access-tokens:read" }, { "action": "grafana-synthetic-monitoring-app.checks:delete" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, { "action": "grafana-synthetic-monitoring-app.thresholds:delete" }, - { "action": "grafana-synthetic-monitoring-app.tokens:delete" } + { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } ] }, "grants": ["Admin"] @@ -268,7 +268,7 @@ { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, - { "action": "grafana-synthetic-monitoring-app.tokens:read" }, + { "action": "grafana-synthetic-monitoring-app.access-tokens:read" }, { "action": "grafana-synthetic-monitoring-app.checks:delete" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, @@ -289,7 +289,7 @@ { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, - { "action": "grafana-synthetic-monitoring-app.tokens:read" } + { "action": "grafana-synthetic-monitoring-app.access-tokens:read" } ] }, "grants": ["Viewer"] From f78d04a85032024e113316600a66d43630873d75 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 19 Nov 2024 16:02:12 -0300 Subject: [PATCH 09/30] fix: restrict config page to writers --- src/plugin.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin.json b/src/plugin.json index c8ecd6bf0..4cfcf7f3f 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -69,6 +69,7 @@ "type": "page", "name": "Config", "path": "/a/grafana-synthetic-monitoring-app/config", + "action": "grafana-synthetic-monitoring-app:write", "addToNav": true }, { From 7cc742415488bb1d6d1a8f3e83916be0b2016dab Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 19 Nov 2024 16:16:14 -0300 Subject: [PATCH 10/30] fix: change required permissions to register a ds --- src/datasource/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datasource/plugin.json b/src/datasource/plugin.json index 8e4975d36..58a24a67e 100644 --- a/src/datasource/plugin.json +++ b/src/datasource/plugin.json @@ -21,7 +21,7 @@ "path": "save", "method": "*", "url": "{{.JsonData.apiHost}}/api/v1/register/save", - "reqAction": "grafana-synthetic-monitoring-app:write", + "reqAction": "grafana-synthetic-monitoring-app.plugin:enable", "headers": [ { "name": "Authorization", From 8b41cd5fe45e2f2e67d3fa971408a69decb645eb Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 19 Nov 2024 16:22:59 -0300 Subject: [PATCH 11/30] fix: change access-token create for write to match convention --- src/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.json b/src/plugin.json index 4cfcf7f3f..7da7b245e 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -203,7 +203,7 @@ { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app:write" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:create" }, + { "action": "grafana-synthetic-monitoring-app.access-tokens:write" }, { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } ] }, From 33fc3f4bc5ae7aba13a351ee2dd90b1884ae7e97 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Mon, 25 Nov 2024 18:01:39 -0300 Subject: [PATCH 12/30] fix: add missing threshold: delete permission in Threshold Writer role --- src/plugin.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugin.json b/src/plugin.json index 7da7b245e..31642d916 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -189,7 +189,8 @@ { "action": "grafana-synthetic-monitoring-app:write" }, { "action": "grafana-synthetic-monitoring-app.thresholds:write" }, - { "action": "grafana-synthetic-monitoring-app.thresholds:read" } + { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, + { "action": "grafana-synthetic-monitoring-app.thresholds:delete" } ] }, "grants": ["Admin", "Editor"] From f25ab159e8770450d005f6069239abc39f17c64c Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Wed, 27 Nov 2024 18:24:08 -0300 Subject: [PATCH 13/30] fix: change required permissions to see SM home page --- src/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.json b/src/plugin.json index 31642d916..2f069171c 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -40,7 +40,7 @@ "type": "page", "name": "Home", "path": "/a/grafana-synthetic-monitoring-app/home", - "action": "grafana-synthetic-monitoring-app.checks:read", + "action": "grafana-synthetic-monitoring-app:read", "addToNav": true, "defaultNav": true }, From 24f985737cb621f54e57d93f6aaa2f83451468c5 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Thu, 28 Nov 2024 11:28:35 -0300 Subject: [PATCH 14/30] RBAC: enforce permissions in frontend using user roles (#986) * feat: create useUserPermissions hook * feat: define PluginPermissions type * feat: enforce Check permissions using RBAC * feat: enforce Probe permissions usign RBAC * feat: enforce Alert permissions using RBAC * fix: lint * feat: enforce Config permissions using RBAC * feat: apply new permissions to plugin installation * fix: remove console.log * fix: fallback to basic user roles contemplating roles hierarchy * fix: change PluginPermission to use write instead of edit * fix: add tests * fix: update types for access-tokens permissions * fix: lint * fix: tests * fix: show missing permissions alert * fix: adjust types to match plugin definitions * fix: refactor getUserPermissions function * fix: change plugin permissions to use template literal types * fix: uppercase RBAC in function names * fix: updates after rebasing with main * fix: adapt after rebasing with main * fix: remove useCanWriteSM hook - instead, we should query permissions from getUserPermissions * fix: lint * fix: check for metrics ds query access in order to display alerts --- .../AddNewCheckButton/AddNewCheckButton.tsx | 6 +- src/components/AppInitializer.tsx | 18 ++++-- src/components/CheckForm/CheckForm.tsx | 7 ++- src/components/ConfigActions.tsx | 11 ++-- .../DeleteProbeButton/DeleteProbeButton.tsx | 10 ++-- src/components/LinkedDatasourceView.tsx | 5 +- src/components/ProbeCard/ProbeCard.test.tsx | 14 ++++- src/components/ProbeCard/ProbeCard.tsx | 6 +- .../ProbeEditor/ProbeEditor.test.tsx | 14 ++++- src/components/ProbeEditor/ProbeEditor.tsx | 8 +-- .../ProbeStatus/ProbeStatus.test.tsx | 10 +++- src/components/ProbeStatus/ProbeStatus.tsx | 6 +- src/data/permissions.ts | 57 +++++++++++++++++++ src/hooks/useAlertPermissions.ts | 17 ++++++ src/hooks/useCanEditProbe.ts | 11 ++-- src/hooks/useDSPermission.ts | 16 ------ src/hooks/usePluginPermissions.ts | 13 +++++ src/page/AlertingPage.tsx | 21 ++++--- src/page/CheckList/components/BulkActions.tsx | 24 ++++++-- .../components/CheckItemActionButtons.tsx | 11 ++-- .../CheckList/components/CheckListHeader.tsx | 18 +++--- .../ConfigPageLayout/tabs/AccessTokensTab.tsx | 8 +-- src/page/ConfigPageLayout/tabs/GeneralTab.tsx | 4 +- src/page/ContactAdminAlert.tsx | 17 ++++++ .../EditCheck/__tests__/EditCheck.test.tsx | 8 ++- src/page/EditProbe/EditProbe.tsx | 14 ++--- src/page/Probes/Probes.tsx | 6 +- src/routing/InitialisedRouter.tsx | 7 ++- src/scenes/Common/editButton.tsx | 6 +- src/test/fixtures/rbacPermissions.ts | 50 ++++++++++++++++ src/test/mocks/@grafana/runtime.tsx | 3 +- src/test/utils.ts | 55 ++++++++++++++++++ src/types.ts | 10 ++++ 33 files changed, 380 insertions(+), 111 deletions(-) create mode 100644 src/data/permissions.ts create mode 100644 src/hooks/useAlertPermissions.ts create mode 100644 src/hooks/usePluginPermissions.ts create mode 100644 src/page/ContactAdminAlert.tsx create mode 100644 src/test/fixtures/rbacPermissions.ts diff --git a/src/components/AddNewCheckButton/AddNewCheckButton.tsx b/src/components/AddNewCheckButton/AddNewCheckButton.tsx index 91726af41..9b68e4428 100644 --- a/src/components/AddNewCheckButton/AddNewCheckButton.tsx +++ b/src/components/AddNewCheckButton/AddNewCheckButton.tsx @@ -2,14 +2,14 @@ import React from 'react'; import { Button } from '@grafana/ui'; import { ROUTES } from 'routing/types'; -import { useCanWriteSM } from 'hooks/useDSPermission'; +import { getUserPermissions } from 'data/permissions'; import { useNavigation } from 'hooks/useNavigation'; export function AddNewCheckButton() { const navigate = useNavigation(); - const canEdit = useCanWriteSM(); + const { canWriteChecks } = getUserPermissions(); - if (!canEdit) { + if (!canWriteChecks) { return null; } diff --git a/src/components/AppInitializer.tsx b/src/components/AppInitializer.tsx index c4d894708..c0e795fc4 100644 --- a/src/components/AppInitializer.tsx +++ b/src/components/AppInitializer.tsx @@ -6,9 +6,12 @@ import { DataTestIds } from 'test/dataTestIds'; import { hasGlobalPermission } from 'utils'; import { ROUTES } from 'routing/types'; +import { getUserPermissions } from 'data/permissions'; + import { useAppInitializer } from 'hooks/useAppInitializer'; import { useMeta } from 'hooks/useMeta'; import { MismatchedDatasourceModal } from 'components/MismatchedDatasourceModal'; +import { ContactAdminAlert } from 'page/ContactAdminAlert'; interface Props { redirectTo?: ROUTES; @@ -19,7 +22,10 @@ interface Props { export const AppInitializer = ({ redirectTo, buttonText }: PropsWithChildren) => { const { jsonData } = useMeta(); const styles = useStyles2(getStyles); - const canInitialize = hasGlobalPermission(`datasources:create`); + const { canEnablePlugin } = getUserPermissions(); + + const meetsMinPermissions = hasGlobalPermission(`datasources:read`); + const canInitialize = canEnablePlugin && hasGlobalPermission(`datasources:create`); const { error, @@ -35,12 +41,12 @@ export const AppInitializer = ({ redirectTo, buttonText }: PropsWithChildren; + } + if (!canInitialize) { - return ( - - Contact your administrator to get you started. - - ); + return ; } return ( diff --git a/src/components/CheckForm/CheckForm.tsx b/src/components/CheckForm/CheckForm.tsx index 96e16914d..71f8cddb8 100644 --- a/src/components/CheckForm/CheckForm.tsx +++ b/src/components/CheckForm/CheckForm.tsx @@ -12,9 +12,10 @@ import { createNavModel } from 'utils'; import { ROUTES } from 'routing/types'; import { generateRoutePath } from 'routing/utils'; import { AdHocCheckResponse } from 'datasource/responses.types'; +import { getUserPermissions } from 'data/permissions'; import { useCheckTypeGroupOption } from 'hooks/useCheckTypeGroupOptions'; import { useCheckTypeOptions } from 'hooks/useCheckTypeOptions'; -import { useCanReadLogs, useCanWriteSM } from 'hooks/useDSPermission'; +import { useCanReadLogs } from 'hooks/useDSPermission'; import { useLimits } from 'hooks/useLimits'; import { toFormValues } from 'components/CheckEditor/checkFormTransformations'; import { CheckJobName } from 'components/CheckEditor/FormComponents/CheckJobName'; @@ -72,7 +73,7 @@ type CheckFormProps = { }; export const CheckForm = ({ check, disabled }: CheckFormProps) => { - const canEdit = useCanWriteSM(); + const { canWriteChecks } = getUserPermissions(); const canReadLogs = useCanReadLogs(); const [openTestCheckModal, setOpenTestCheckModal] = useState(false); const [adhocTestData, setAdhocTestData] = useState(); @@ -90,7 +91,7 @@ export const CheckForm = ({ check, disabled }: CheckFormProps) => { isOverCheckLimit || (checkType === CheckType.Browser && isOverBrowserLimit) || ([CheckType.MULTI_HTTP, CheckType.Scripted].includes(checkType) && isOverScriptedLimit); - const isDisabled = disabled || !canEdit || getLimitDisabled({ isExistingCheck, isLoading, overLimit }); + const isDisabled = disabled || !canWriteChecks || getLimitDisabled({ isExistingCheck, isLoading, overLimit }); const formMethods = useForm({ defaultValues: toFormValues(initialCheck, checkType), diff --git a/src/components/ConfigActions.tsx b/src/components/ConfigActions.tsx index 17ad11ad2..6347b5234 100644 --- a/src/components/ConfigActions.tsx +++ b/src/components/ConfigActions.tsx @@ -2,17 +2,18 @@ import React, { useState } from 'react'; import { getBackendSrv } from '@grafana/runtime'; import { Button, LinkButton } from '@grafana/ui'; -import { hasGlobalPermission } from 'utils'; import { ROUTES } from 'routing/types'; import { getRoute } from 'routing/utils'; import { useMeta } from 'hooks/useMeta'; +import { usePluginPermissions } from 'hooks/usePluginPermissions'; import { DisablePluginModal } from './DisablePluginModal'; export const ConfigActions = ({ initialized }: { initialized?: boolean }) => { const [showDisableModal, setShowDisableModal] = useState(false); const meta = useMeta(); - const canEdit = hasGlobalPermission(`plugins:write`); + + const { canEnablePlugin, canDisablePlugin, canEditPlugin } = usePluginPermissions(); const handleEnable = async () => { await getBackendSrv() @@ -28,11 +29,11 @@ export const ConfigActions = ({ initialized }: { initialized?: boolean }) => { window.location.reload(); }; - if (!canEdit) { + if (!canEditPlugin) { return null; } - if (!meta.enabled) { + if (!meta.enabled && canEnablePlugin) { return ( diff --git a/src/components/LinkedDatasourceView.tsx b/src/components/LinkedDatasourceView.tsx index e9b32b381..720a0b017 100644 --- a/src/components/LinkedDatasourceView.tsx +++ b/src/components/LinkedDatasourceView.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { Alert, Card, Tag } from '@grafana/ui'; -import { useCanWriteLogs, useCanWriteMetrics, useCanWriteSM } from 'hooks/useDSPermission'; +import { getUserPermissions } from 'data/permissions'; +import { useCanWriteLogs, useCanWriteMetrics } from 'hooks/useDSPermission'; import { useLogsDS } from 'hooks/useLogsDS'; import { useMetricsDS } from 'hooks/useMetricsDS'; import { useSMDS } from 'hooks/useSMDS'; @@ -15,7 +16,7 @@ export const LinkedDatasourceView = ({ type }: LinkedDatasourceViewProps) => { const logsDS = useLogsDS(); const smDS = useSMDS(); - const canEditSM = useCanWriteSM(); + const { canWriteSM: canEditSM } = getUserPermissions(); const canEditLogs = useCanWriteLogs(); const canEditMetrics = useCanWriteMetrics(); diff --git a/src/components/ProbeCard/ProbeCard.test.tsx b/src/components/ProbeCard/ProbeCard.test.tsx index 7e7c818fb..5e9aabd39 100644 --- a/src/components/ProbeCard/ProbeCard.test.tsx +++ b/src/components/ProbeCard/ProbeCard.test.tsx @@ -6,7 +6,7 @@ import { userEvent } from '@testing-library/user-event'; import { DataTestIds } from 'test/dataTestIds'; import { OFFLINE_PROBE, ONLINE_PROBE, PRIVATE_PROBE, PUBLIC_PROBE } from 'test/fixtures/probes'; import { render } from 'test/render'; -import { probeToExtendedProbe, runTestAsViewer } from 'test/utils'; +import { probeToExtendedProbe, runTestAsRBACReader, runTestAsViewer } from 'test/utils'; import { type ExtendedProbe } from 'types'; import { ROUTES } from 'routing/types'; @@ -92,6 +92,18 @@ it(`Displays the correct information for a private probe as a viewer`, async () expect(button).toHaveTextContent('View'); }); +it(`Displays the correct information for a private probe as a RBAC viewer`, async () => { + runTestAsRBACReader(); + const probe = probeToExtendedProbe(PRIVATE_PROBE); + + render(); + await screen.findByText(probe.name, { exact: false }); + + const button = screen.getByTestId('probe-card-action-button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('View'); +}); + it(`Displays the correct information for a public probe`, async () => { const probe = probeToExtendedProbe(PUBLIC_PROBE); diff --git a/src/components/ProbeCard/ProbeCard.tsx b/src/components/ProbeCard/ProbeCard.tsx index 0e81c3de1..6824d5561 100644 --- a/src/components/ProbeCard/ProbeCard.tsx +++ b/src/components/ProbeCard/ProbeCard.tsx @@ -15,8 +15,8 @@ import { ProbeLabels } from './ProbeLabels'; import { ProbeStatus } from './ProbeStatus'; export const ProbeCard = ({ probe }: { probe: ExtendedProbe }) => { - const canEdit = useCanEditProbe(probe); - const probeEditHref = generateRoutePath(canEdit ? ROUTES.EditProbe : ROUTES.ViewProbe, { id: probe.id! }); + const { canWriteProbes } = useCanEditProbe(probe); + const probeEditHref = generateRoutePath(canWriteProbes ? ROUTES.EditProbe : ROUTES.ViewProbe, { id: probe.id! }); const labelsString = labelsToString(probe.labels); const styles = useStyles2(getStyles2); @@ -55,7 +55,7 @@ export const ProbeCard = ({ probe }: { probe: ExtendedProbe }) => { - {canEdit ? ( + {canWriteProbes ? ( <> { await assertUneditable(); }); +it('the form is uneditable when logged in as a RBAC viewer', async () => { + runTestAsRBACReader(); + await renderProbeEditor(); + await assertUneditable(); +}); + it('the form actions are unavailable when viewing a public probe', async () => { await renderProbeEditor({ probe: PUBLIC_PROBE }); await assertNoActions(); @@ -124,6 +130,12 @@ it('should render the form in read mode when passing `forceReadMode`', async () await assertUneditable(); }); +it('the form actions are unavailable as a RBAC viewer', async () => { + runTestAsRBACReader(); + await renderProbeEditor(); + await assertNoActions(); +}); + async function assertUneditable() { const nameInput = await screen.findByLabelText('Probe Name', { exact: false }); expect(nameInput).toBeDisabled(); diff --git a/src/components/ProbeEditor/ProbeEditor.tsx b/src/components/ProbeEditor/ProbeEditor.tsx index 4a91e7508..a13e41874 100644 --- a/src/components/ProbeEditor/ProbeEditor.tsx +++ b/src/components/ProbeEditor/ProbeEditor.tsx @@ -36,8 +36,8 @@ export const ProbeEditor = ({ forceViewMode, // When true, the form is in view mode }: ProbeEditorProps) => { const styles = useStyles2(getStyles); - const canEdit = useCanEditProbe(probe); - const writeMode = canEdit && !forceViewMode; + const { canWriteProbes } = useCanEditProbe(probe); + const writeMode = canWriteProbes && !forceViewMode; const form = useForm({ defaultValues: probe, resolver: zodResolver(ProbeSchema) }); const { latitude, longitude } = form.watch(); const handleSubmit = form.handleSubmit((formValues: Probe) => onSubmit(formValues)); @@ -164,7 +164,7 @@ export const ProbeEditor = ({ /> - {canEdit && disabled={!writeMode} labelDestination={'probe'} />} + {canWriteProbes && disabled={!writeMode} labelDestination={'probe'} />}
Capabilities
- {canEdit && ( + {canWriteProbes && ( <>
- {canEdit && ( + {canWriteProbes && ( @@ -109,12 +109,17 @@ const AlertingPageContent = () => { key={`${alertRule.alert}-${index}`} rule={alertRule} onSubmit={getUpdateRules(index)} - canEdit={canEdit} + canEdit={canWriteAlerts} /> ))} {Boolean(alertRules?.length) ? ( - @@ -145,7 +150,7 @@ const AlertingPageContent = () => { const InsufficientPermissions = () => { return ( - You do not have the appropriate permissions to read the alert rules. To request access contact your administrator. + Contact your administrator to ensure you have Query access to the metrics datasource. ); }; diff --git a/src/page/CheckList/components/BulkActions.tsx b/src/page/CheckList/components/BulkActions.tsx index 804995e32..b2490971a 100644 --- a/src/page/CheckList/components/BulkActions.tsx +++ b/src/page/CheckList/components/BulkActions.tsx @@ -4,8 +4,8 @@ import { Button, ButtonCascader, ConfirmModal, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; import { Check } from 'types'; +import { getUserPermissions } from 'data/permissions'; import { useBulkDeleteChecks, useBulkUpdateChecks } from 'data/useChecks'; -import { useCanWriteSM } from 'hooks/useDSPermission'; import { BulkActionsModal } from 'page/CheckList/components/BulkActionsModal'; interface BulkActionsProps { @@ -19,7 +19,7 @@ enum BulkAction { } export const BulkActions = ({ checks, onResolved }: BulkActionsProps) => { - const canEdit = useCanWriteSM(); + const { canWriteChecks, canDeleteChecks } = getUserPermissions(); const styles = useStyles2(getStyles); const [bulkEditAction, setBulkEditAction] = useState(null); const [showDeleteModal, setShowDeleteModal] = useState(false); @@ -62,7 +62,7 @@ export const BulkActions = ({ checks, onResolved }: BulkActionsProps) => { value: BulkAction.Remove, }, ]} - disabled={!canEdit} + disabled={!canWriteChecks} onChange={(value: string[]) => { const action = value[0] as BulkAction; setBulkEditAction(action); @@ -71,10 +71,22 @@ export const BulkActions = ({ checks, onResolved }: BulkActionsProps) => { Bulk Edit Probes )} - - @@ -83,7 +95,7 @@ export const BulkActions = ({ checks, onResolved }: BulkActionsProps) => { variant="destructive" fill="text" onClick={() => setShowDeleteModal(true)} - disabled={!canEdit} + disabled={!canDeleteChecks} > Delete diff --git a/src/page/CheckList/components/CheckItemActionButtons.tsx b/src/page/CheckList/components/CheckItemActionButtons.tsx index 9075f78e7..d066f840a 100644 --- a/src/page/CheckList/components/CheckItemActionButtons.tsx +++ b/src/page/CheckList/components/CheckItemActionButtons.tsx @@ -7,7 +7,7 @@ import { Check } from 'types'; import { ROUTES } from 'routing/types'; import { generateRoutePath, getRoute } from 'routing/utils'; import { useDeleteCheck } from 'data/useChecks'; -import { useCanReadMetrics, useCanWriteSM } from 'hooks/useDSPermission'; +import { getUserPermissions } from 'data/permissions'; interface CheckItemActionButtonsProps { check: Check; @@ -15,8 +15,7 @@ interface CheckItemActionButtonsProps { } export const CheckItemActionButtons = ({ check, viewDashboardAsIcon }: CheckItemActionButtonsProps) => { - const canEdit = useCanWriteSM(); - const canReadMetrics = useCanReadMetrics(); + const { canReadChecks, canWriteChecks, canDeleteChecks } = getUserPermissions(); const styles = useStyles2(getStyles); const [showDeleteModal, setShowDeleteModal] = useState(false); @@ -24,7 +23,7 @@ export const CheckItemActionButtons = ({ check, viewDashboardAsIcon }: CheckItem return (
- {canReadMetrics && ( + {canReadChecks && ( <> {viewDashboardAsIcon ? ( @@ -54,7 +53,7 @@ export const CheckItemActionButtons = ({ check, viewDashboardAsIcon }: CheckItem tooltip="Delete check" name="trash-alt" onClick={() => setShowDeleteModal(true)} - disabled={!canEdit} + disabled={!canDeleteChecks} /> { - const canEdit = useCanWriteSM(); + const { canWriteChecks, canWriteThresholds } = getUserPermissions(); + const styles = useStyles2(getStyles); const [showThresholdModal, setShowThresholdModal] = useState(false); const hasChecks = checks.length > 0; @@ -95,14 +96,13 @@ export const CheckListHeader = ({ checkFilters={checkFilters} onChange={onFilterChange} /> - {canEdit && ( - <> - - - + {canWriteThresholds && ( + )} + + {canWriteChecks && }
diff --git a/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx b/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx index d6d2ed31d..89082a7d4 100644 --- a/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx +++ b/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx @@ -2,14 +2,14 @@ import React, { useState } from 'react'; import { Alert, Button, Modal, Space, TextLink } from '@grafana/ui'; import { FaroEvent, reportError, reportEvent } from 'faro'; -import { useCanWriteSM } from 'hooks/useDSPermission'; +import { getUserPermissions } from 'data/permissions'; import { useSMDS } from 'hooks/useSMDS'; import { Clipboard } from 'components/Clipboard'; import { ConfigContent } from '../ConfigContent'; export function AccessTokensTab() { - const canCreateAccessToken = useCanWriteSM(); + const { canWriteTokens } = getUserPermissions(); const smDS = useSMDS(); const [showModal, setShowModal] = useState(false); const [error, setError] = useState(); @@ -42,8 +42,8 @@ export function AccessTokensTab() { documentation to learn more about how to interact with the synthetic monitoring API.
); }; @@ -93,6 +97,16 @@ const AlertingPageContent = () => { {alertError} )} + {!canWriteAlerts && ( + + You are not able to edit alerts because you're missing alert.instances.external:write{' '} + permissions + + } + /> + )} {alertRules?.length === 0 && !Boolean(alertError) && (
@@ -147,10 +161,10 @@ const AlertingPageContent = () => { ); }; -const InsufficientPermissions = () => { +const InsufficientPermissions = ({ message }: { message: string | JSX.Element }) => { return ( - Contact your administrator to ensure you have Query access to the metrics datasource. + {message} ); }; diff --git a/src/page/ContactAdminAlert.tsx b/src/page/ContactAdminAlert.tsx index d9a7394ba..00bf2cb88 100644 --- a/src/page/ContactAdminAlert.tsx +++ b/src/page/ContactAdminAlert.tsx @@ -8,7 +8,7 @@ export const ContactAdminAlert = ({ permissions }: { permissions?: string[] }) =
You are missing the following permissions:
{permissions.map((permission) => ( - {permission} + {permission} ))}
)} From c25791cc0f700f4fc036ac879fa970989c888ee5 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Thu, 28 Nov 2024 16:27:42 -0300 Subject: [PATCH 18/30] fix: lint --- src/page/AlertingPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/page/AlertingPage.tsx b/src/page/AlertingPage.tsx index e0f018dd4..9319f791e 100644 --- a/src/page/AlertingPage.tsx +++ b/src/page/AlertingPage.tsx @@ -101,7 +101,7 @@ const AlertingPageContent = () => { - You are not able to edit alerts because you're missing alert.instances.external:write{' '} + You are not able to edit alerts because you are missing alert.instances.external:write{' '} permissions } From a63e390c44ca19012c3eca78bb209c4544b24d41 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Thu, 28 Nov 2024 16:49:41 -0300 Subject: [PATCH 19/30] fix: consolidate enable/disable plugin actions into a single write one --- src/components/AppInitializer.tsx | 4 ++-- src/components/ConfigActions.tsx | 8 ++++---- src/configPage/PluginConfigPage/PluginConfigPage.tsx | 10 ++++++---- src/data/permissions.ts | 3 +-- src/datasource/plugin.json | 2 +- src/hooks/usePluginPermissions.ts | 7 ++----- src/plugin.json | 5 ++--- src/test/fixtures/rbacPermissions.ts | 3 +-- src/types.ts | 2 +- 9 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/components/AppInitializer.tsx b/src/components/AppInitializer.tsx index b66f66201..e27613f8b 100644 --- a/src/components/AppInitializer.tsx +++ b/src/components/AppInitializer.tsx @@ -21,10 +21,10 @@ interface Props { export const AppInitializer = ({ redirectTo, buttonText }: PropsWithChildren) => { const { jsonData } = useMeta(); const styles = useStyles2(getStyles); - const { canEnablePlugin } = getUserPermissions(); + const { canWritePlugin } = getUserPermissions(); const meetsMinPermissions = hasGlobalPermission(`datasources:read`); - const canInitialize = canEnablePlugin && hasGlobalPermission(`datasources:create`); + const canInitialize = canWritePlugin && hasGlobalPermission(`datasources:create`); const { error, diff --git a/src/components/ConfigActions.tsx b/src/components/ConfigActions.tsx index 6347b5234..36ae2f249 100644 --- a/src/components/ConfigActions.tsx +++ b/src/components/ConfigActions.tsx @@ -13,7 +13,7 @@ export const ConfigActions = ({ initialized }: { initialized?: boolean }) => { const [showDisableModal, setShowDisableModal] = useState(false); const meta = useMeta(); - const { canEnablePlugin, canDisablePlugin, canEditPlugin } = usePluginPermissions(); + const { canWritePlugin } = usePluginPermissions(); const handleEnable = async () => { await getBackendSrv() @@ -29,11 +29,11 @@ export const ConfigActions = ({ initialized }: { initialized?: boolean }) => { window.location.reload(); }; - if (!canEditPlugin) { + if (!canWritePlugin) { return null; } - if (!meta.enabled && canEnablePlugin) { + if (!meta.enabled && canWritePlugin) { return ( ); diff --git a/src/components/AppInitializer.tsx b/src/components/AppInitializer.tsx index e27613f8b..816029416 100644 --- a/src/components/AppInitializer.tsx +++ b/src/components/AppInitializer.tsx @@ -41,7 +41,7 @@ export const AppInitializer = ({ redirectTo, buttonText }: PropsWithChildren; + return ; } if (!canInitialize) { diff --git a/src/components/ConfigActions.tsx b/src/components/ConfigActions.tsx index 36ae2f249..18dc14214 100644 --- a/src/components/ConfigActions.tsx +++ b/src/components/ConfigActions.tsx @@ -5,7 +5,7 @@ import { Button, LinkButton } from '@grafana/ui'; import { ROUTES } from 'routing/types'; import { getRoute } from 'routing/utils'; import { useMeta } from 'hooks/useMeta'; -import { usePluginPermissions } from 'hooks/usePluginPermissions'; +import { usePluginPermissionCanWrite } from 'hooks/usePluginPermissionsCanWrite'; import { DisablePluginModal } from './DisablePluginModal'; @@ -13,7 +13,7 @@ export const ConfigActions = ({ initialized }: { initialized?: boolean }) => { const [showDisableModal, setShowDisableModal] = useState(false); const meta = useMeta(); - const { canWritePlugin } = usePluginPermissions(); + const canWritePlugin = usePluginPermissionCanWrite(); const handleEnable = async () => { await getBackendSrv() diff --git a/src/components/LinkedDatasourceView.tsx b/src/components/LinkedDatasourceView.tsx index 720a0b017..0d2d7e803 100644 --- a/src/components/LinkedDatasourceView.tsx +++ b/src/components/LinkedDatasourceView.tsx @@ -16,14 +16,14 @@ export const LinkedDatasourceView = ({ type }: LinkedDatasourceViewProps) => { const logsDS = useLogsDS(); const smDS = useSMDS(); - const { canWriteSM: canEditSM } = getUserPermissions(); + const { canWriteSM } = getUserPermissions(); const canEditLogs = useCanWriteLogs(); const canEditMetrics = useCanWriteMetrics(); const canEditMap = { prometheus: canEditMetrics, loki: canEditLogs, - 'synthetic-monitoring-datasource': canEditSM, + 'synthetic-monitoring-datasource': canWriteSM, }; const dsMap = { diff --git a/src/configPage/PluginConfigPage/PluginConfigPage.tsx b/src/configPage/PluginConfigPage/PluginConfigPage.tsx index 84ec03cd9..d7818cfb6 100644 --- a/src/configPage/PluginConfigPage/PluginConfigPage.tsx +++ b/src/configPage/PluginConfigPage/PluginConfigPage.tsx @@ -8,7 +8,7 @@ import { ProvisioningJsonData } from 'types'; import { ROUTES } from 'routing/types'; import { getRoute } from 'routing/utils'; import type { SMDataSource } from 'datasource/DataSource'; -import { usePluginPermissions } from 'hooks/usePluginPermissions'; +import { usePluginPermissionCanWrite } from 'hooks/usePluginPermissionsCanWrite'; import { DataSourceInfo, useLinkedDataSources } from './PluginConfigPage.hooks'; import { enablePlugin } from './PluginConfigPage.utils'; @@ -38,7 +38,7 @@ export function PluginConfigPage({ const appHomeUrl = getRoute(ROUTES.Home); const [isEnabling, setIsEnabling] = useState(false); - const { canWritePlugin } = usePluginPermissions(); + const canWritePlugin = usePluginPermissionCanWrite(); const { api, linked, isLoading } = useLinkedDataSources(); const initialized = isInitialized(api?.dataSource); diff --git a/src/hooks/useAlertPermissions.ts b/src/hooks/useAlertAccessControl.ts similarity index 93% rename from src/hooks/useAlertPermissions.ts rename to src/hooks/useAlertAccessControl.ts index e36d48e01..838557af3 100644 --- a/src/hooks/useAlertPermissions.ts +++ b/src/hooks/useAlertAccessControl.ts @@ -3,7 +3,7 @@ import { getUserPermissions } from 'data/permissions'; import { useDSPermission } from './useDSPermission'; import { useMetricsDS } from './useMetricsDS'; -export function useAlertPermissions() { +export function useAlertAccessControl() { const { canReadAlerts, canWriteAlerts, canDeleteAlerts } = getUserPermissions(); const canEditAlertInDs = useDSPermission(`metrics`, `alert.instances.external:write`); diff --git a/src/hooks/usePluginPermissions.ts b/src/hooks/usePluginPermissionsCanWrite.ts similarity index 53% rename from src/hooks/usePluginPermissions.ts rename to src/hooks/usePluginPermissionsCanWrite.ts index 421c70369..da046c01f 100644 --- a/src/hooks/usePluginPermissions.ts +++ b/src/hooks/usePluginPermissionsCanWrite.ts @@ -1,10 +1,8 @@ import { hasGlobalPermission } from 'utils'; import { getUserPermissions } from 'data/permissions'; -export function usePluginPermissions() { +export function usePluginPermissionCanWrite() { const { canWritePlugin } = getUserPermissions(); - return { - canWritePlugin: hasGlobalPermission(`plugins:write`) && canWritePlugin, - }; + return hasGlobalPermission(`plugins:write`) && canWritePlugin; } diff --git a/src/page/AlertingPage.tsx b/src/page/AlertingPage.tsx index 6038fd04d..ecf6edbd6 100644 --- a/src/page/AlertingPage.tsx +++ b/src/page/AlertingPage.tsx @@ -5,11 +5,13 @@ import { Alert, Button, Modal, Spinner, Stack, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; import { AlertFormValues, AlertRule } from 'types'; -import { useAlertPermissions } from 'hooks/useAlertPermissions'; +import { useAlertAccessControl } from 'hooks/useAlertAccessControl'; import { useAlerts } from 'hooks/useAlerts'; import { transformAlertFormValues } from 'components/alertingTransformations'; import { AlertRuleForm } from 'components/AlertRuleForm'; +import { ContactAdminAlert } from './ContactAdminAlert'; + type SplitAlertRules = { recordingRules: AlertRule[]; alertingRules: AlertRule[]; @@ -25,7 +27,7 @@ export const AlertingPage = () => { const Alerting = () => { const styles = useStyles2(getStyles); - const { canReadAlerts } = useAlertPermissions(); + const { canReadAlerts } = useAlertAccessControl(); return (
@@ -42,7 +44,7 @@ const Alerting = () => { {canReadAlerts ? ( ) : ( - + )}
); @@ -53,7 +55,7 @@ const AlertingPageContent = () => { const [updatingDefaultRules, setUpdatingDefaultRules] = useState(false); const [showResetModal, setShowResetModal] = useState(false); const { alertRules, setDefaultRules, setRules, alertError } = useAlerts(); - const { canWriteAlerts, hasWriterRole } = useAlertPermissions(); + const { canWriteAlerts, hasWriterRole } = useAlertAccessControl(); const { recordingRules, alertingRules } = alertRules?.reduce( (rules, currentRule) => { @@ -98,14 +100,7 @@ const AlertingPageContent = () => { )} {hasWriterRole && !canWriteAlerts && ( - - You are not able to edit alerts because you are missing alert.instances.external:write{' '} - permissions - - } - /> + )} {alertRules?.length === 0 && !Boolean(alertError) && (
@@ -161,14 +156,6 @@ const AlertingPageContent = () => { ); }; -const InsufficientPermissions = ({ message }: { message: string | JSX.Element }) => { - return ( - - {message} - - ); -}; - const getStyles = (theme: GrafanaTheme2) => ({ emptyCard: css({ backgroundColor: theme.colors.background.secondary, diff --git a/src/page/ContactAdminAlert.tsx b/src/page/ContactAdminAlert.tsx index 00bf2cb88..53467727c 100644 --- a/src/page/ContactAdminAlert.tsx +++ b/src/page/ContactAdminAlert.tsx @@ -1,13 +1,23 @@ import React from 'react'; -import { Alert, Stack } from '@grafana/ui'; +import { Alert, AlertVariant, Stack } from '@grafana/ui'; -export const ContactAdminAlert = ({ permissions }: { permissions?: string[] }) => { +interface ContactAdminAlertProps { + title?: string; + missingPermissions?: string[]; + severity?: AlertVariant; +} + +export const ContactAdminAlert = ({ + title = 'Contact your administrator to get you started.', + missingPermissions, + severity = 'info', +}: ContactAdminAlertProps) => { return ( - - {permissions && ( + + {missingPermissions && (
You are missing the following permissions:
- {permissions.map((permission) => ( + {missingPermissions.map((permission) => ( {permission} ))}
diff --git a/src/page/UnauthorizedPage.tsx b/src/page/UnauthorizedPage.tsx index 0b5093689..a5db6da44 100644 --- a/src/page/UnauthorizedPage.tsx +++ b/src/page/UnauthorizedPage.tsx @@ -10,7 +10,7 @@ interface UnauthorizedPageProps { export const UnauthorizedPage = ({ permissions }: UnauthorizedPageProps) => { return ( - + ); }; From 3e8822ebf020a1aa4fdcf3e07d6c6e5fadff66ae Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 14:40:11 -0300 Subject: [PATCH 23/30] chore: remove ConfigActions as not user after rebase with main --- src/components/ConfigActions.test.tsx | 61 --------------------------- src/components/ConfigActions.tsx | 60 -------------------------- 2 files changed, 121 deletions(-) delete mode 100644 src/components/ConfigActions.test.tsx delete mode 100644 src/components/ConfigActions.tsx diff --git a/src/components/ConfigActions.test.tsx b/src/components/ConfigActions.test.tsx deleted file mode 100644 index 514290f93..000000000 --- a/src/components/ConfigActions.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { screen } from '@testing-library/react'; -import { render } from 'test/render'; - -import { hasGlobalPermission } from 'utils'; -import { ConfigActions } from 'components/ConfigActions'; - -jest.mock('utils', () => { - return { - ...jest.requireActual('utils'), - hasGlobalPermission: jest.fn().mockReturnValue(true), - }; -}); - -it('shows disable option when activated', async () => { - render(); - - const disableButton = await screen.findByText('Disable synthetic monitoring'); - expect(disableButton).toBeInTheDocument(); -}); - -it('shows enable action when disabled', async () => { - render(, { - meta: { - enabled: false, - }, - }); - - const enableButton = await screen.findByText('Enable plugin'); - expect(enableButton).toBeInTheDocument(); -}); - -it('shows setup action when not intialized', async () => { - render(); - const setupButton = await screen.findByText('Setup'); - expect(setupButton).toBeInTheDocument(); -}); - -it(`doesn't show any config actions when the user doesn't have write permissions`, async () => { - jest.mocked(hasGlobalPermission).mockReturnValue(false); - - render(); - - expect(screen.queryByText('Disable synthetic monitoring')).not.toBeInTheDocument(); - expect(screen.queryByText('Enable plugin')).not.toBeInTheDocument(); - expect(screen.queryByText('Setup')).not.toBeInTheDocument(); -}); - -it(`doesn't show any config actions when the user doesn't have write permissions and meta enabled is false`, async () => { - jest.mocked(hasGlobalPermission).mockReturnValue(false); - - render(, { - meta: { - enabled: false, - }, - }); - - expect(screen.queryByText('Disable synthetic monitoring')).not.toBeInTheDocument(); - expect(screen.queryByText('Enable plugin')).not.toBeInTheDocument(); - expect(screen.queryByText('Setup')).not.toBeInTheDocument(); -}); diff --git a/src/components/ConfigActions.tsx b/src/components/ConfigActions.tsx deleted file mode 100644 index 18dc14214..000000000 --- a/src/components/ConfigActions.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useState } from 'react'; -import { getBackendSrv } from '@grafana/runtime'; -import { Button, LinkButton } from '@grafana/ui'; - -import { ROUTES } from 'routing/types'; -import { getRoute } from 'routing/utils'; -import { useMeta } from 'hooks/useMeta'; -import { usePluginPermissionCanWrite } from 'hooks/usePluginPermissionsCanWrite'; - -import { DisablePluginModal } from './DisablePluginModal'; - -export const ConfigActions = ({ initialized }: { initialized?: boolean }) => { - const [showDisableModal, setShowDisableModal] = useState(false); - const meta = useMeta(); - - const canWritePlugin = usePluginPermissionCanWrite(); - - const handleEnable = async () => { - await getBackendSrv() - .fetch({ - url: `/api/plugins/${meta.id}/settings`, - method: 'POST', - data: { - enabled: true, - pinned: true, - }, - }) - .toPromise(); - window.location.reload(); - }; - - if (!canWritePlugin) { - return null; - } - - if (!meta.enabled && canWritePlugin) { - return ( - - ); - } - - if (initialized && canWritePlugin) { - return ( - <> - - setShowDisableModal(false)} /> - - ); - } - - return ( - - Setup - - ); -}; From 6d8702a72062f2c6d7dde5c0aa78c0832109aaa3 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 14:52:28 -0300 Subject: [PATCH 24/30] fix: add message when missing access token write permission --- src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx | 8 ++++++++ src/page/ContactAdminAlert.tsx | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx b/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx index 89082a7d4..8a23ed514 100644 --- a/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx +++ b/src/page/ConfigPageLayout/tabs/AccessTokensTab.tsx @@ -5,6 +5,7 @@ import { FaroEvent, reportError, reportEvent } from 'faro'; import { getUserPermissions } from 'data/permissions'; import { useSMDS } from 'hooks/useSMDS'; import { Clipboard } from 'components/Clipboard'; +import { ContactAdminAlert } from 'page/ContactAdminAlert'; import { ConfigContent } from '../ConfigContent'; @@ -30,6 +31,13 @@ export function AccessTokensTab() { return ( + {!canWriteTokens && ( + + )} + You can use an SM access token to authenticate with the synthetic monitoring api. Check out the{' '} diff --git a/src/page/ContactAdminAlert.tsx b/src/page/ContactAdminAlert.tsx index 53467727c..b03036bf2 100644 --- a/src/page/ContactAdminAlert.tsx +++ b/src/page/ContactAdminAlert.tsx @@ -16,7 +16,7 @@ export const ContactAdminAlert = ({ {missingPermissions && ( -
You are missing the following permissions:
+
You are missing the following permission(s):
{missingPermissions.map((permission) => ( {permission} ))} From 2f0fa83f18daa0556e5c334f84f673e79d315b33 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 14:54:37 -0300 Subject: [PATCH 25/30] fix: remove Access Tokens Reader role --- src/plugin.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 79b50cbc5..d33a5b1d6 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -210,19 +210,6 @@ }, "grants": ["Admin"] }, - { - "role": { - "name": "Access tokens reader", - "description": "Read access tokens in the Synthetic Monitoring app", - "permissions": [ - { "action": "plugins.app:access", "scope": "plugins:id:grafana-synthetic-monitoring-app" }, - { "action": "grafana-synthetic-monitoring-app:read" }, - - { "action": "grafana-synthetic-monitoring-app.access-tokens:read" } - ] - }, - "grants": ["Admin", "Editor"] - }, { "role": { "name": "Admin", From 3a0e12afd2670f38e10767aa977cc7385dad95b3 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 15:05:27 -0300 Subject: [PATCH 26/30] fix: remove access-tokens: read and delete actions - There's no use for them right now --- src/data/permissions.ts | 2 -- src/plugin.json | 11 +++-------- src/test/fixtures/rbacPermissions.ts | 5 ----- src/types.ts | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/data/permissions.ts b/src/data/permissions.ts index 42529c0a0..b5cf42d41 100644 --- a/src/data/permissions.ts +++ b/src/data/permissions.ts @@ -46,9 +46,7 @@ export const getUserPermissions = () => ({ canReadThresholds: isUserActionAllowed('grafana-synthetic-monitoring-app.thresholds:read', OrgRole.Viewer), canWriteThresholds: isUserActionAllowed('grafana-synthetic-monitoring-app.thresholds:write', OrgRole.Editor), - canReadTokens: isUserActionAllowed('grafana-synthetic-monitoring-app.access-tokens:read', OrgRole.Admin), canWriteTokens: isUserActionAllowed('grafana-synthetic-monitoring-app.access-tokens:write', OrgRole.Admin), - canDeleteTokens: isUserActionAllowed('grafana-synthetic-monitoring-app.access-tokens:delete', OrgRole.Admin), canWritePlugin: isUserActionAllowed('grafana-synthetic-monitoring-app.plugin:write', OrgRole.Admin), diff --git a/src/plugin.json b/src/plugin.json index d33a5b1d6..a924a56c9 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -204,8 +204,7 @@ { "action": "grafana-synthetic-monitoring-app:read" }, { "action": "grafana-synthetic-monitoring-app:write" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:write" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } + { "action": "grafana-synthetic-monitoring-app.access-tokens:write" } ] }, "grants": ["Admin"] @@ -229,12 +228,10 @@ { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:read" }, { "action": "grafana-synthetic-monitoring-app.checks:delete" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, - { "action": "grafana-synthetic-monitoring-app.thresholds:delete" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:delete" } + { "action": "grafana-synthetic-monitoring-app.thresholds:delete" } ] }, "grants": ["Admin"] @@ -256,7 +253,6 @@ { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:read" }, { "action": "grafana-synthetic-monitoring-app.checks:delete" }, { "action": "grafana-synthetic-monitoring-app.probes:delete" }, { "action": "grafana-synthetic-monitoring-app.alerts:delete" }, @@ -276,8 +272,7 @@ { "action": "grafana-synthetic-monitoring-app.checks:read" }, { "action": "grafana-synthetic-monitoring-app.probes:read" }, { "action": "grafana-synthetic-monitoring-app.alerts:read" }, - { "action": "grafana-synthetic-monitoring-app.thresholds:read" }, - { "action": "grafana-synthetic-monitoring-app.access-tokens:read" } + { "action": "grafana-synthetic-monitoring-app.thresholds:read" } ] }, "grants": ["Viewer"] diff --git a/src/test/fixtures/rbacPermissions.ts b/src/test/fixtures/rbacPermissions.ts index ecd9bfacd..5d65d2179 100644 --- a/src/test/fixtures/rbacPermissions.ts +++ b/src/test/fixtures/rbacPermissions.ts @@ -11,12 +11,10 @@ export const FULL_ADMIN_ACCESS = { 'grafana-synthetic-monitoring-app.probes:read': true, 'grafana-synthetic-monitoring-app.alerts:read': true, 'grafana-synthetic-monitoring-app.thresholds:read': true, - 'grafana-synthetic-monitoring-app.access-tokens:read': true, 'grafana-synthetic-monitoring-app.checks:delete': true, 'grafana-synthetic-monitoring-app.probes:delete': true, 'grafana-synthetic-monitoring-app.alerts:delete': true, 'grafana-synthetic-monitoring-app.thresholds:delete': true, - 'grafana-synthetic-monitoring-app.access-tokens:delete': true, }; export const FULL_WRITER_ACCESS = { @@ -31,12 +29,10 @@ export const FULL_WRITER_ACCESS = { 'grafana-synthetic-monitoring-app.probes:read': true, 'grafana-synthetic-monitoring-app.alerts:read': true, 'grafana-synthetic-monitoring-app.thresholds:read': true, - 'grafana-synthetic-monitoring-app.access-tokens:read': true, 'grafana-synthetic-monitoring-app.checks:delete': true, 'grafana-synthetic-monitoring-app.probes:delete': true, 'grafana-synthetic-monitoring-app.alerts:delete': true, 'grafana-synthetic-monitoring-app.thresholds:delete': true, - 'grafana-synthetic-monitoring-app.access-tokens:delete': true, }; export const FULL_READONLY_ACCESS = { @@ -45,5 +41,4 @@ export const FULL_READONLY_ACCESS = { 'grafana-synthetic-monitoring-app.probes:read': true, 'grafana-synthetic-monitoring-app.alerts:read': true, 'grafana-synthetic-monitoring-app.thresholds:read': true, - 'grafana-synthetic-monitoring-app.access-tokens:read': true, }; diff --git a/src/types.ts b/src/types.ts index d3d83b2ce..d3d2fe6b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -807,5 +807,5 @@ export type PluginPermissions = | `${PermissionBase}.probes:${'read' | 'write' | 'delete'}` | `${PermissionBase}.alerts:${'read' | 'write' | 'delete'}` | `${PermissionBase}.thresholds:${'read' | 'write' | 'delete'}` - | `${PermissionBase}.access-tokens:${'read' | 'write' | 'delete'}` + | `${PermissionBase}.access-tokens:${'write'}` | `${PermissionBase}.plugin:${'write'}`; From 672adec46d1cf69f2aae22284a0b985e8a75815f Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 17:24:04 -0300 Subject: [PATCH 27/30] fix: list missing permissions to initialize plugin --- src/components/AppInitializer.tsx | 8 +++--- src/page/ContactAdminAlert.tsx | 41 ++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/components/AppInitializer.tsx b/src/components/AppInitializer.tsx index 816029416..26e801119 100644 --- a/src/components/AppInitializer.tsx +++ b/src/components/AppInitializer.tsx @@ -23,7 +23,7 @@ export const AppInitializer = ({ redirectTo, buttonText }: PropsWithChildren; } if (!canInitialize) { - return ; + return ( + + ); } return ( diff --git a/src/page/ContactAdminAlert.tsx b/src/page/ContactAdminAlert.tsx index b03036bf2..14da28286 100644 --- a/src/page/ContactAdminAlert.tsx +++ b/src/page/ContactAdminAlert.tsx @@ -1,5 +1,7 @@ import React from 'react'; -import { Alert, AlertVariant, Stack } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, AlertVariant, Stack, useStyles2 } from '@grafana/ui'; +import { css } from '@emotion/css'; interface ContactAdminAlertProps { title?: string; @@ -12,16 +14,33 @@ export const ContactAdminAlert = ({ missingPermissions, severity = 'info', }: ContactAdminAlertProps) => { + const styles = useStyles2(getStyles); return ( - - {missingPermissions && ( - -
You are missing the following permission(s):
- {missingPermissions.map((permission) => ( - {permission} - ))} -
- )} -
+
+ + {missingPermissions && ( + + You need the following permission(s): + + {missingPermissions.map((permission) => ( + + {permission} + + ))} + + + )} + +
); }; + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css({ + textAlign: 'left', + }), + + permission: css({ + width: 'fit-content', + }), +}); From c9437ba7bd8559673cdc0af6cb0f9c82c69be876 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 17:33:14 -0300 Subject: [PATCH 28/30] fix: change Unauthorized page layout - Remove card to get rid of hover effect - Only dispay the relevant message --- src/page/UnauthorizedPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/page/UnauthorizedPage.tsx b/src/page/UnauthorizedPage.tsx index a5db6da44..514d290b1 100644 --- a/src/page/UnauthorizedPage.tsx +++ b/src/page/UnauthorizedPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ContactAdminAlert } from './ContactAdminAlert'; -import { SubsectionWelcomePage } from './SubsectionWelcomePage'; +import { PluginPage } from '@grafana/runtime'; interface UnauthorizedPageProps { permissions: string[]; @@ -9,8 +9,8 @@ interface UnauthorizedPageProps { export const UnauthorizedPage = ({ permissions }: UnauthorizedPageProps) => { return ( - + - + ); }; From 1e36e5ad47a02fc386ad61aed5089dddfd283317 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 17:47:35 -0300 Subject: [PATCH 29/30] fix: restrict terraform access --- src/page/ConfigPageLayout/tabs/TerraformTab.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/page/ConfigPageLayout/tabs/TerraformTab.tsx b/src/page/ConfigPageLayout/tabs/TerraformTab.tsx index 75bc47fb6..7e1d3b87e 100644 --- a/src/page/ConfigPageLayout/tabs/TerraformTab.tsx +++ b/src/page/ConfigPageLayout/tabs/TerraformTab.tsx @@ -10,10 +10,13 @@ import { useTerraformConfig } from 'hooks/useTerraformConfig'; import { Clipboard } from 'components/Clipboard'; import { ConfigContent } from '../ConfigContent'; +import { getUserPermissions } from 'data/permissions'; +import { ContactAdminAlert } from 'page/ContactAdminAlert'; export function TerraformTab() { const { config, checkCommands, probeCommands, error, isLoading } = useTerraformConfig(); const styles = useStyles2(getStyles); + const { canReadChecks, canReadProbes } = getUserPermissions(); useEffect(() => { reportEvent(FaroEvent.SHOW_TERRAFORM_CONFIG); }, []); @@ -22,6 +25,18 @@ export function TerraformTab() { return ; } + if (!canReadChecks && !canReadProbes) { + return ( + + ); + } + return ( {error && } From 955cd9c932f8d3de5505cdbd9bedcc6c61b81298 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Fri, 29 Nov 2024 17:50:05 -0300 Subject: [PATCH 30/30] fix: lint --- src/page/ConfigPageLayout/tabs/TerraformTab.tsx | 4 ++-- src/page/UnauthorizedPage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/page/ConfigPageLayout/tabs/TerraformTab.tsx b/src/page/ConfigPageLayout/tabs/TerraformTab.tsx index 7e1d3b87e..4b5233564 100644 --- a/src/page/ConfigPageLayout/tabs/TerraformTab.tsx +++ b/src/page/ConfigPageLayout/tabs/TerraformTab.tsx @@ -6,12 +6,12 @@ import { css } from '@emotion/css'; import { FaroEvent, reportEvent } from 'faro'; import { ROUTES } from 'routing/types'; import { generateRoutePath } from 'routing/utils'; +import { getUserPermissions } from 'data/permissions'; import { useTerraformConfig } from 'hooks/useTerraformConfig'; import { Clipboard } from 'components/Clipboard'; +import { ContactAdminAlert } from 'page/ContactAdminAlert'; import { ConfigContent } from '../ConfigContent'; -import { getUserPermissions } from 'data/permissions'; -import { ContactAdminAlert } from 'page/ContactAdminAlert'; export function TerraformTab() { const { config, checkCommands, probeCommands, error, isLoading } = useTerraformConfig(); diff --git a/src/page/UnauthorizedPage.tsx b/src/page/UnauthorizedPage.tsx index 514d290b1..f0dd03e68 100644 --- a/src/page/UnauthorizedPage.tsx +++ b/src/page/UnauthorizedPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { PluginPage } from '@grafana/runtime'; import { ContactAdminAlert } from './ContactAdminAlert'; -import { PluginPage } from '@grafana/runtime'; interface UnauthorizedPageProps { permissions: string[];