RBAC: Return 404 instead of 403 if a dashboard cannot be found (#102815)
return 404 instead of 403 if a dashboard cannot be found
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
// ToDashboardErrorResponse returns a different response status according to the dashboard error type
|
||||
func ToDashboardErrorResponse(ctx context.Context, pluginStore pluginstore.Store, err error) response.Response {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
if body := dashboardErr.Body(); body != nil {
|
||||
return response.JSON(dashboardErr.StatusCode, body)
|
||||
|
||||
@@ -10,13 +10,14 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// ToFolderErrorResponse returns a different response status according to the folder error type
|
||||
func ToFolderErrorResponse(err error) response.Response {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
return response.Error(dashboardErr.StatusCode, err.Error(), err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -22,8 +23,8 @@ func TestToFolderErrorResponse(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "dashboard error",
|
||||
input: dashboards.DashboardErr{StatusCode: 400, Reason: "Dashboard Error", Status: "error"},
|
||||
want: response.Error(400, "Dashboard Error", dashboards.DashboardErr{StatusCode: 400, Reason: "Dashboard Error", Status: "error"}),
|
||||
input: dashboardaccess.DashboardErr{StatusCode: 400, Reason: "Dashboard Error", Status: "error"},
|
||||
want: response.Error(400, "Dashboard Error", dashboardaccess.DashboardErr{StatusCode: 400, Reason: "Dashboard Error", Status: "error"}),
|
||||
},
|
||||
{
|
||||
name: "folder title empty",
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardversion/dashverimpl"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -65,7 +66,7 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI
|
||||
|
||||
func dashboardGuardianResponse(err error) response.Response {
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
}
|
||||
@@ -372,7 +373,7 @@ func (hs *HTTPServer) RestoreDeletedDashboard(c *contextmodel.ReqContext) respon
|
||||
|
||||
err = hs.DashboardService.RestoreDashboard(c.Req.Context(), dash, c.SignedInUser, cmd.FolderUID)
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
}
|
||||
@@ -411,7 +412,7 @@ func (hs *HTTPServer) SoftDeleteDashboard(c *contextmodel.ReqContext) response.R
|
||||
|
||||
err := hs.DashboardService.SoftDeleteDashboard(c.Req.Context(), c.SignedInUser.GetOrgID(), uid)
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
if errors.Is(err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
@@ -497,7 +498,7 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
|
||||
|
||||
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, dash.UID, c.SignedInUser.GetOrgID())
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
if errors.Is(err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -87,6 +88,17 @@ func deny(c *contextmodel.ReqContext, evaluator Evaluator, err error) {
|
||||
id := newID()
|
||||
if err != nil {
|
||||
c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id)
|
||||
// Return 404s for dashboard not found errors, our plugins rely on being able to distinguish between access denied and not found.
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
if c.IsApiRequest() && dashboardErr.StatusCode == http.StatusNotFound {
|
||||
c.JSON(http.StatusNotFound, map[string]string{
|
||||
"title": "Not found", // the component needs to pick this up
|
||||
"message": dashboardErr.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.Logger.Info(
|
||||
"Access denied",
|
||||
|
||||
@@ -2,6 +2,8 @@ package dashboardaccess
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type PermissionType int
|
||||
@@ -30,3 +32,32 @@ var (
|
||||
ErrPermissionsWithRoleNotAllowed = errors.New("permissions cannot have both a user and team")
|
||||
ErrPermissionsWithUserAndTeamNotAllowed = errors.New("team and user permissions cannot have an associated role")
|
||||
)
|
||||
|
||||
// DashboardErr represents a dashboard error.
|
||||
type DashboardErr struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// Equal returns whether equal to another DashboardErr.
|
||||
func (e DashboardErr) Equal(o DashboardErr) bool {
|
||||
return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason
|
||||
}
|
||||
|
||||
// Error returns the error message.
|
||||
func (e DashboardErr) Error() string {
|
||||
if e.Reason != "" {
|
||||
return e.Reason
|
||||
}
|
||||
return "Dashboard Error"
|
||||
}
|
||||
|
||||
// Body returns the error's response body, if applicable.
|
||||
func (e DashboardErr) Body() util.DynMap {
|
||||
if e.Status == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return util.DynMap{"status": e.Status, "message": e.Error()}
|
||||
}
|
||||
|
||||
@@ -4,115 +4,115 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
)
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrDashboardNotFound = DashboardErr{
|
||||
ErrDashboardNotFound = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard not found",
|
||||
StatusCode: 404,
|
||||
Status: "not-found",
|
||||
}
|
||||
ErrDashboardCorrupt = DashboardErr{
|
||||
ErrDashboardCorrupt = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard data is missing or corrupt",
|
||||
StatusCode: 500,
|
||||
Status: "not-found",
|
||||
}
|
||||
ErrDashboardPanelNotFound = DashboardErr{
|
||||
ErrDashboardPanelNotFound = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard panel not found",
|
||||
StatusCode: 404,
|
||||
Status: "not-found",
|
||||
}
|
||||
ErrDashboardFolderNotFound = DashboardErr{
|
||||
ErrDashboardFolderNotFound = dashboardaccess.DashboardErr{
|
||||
Reason: "Folder not found",
|
||||
StatusCode: 404,
|
||||
}
|
||||
ErrDashboardWithSameUIDExists = DashboardErr{
|
||||
ErrDashboardWithSameUIDExists = dashboardaccess.DashboardErr{
|
||||
Reason: "A dashboard with the same uid already exists",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardVersionMismatch = DashboardErr{
|
||||
ErrDashboardVersionMismatch = dashboardaccess.DashboardErr{
|
||||
Reason: "The dashboard has been changed by someone else",
|
||||
StatusCode: 412,
|
||||
Status: "version-mismatch",
|
||||
}
|
||||
ErrDashboardTitleEmpty = DashboardErr{
|
||||
ErrDashboardTitleEmpty = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard title cannot be empty",
|
||||
StatusCode: 400,
|
||||
Status: "empty-name",
|
||||
}
|
||||
ErrDashboardTitleTooLong = DashboardErr{
|
||||
ErrDashboardTitleTooLong = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard title cannot contain more than 5 000 characters",
|
||||
StatusCode: 400,
|
||||
Status: "title-too-long",
|
||||
}
|
||||
ErrDashboardFolderCannotHaveParent = DashboardErr{
|
||||
ErrDashboardFolderCannotHaveParent = dashboardaccess.DashboardErr{
|
||||
Reason: "A Dashboard Folder cannot be added to another folder",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardsWithSameSlugExists = DashboardErr{
|
||||
ErrDashboardsWithSameSlugExists = dashboardaccess.DashboardErr{
|
||||
Reason: "Multiple dashboards with the same slug exists",
|
||||
StatusCode: 412,
|
||||
}
|
||||
ErrDashboardTypeMismatch = DashboardErr{
|
||||
ErrDashboardTypeMismatch = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard cannot be changed to a folder",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardFolderNameExists = DashboardErr{
|
||||
ErrDashboardFolderNameExists = dashboardaccess.DashboardErr{
|
||||
Reason: "A folder with that name already exists",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardUpdateAccessDenied = DashboardErr{
|
||||
ErrDashboardUpdateAccessDenied = dashboardaccess.DashboardErr{
|
||||
Reason: "Access denied to save dashboard",
|
||||
StatusCode: 403,
|
||||
}
|
||||
ErrDashboardInvalidUid = DashboardErr{
|
||||
ErrDashboardInvalidUid = dashboardaccess.DashboardErr{
|
||||
Reason: "uid contains illegal characters",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardUidTooLong = DashboardErr{
|
||||
ErrDashboardUidTooLong = dashboardaccess.DashboardErr{
|
||||
Reason: "uid too long, max 40 characters",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardMessageTooLong = DashboardErr{
|
||||
ErrDashboardMessageTooLong = dashboardaccess.DashboardErr{
|
||||
Reason: "message too long, max 500 characters",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardCannotSaveProvisionedDashboard = DashboardErr{
|
||||
ErrDashboardCannotSaveProvisionedDashboard = dashboardaccess.DashboardErr{
|
||||
Reason: "Cannot save provisioned dashboard",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardRefreshIntervalTooShort = DashboardErr{
|
||||
ErrDashboardRefreshIntervalTooShort = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard refresh interval is too low",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardCannotDeleteProvisionedDashboard = DashboardErr{
|
||||
ErrDashboardCannotDeleteProvisionedDashboard = dashboardaccess.DashboardErr{
|
||||
Reason: "provisioned dashboard cannot be deleted",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardIdentifierNotSet = DashboardErr{
|
||||
ErrDashboardIdentifierNotSet = dashboardaccess.DashboardErr{
|
||||
Reason: "Unique identifier needed to be able to get a dashboard",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardIdentifierInvalid = DashboardErr{
|
||||
ErrDashboardIdentifierInvalid = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard ID not a number",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardPanelIdentifierInvalid = DashboardErr{
|
||||
ErrDashboardPanelIdentifierInvalid = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard panel ID not a number",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrDashboardOrPanelIdentifierNotSet = DashboardErr{
|
||||
ErrDashboardOrPanelIdentifierNotSet = dashboardaccess.DashboardErr{
|
||||
Reason: "Unique identifier needed to be able to get a dashboard panel",
|
||||
StatusCode: 400,
|
||||
}
|
||||
ErrProvisionedDashboardNotFound = DashboardErr{
|
||||
ErrProvisionedDashboardNotFound = dashboardaccess.DashboardErr{
|
||||
Reason: "Dashboard is not provisioned",
|
||||
StatusCode: 404,
|
||||
Status: "not-found",
|
||||
}
|
||||
ErrFolderRestoreNotFound = DashboardErr{
|
||||
ErrFolderRestoreNotFound = dashboardaccess.DashboardErr{
|
||||
Reason: "Restoring folder not found",
|
||||
StatusCode: 400,
|
||||
Status: "bad-request",
|
||||
@@ -130,35 +130,6 @@ var (
|
||||
ErrFolderCreationAccessDenied = errutil.Forbidden("folders.forbiddenCreation", errutil.WithPublicMessage("not enough permissions to create a folder in the selected location"))
|
||||
)
|
||||
|
||||
// DashboardErr represents a dashboard error.
|
||||
type DashboardErr struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// Equal returns whether equal to another DashboardErr.
|
||||
func (e DashboardErr) Equal(o DashboardErr) bool {
|
||||
return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason
|
||||
}
|
||||
|
||||
// Error returns the error message.
|
||||
func (e DashboardErr) Error() string {
|
||||
if e.Reason != "" {
|
||||
return e.Reason
|
||||
}
|
||||
return "Dashboard Error"
|
||||
}
|
||||
|
||||
// Body returns the error's response body, if applicable.
|
||||
func (e DashboardErr) Body() util.DynMap {
|
||||
if e.Status == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return util.DynMap{"status": e.Status, "message": e.Error()}
|
||||
}
|
||||
|
||||
type UpdatePluginDashboardError struct {
|
||||
PluginId string
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
@@ -143,7 +144,7 @@ func (pd *PublicDashboardServiceImpl) FindDashboard(ctx context.Context, orgId i
|
||||
return pd.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId})
|
||||
})
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
if dashboardErr.StatusCode == 404 {
|
||||
return nil, ErrDashboardNotFound.Errorf("FindDashboard: dashboard not found by orgId: %d and dashboardUid: %s", orgId, dashboardUid)
|
||||
|
||||
Reference in New Issue
Block a user