mirror of
https://github.com/grafana/grafana.git
synced 2026-08-11 05:34:53 -05:00
Access control: FGAC for annotation updates (#46462)
* proposal * PR feedback * fix canSave bug * update scope naming * linting * linting Co-authored-by: Ezequiel Victorero <ezequiel.victorero@grafana.com>
This commit is contained in:
co-authored by
Ezequiel Victorero
parent
6eecd021a4
commit
f2450575b3
+83
-22
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -9,13 +10,15 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func GetAnnotations(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response {
|
||||
query := &annotations.ItemQuery{
|
||||
From: c.QueryInt64("from"),
|
||||
To: c.QueryInt64("to"),
|
||||
@@ -54,12 +57,20 @@ func (e *CreateAnnotationError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func PostAnnotation(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response {
|
||||
cmd := dtos.PostAnnotationsCmd{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave {
|
||||
|
||||
var canSave bool
|
||||
var err error
|
||||
if cmd.DashboardId != 0 {
|
||||
canSave, err = canSaveLocalAnnotation(c, cmd.DashboardId)
|
||||
} else {
|
||||
canSave = canSaveGlobalAnnotation(c)
|
||||
}
|
||||
if err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
@@ -105,7 +116,7 @@ func formatGraphiteAnnotation(what string, data string) string {
|
||||
return text
|
||||
}
|
||||
|
||||
func PostGraphiteAnnotation(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) PostGraphiteAnnotation(c *models.ReqContext) response.Response {
|
||||
cmd := dtos.PostGraphiteAnnotationsCmd{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
@@ -160,7 +171,7 @@ func PostGraphiteAnnotation(c *models.ReqContext) response.Response {
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateAnnotation(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response {
|
||||
cmd := dtos.UpdateAnnotationsCmd{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
@@ -178,7 +189,15 @@ func UpdateAnnotation(c *models.ReqContext) response.Response {
|
||||
return resp
|
||||
}
|
||||
|
||||
if canSave, err := canSaveByDashboardID(c, annotation.DashboardId); err != nil || !canSave {
|
||||
canSave := true
|
||||
if annotation.GetType() == annotations.Local {
|
||||
canSave, err = canSaveLocalAnnotation(c, annotation.DashboardId)
|
||||
} else {
|
||||
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
|
||||
canSave = canSaveGlobalAnnotation(c)
|
||||
}
|
||||
}
|
||||
if err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
@@ -199,7 +218,7 @@ func UpdateAnnotation(c *models.ReqContext) response.Response {
|
||||
return response.Success("Annotation updated")
|
||||
}
|
||||
|
||||
func PatchAnnotation(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response {
|
||||
cmd := dtos.PatchAnnotationsCmd{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
@@ -216,7 +235,15 @@ func PatchAnnotation(c *models.ReqContext) response.Response {
|
||||
return resp
|
||||
}
|
||||
|
||||
if canSave, err := canSaveByDashboardID(c, annotation.DashboardId); err != nil || !canSave {
|
||||
canSave := true
|
||||
if annotation.GetType() == annotations.Local {
|
||||
canSave, err = canSaveLocalAnnotation(c, annotation.DashboardId)
|
||||
} else {
|
||||
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
|
||||
canSave = canSaveGlobalAnnotation(c)
|
||||
}
|
||||
}
|
||||
if err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
@@ -253,7 +280,7 @@ func PatchAnnotation(c *models.ReqContext) response.Response {
|
||||
return response.Success("Annotation patched")
|
||||
}
|
||||
|
||||
func DeleteAnnotations(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) DeleteAnnotations(c *models.ReqContext) response.Response {
|
||||
cmd := dtos.DeleteAnnotationsCmd{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
@@ -274,7 +301,7 @@ func DeleteAnnotations(c *models.ReqContext) response.Response {
|
||||
return response.Success("Annotations deleted")
|
||||
}
|
||||
|
||||
func DeleteAnnotationByID(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Response {
|
||||
annotationID, err := strconv.ParseInt(web.Params(c.Req)[":annotationId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "annotationId is invalid", err)
|
||||
@@ -287,7 +314,13 @@ func DeleteAnnotationByID(c *models.ReqContext) response.Response {
|
||||
return resp
|
||||
}
|
||||
|
||||
if canSave, err := canSaveByDashboardID(c, annotation.DashboardId); err != nil || !canSave {
|
||||
var canSave bool
|
||||
if annotation.GetType() == annotations.Local {
|
||||
canSave, err = canSaveLocalAnnotation(c, annotation.DashboardId)
|
||||
} else {
|
||||
canSave = canSaveGlobalAnnotation(c)
|
||||
}
|
||||
if err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
@@ -302,21 +335,19 @@ func DeleteAnnotationByID(c *models.ReqContext) response.Response {
|
||||
return response.Success("Annotation deleted")
|
||||
}
|
||||
|
||||
func canSaveByDashboardID(c *models.ReqContext, dashboardID int64) (bool, error) {
|
||||
if dashboardID == 0 && !c.SignedInUser.HasRole(models.ROLE_EDITOR) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if dashboardID != 0 {
|
||||
guard := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guard.CanEdit(); err != nil || !canEdit {
|
||||
return false, err
|
||||
}
|
||||
func canSaveLocalAnnotation(c *models.ReqContext, dashboardID int64) (bool, error) {
|
||||
guard := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guard.CanEdit(); err != nil || !canEdit {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func canSaveGlobalAnnotation(c *models.ReqContext) bool {
|
||||
return c.SignedInUser.HasRole(models.ROLE_EDITOR)
|
||||
}
|
||||
|
||||
func findAnnotationByID(repo annotations.Repository, annotationID int64, orgID int64) (*annotations.ItemDTO, response.Response) {
|
||||
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgID})
|
||||
|
||||
@@ -331,7 +362,7 @@ func findAnnotationByID(repo annotations.Repository, annotationID int64, orgID i
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
func GetAnnotationTags(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) GetAnnotationTags(c *models.ReqContext) response.Response {
|
||||
query := &annotations.TagsQuery{
|
||||
OrgID: c.OrgId,
|
||||
Tag: c.Query("tag"),
|
||||
@@ -346,3 +377,33 @@ func GetAnnotationTags(c *models.ReqContext) response.Response {
|
||||
|
||||
return response.JSON(200, annotations.GetAnnotationTagsResponse{Result: result})
|
||||
}
|
||||
|
||||
// AnnotationTypeScopeResolver provides an AttributeScopeResolver able to
|
||||
// resolve annotation types. Scope "annotations:id:<id>" will be translated to "annotations:type:<type>,
|
||||
// where <type> is the type of annotation with id <id>.
|
||||
func AnnotationTypeScopeResolver() (string, accesscontrol.AttributeScopeResolveFunc) {
|
||||
annotationTypeResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) {
|
||||
scopeParts := strings.Split(initialScope, ":")
|
||||
if scopeParts[0] != accesscontrol.ScopeAnnotationsRoot || len(scopeParts) != 3 {
|
||||
return "", accesscontrol.ErrInvalidScope
|
||||
}
|
||||
|
||||
annotationIdStr := scopeParts[2]
|
||||
annotationId, err := strconv.Atoi(annotationIdStr)
|
||||
if err != nil {
|
||||
return "", accesscontrol.ErrInvalidScope
|
||||
}
|
||||
|
||||
annotation, resp := findAnnotationByID(annotations.GetRepository(), int64(annotationId), orgID)
|
||||
if resp != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if annotation.GetType() == annotations.Global {
|
||||
return accesscontrol.ScopeAnnotationsTypeGlobal, nil
|
||||
} else {
|
||||
return accesscontrol.ScopeAnnotationsTypeLocal, nil
|
||||
}
|
||||
}
|
||||
return accesscontrol.ScopeAnnotationsProvider.GetResourceScope(""), annotationTypeResolver
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user