diff --git a/docs/sources/http_api/dashboard_permissions.md b/docs/sources/http_api/dashboard_permissions.md index 3496b90390d..ae159309870 100644 --- a/docs/sources/http_api/dashboard_permissions.md +++ b/docs/sources/http_api/dashboard_permissions.md @@ -19,6 +19,136 @@ The permission levels for the permission field: ## Get permissions for a dashboard +`GET /api/dashboards/uid/:uid/permissions` + +Gets all existing permissions for the dashboard with the given `uid`. + +**Example request**: + +```http +GET /api/dashboards/uid/dHEquNzGz/permissions HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response** + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 551 + +[ + { + "id": 1, + "dashboardId": -1, + "created": "2017-06-20T02:00:00+02:00", + "updated": "2017-06-20T02:00:00+02:00", + "userId": 0, + "userLogin": "", + "userEmail": "", + "teamId": 0, + "team": "", + "role": "Viewer", + "permission": 1, + "permissionName": "View", + "uid": "dHEquNzGz", + "title": "", + "slug": "", + "isFolder": false, + "url": "" + }, + { + "id": 2, + "dashboardId": -1, + "created": "2017-06-20T02:00:00+02:00", + "updated": "2017-06-20T02:00:00+02:00", + "userId": 0, + "userLogin": "", + "userEmail": "", + "teamId": 0, + "team": "", + "role": "Editor", + "permission": 2, + "permissionName": "Edit", + "uid": "dHEquNzGz", + "title": "", + "slug": "", + "isFolder": false, + "url": "" + } +] +``` + +Status Codes: + +- **200** - Ok +- **401** - Unauthorized +- **403** - Access denied +- **404** - Dashboard not found + +## Update permissions for a dashboard + +`POST /api/dashboards/uid/:uid/permissions` + +Updates permissions for a dashboard. This operation will remove existing permissions if they're not included in the request. + +**Example request**: + +```http +POST /api/dashboards/uid/dHEquNzGz/permissions +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "items": [ + { + "role": "Viewer", + "permission": 1 + }, + { + "role": "Editor", + "permission": 2 + }, + { + "teamId": 1, + "permission": 1 + }, + { + "userId": 11, + "permission": 4 + } + ] +} +``` + +JSON body schema: + +- **items** - The permission items to add/update. Items that are omitted from the list will be removed. + +**Example response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 35 + +{"message":"Dashboard permissions updated"} +``` + +Status Codes: + +- **200** - Ok +- **401** - Unauthorized +- **403** - Access denied +- **404** - Dashboard not found + +## Get permissions for a dashboard by id + +> **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new dashboard permissions API](#get-permissions-for-a-dashboard). + `GET /api/dashboards/id/:dashboardId/permissions` Gets all existing permissions for the dashboard with the given `dashboardId`. @@ -88,7 +218,9 @@ Status Codes: - **403** - Access denied - **404** - Dashboard not found -## Update permissions for a dashboard +## Update permissions for a dashboard by id + +> **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new dashboard permissions API](#update-permissions-for-a-dashboard). `POST /api/dashboards/id/:dashboardId/permissions` diff --git a/pkg/api/api.go b/pkg/api/api.go index a7de6111fab..24883640e05 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -354,6 +354,12 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) { dashboardRoute.Get("/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsRead)), routing.Wrap(hs.GetDashboard)) dashboardRoute.Delete("/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsDelete)), routing.Wrap(hs.DeleteDashboardByUID)) + dashboardRoute.Group("/uid/:uid", func(dashUidRoute routing.RouteRegister) { + dashUidRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { + dashboardPermissionRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsPermissionsRead)), routing.Wrap(hs.GetDashboardPermissionList)) + dashboardPermissionRoute.Post("/", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsPermissionsWrite)), routing.Wrap(hs.UpdateDashboardPermissions)) + }) + }) if hs.ThumbService != nil { dashboardRoute.Get("/uid/:uid/img/:kind/:theme", hs.ThumbService.GetImage) @@ -371,6 +377,7 @@ func (hs *HTTPServer) registerRoutes() { dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard)) dashboardRoute.Get("/tags", hs.GetDashboardTags) + // Deprecated: use /uid/:uid API instead. dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) { dashIdRoute.Get("/versions", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersions)) dashIdRoute.Get("/versions/:id", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersion)) diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go index 0415fe0262e..45d105c9e4d 100644 --- a/pkg/api/dashboard_permission.go +++ b/pkg/api/dashboard_permission.go @@ -17,16 +17,25 @@ import ( ) func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response.Response { - dashID, err := strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + var dashID int64 + var err error + dashUID := web.Params(c.Req)[":uid"] + if dashUID == "" { + dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + } } - _, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, "") + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, dashUID) if rsp != nil { return rsp } + if dashID == 0 { + dashID = dash.Id + } + g := guardian.New(c.Req.Context(), dashID, c.OrgId, c.SignedInUser) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { @@ -60,6 +69,8 @@ func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response. } func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response.Response { + var dashID int64 + var err error apiCmd := dtos.UpdateDashboardAclCommand{} if err := web.Bind(c.Req, &apiCmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) @@ -68,16 +79,23 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response. return response.Error(400, err.Error(), err) } - dashID, err := strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + dashUID := web.Params(c.Req)[":uid"] + if dashUID == "" { + dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + } } - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, "") + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, 0, dashUID) if rsp != nil { return rsp } + if dashUID != "" { + dashID = dash.Id + } + g := guardian.New(c.Req.Context(), dashID, c.OrgId, c.SignedInUser) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { return dashboardGuardianResponse(err) diff --git a/pkg/api/docs/definitions/dashboard.go b/pkg/api/docs/definitions/dashboard.go index 1c261a7da95..a115763cf25 100644 --- a/pkg/api/docs/definitions/dashboard.go +++ b/pkg/api/docs/definitions/dashboard.go @@ -101,7 +101,7 @@ import ( // 422: unprocessableEntityError // 500: internalServerError -// swagger:parameters getDashboardByUID deleteDashboardByUID +// swagger:parameters getDashboardByUID deleteDashboardByUID getDashboardPermissionsWithUid postDashboardPermissionsWithUid type UID struct { // in:path // required:true diff --git a/pkg/api/docs/definitions/dashboard_permissions.go b/pkg/api/docs/definitions/dashboard_permissions.go index 940638dfd1f..46ae3e96865 100644 --- a/pkg/api/docs/definitions/dashboard_permissions.go +++ b/pkg/api/docs/definitions/dashboard_permissions.go @@ -9,6 +9,10 @@ import ( // // Gets all existing permissions for the given dashboard. // +// Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsWithUid) instead +// +// Deprecated: true +// // Responses: // 200: getDashboardPermissionsResponse // 401: unauthorisedError @@ -20,6 +24,35 @@ import ( // // Updates permissions for a dashboard. // +// Please refer to [updated API](#/dashboard_permissions/postDashboardPermissionsWithUid) instead +// +// This operation will remove existing permissions if they’re not included in the request. +// +// Deprecated: true +// +// Responses: +// 200: okResponse +// 400: badRequestError +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + +// swagger:route GET /dashboards/uid/{uid}/permissions dashboard_permissions getDashboardPermissionsWithUid +// +// Gets all existing permissions for the given dashboard. +// +// Responses: +// 200: getDashboardPermissionsResponse +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + +// swagger:route POST /dashboards/uid/{uid}/permissions dashboard_permissions postDashboardPermissionsWithUid +// +// Updates permissions for a dashboard. +// // This operation will remove existing permissions if they’re not included in the request. // // Responses: @@ -30,7 +63,7 @@ import ( // 404: notFoundError // 500: internalServerError -// swagger:parameters postDashboardPermissions updateFolderPermissions +// swagger:parameters postDashboardPermissions updateFolderPermissions postDashboardPermissionsWithUid type PostDashboardPermissionsParam struct { // in:body // required:true diff --git a/public/api-merged.json b/public/api-merged.json index 00fd28dd229..51601635d42 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -3564,9 +3564,11 @@ }, "/dashboards/id/{DashboardID}/permissions": { "get": { + "description": "Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsWithUid) instead", "tags": ["dashboard_permissions"], "summary": "Gets all existing permissions for the given dashboard.", "operationId": "getDashboardPermissions", + "deprecated": true, "parameters": [ { "type": "integer", @@ -3595,10 +3597,11 @@ } }, "post": { - "description": "This operation will remove existing permissions if they’re not included in the request.", + "description": "Please refer to [updated API](#/dashboard_permissions/postDashboardPermissionsWithUid) instead\n\nThis operation will remove existing permissions if they’re not included in the request.", "tags": ["dashboard_permissions"], "summary": "Updates permissions for a dashboard.", "operationId": "postDashboardPermissions", + "deprecated": true, "parameters": [ { "name": "Body", @@ -3911,6 +3914,82 @@ } } }, + "/dashboards/uid/{uid}/permissions": { + "get": { + "tags": ["dashboard_permissions"], + "summary": "Gets all existing permissions for the given dashboard.", + "operationId": "getDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/getDashboardPermissionsResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "This operation will remove existing permissions if they’re not included in the request.", + "tags": ["dashboard_permissions"], + "summary": "Updates permissions for a dashboard.", + "operationId": "postDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpdateDashboardAclCommand" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources": { "get": { "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scope: `datasources:*`.", @@ -8158,14 +8237,6 @@ "summary": "Add External Group.", "operationId": "addTeamGroupApi", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", - "in": "path", - "required": true - }, { "x-go-name": "Body", "name": "body", @@ -8174,6 +8245,14 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true } ], "responses": { @@ -8207,16 +8286,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true } @@ -13521,7 +13600,7 @@ "properties": { "id": { "type": "string", - "x-go-name": "Id" + "x-go-name": "ID" }, "target": { "type": "string", @@ -13536,7 +13615,7 @@ "x-go-name": "Url" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "NavbarPreference": { "type": "object", @@ -14616,7 +14695,7 @@ "x-go-name": "HomeTab" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "Receiver": { "type": "object", diff --git a/public/api-spec.json b/public/api-spec.json index 7467737bf58..bf38c563b31 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -2614,9 +2614,11 @@ }, "/dashboards/id/{DashboardID}/permissions": { "get": { + "description": "Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsWithUid) instead", "tags": ["dashboard_permissions"], "summary": "Gets all existing permissions for the given dashboard.", "operationId": "getDashboardPermissions", + "deprecated": true, "parameters": [ { "type": "integer", @@ -2645,10 +2647,11 @@ } }, "post": { - "description": "This operation will remove existing permissions if they’re not included in the request.", + "description": "Please refer to [updated API](#/dashboard_permissions/postDashboardPermissionsWithUid) instead\n\nThis operation will remove existing permissions if they’re not included in the request.", "tags": ["dashboard_permissions"], "summary": "Updates permissions for a dashboard.", "operationId": "postDashboardPermissions", + "deprecated": true, "parameters": [ { "name": "Body", @@ -2961,6 +2964,82 @@ } } }, + "/dashboards/uid/{uid}/permissions": { + "get": { + "tags": ["dashboard_permissions"], + "summary": "Gets all existing permissions for the given dashboard.", + "operationId": "getDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/getDashboardPermissionsResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "This operation will remove existing permissions if they’re not included in the request.", + "tags": ["dashboard_permissions"], + "summary": "Updates permissions for a dashboard.", + "operationId": "postDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpdateDashboardAclCommand" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources": { "get": { "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scope: `datasources:*`.", @@ -6588,14 +6667,6 @@ "summary": "Add External Group.", "operationId": "addTeamGroupApi", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", - "in": "path", - "required": true - }, { "x-go-name": "Body", "name": "body", @@ -6604,6 +6675,14 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true } ], "responses": { @@ -6637,16 +6716,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true } @@ -10628,7 +10707,7 @@ "properties": { "id": { "type": "string", - "x-go-name": "Id" + "x-go-name": "ID" }, "target": { "type": "string", @@ -10643,7 +10722,7 @@ "x-go-name": "Url" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "NavbarPreference": { "type": "object", @@ -11092,7 +11171,7 @@ "x-go-name": "HomeTab" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "RecordingRuleJSON": { "description": "RecordingRuleJSON is the external representation of a recording rule",