mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Snapshots: Prevent creation of snapshot of nonexistent dashboard (#86806)
* Add validation to check for existence of dashboard before creation * add service mock for FindDashboard * update tests * reorder function * update logic change to bool * update naming validate dashboard * add tests * update return val * remove bool return val * simplify return statement * update test * remove extra spacing * update mock * remove unncessary space
This commit is contained in:
parent
42b0f802de
commit
e8625ecd4a
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@ -15,6 +16,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -26,6 +28,7 @@ type Service interface {
|
||||
DeleteExpiredSnapshots(context.Context, *DeleteExpiredSnapshotsCommand) error
|
||||
GetDashboardSnapshot(context.Context, *GetDashboardSnapshotQuery) (*DashboardSnapshot, error)
|
||||
SearchDashboardSnapshots(context.Context, *GetDashboardSnapshotsQuery) (DashboardSnapshotsList, error)
|
||||
ValidateDashboardExists(context.Context, int64, string) error
|
||||
}
|
||||
|
||||
var client = &http.Client{
|
||||
@ -39,6 +42,18 @@ func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.S
|
||||
return
|
||||
}
|
||||
|
||||
uid := cmd.DashboardCreateCommand.Dashboard.GetNestedString("uid")
|
||||
|
||||
err := svc.ValidateDashboardExists(c.Req.Context(), c.SignedInUser.GetOrgID(), uid)
|
||||
if err != nil {
|
||||
if errors.Is(err, dashboards.ErrDashboardNotFound) {
|
||||
c.JsonApiErr(http.StatusBadRequest, "Dashboard not found", err)
|
||||
return
|
||||
}
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Failed to get dashboard", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd.DashboardCreateCommand.Name == "" {
|
||||
cmd.DashboardCreateCommand.Name = "Unnamed snapshot"
|
||||
}
|
||||
|
@ -4,27 +4,35 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
store dashboardsnapshots.Store
|
||||
secretsService secrets.Service
|
||||
store dashboardsnapshots.Store
|
||||
secretsService secrets.Service
|
||||
dashboardService dashboards.DashboardService
|
||||
}
|
||||
|
||||
// ServiceImpl implements the dashboardsnapshots Service interface
|
||||
var _ dashboardsnapshots.Service = (*ServiceImpl)(nil)
|
||||
|
||||
func ProvideService(store dashboardsnapshots.Store, secretsService secrets.Service) *ServiceImpl {
|
||||
func ProvideService(store dashboardsnapshots.Store, secretsService secrets.Service, dashboardService dashboards.DashboardService) *ServiceImpl {
|
||||
s := &ServiceImpl{
|
||||
store: store,
|
||||
secretsService: secretsService,
|
||||
store: store,
|
||||
secretsService: secretsService,
|
||||
dashboardService: dashboardService,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) ValidateDashboardExists(ctx context.Context, orgId int64, dashboardUid string) error {
|
||||
_, err := s.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) CreateDashboardSnapshot(ctx context.Context, cmd *dashboardsnapshots.CreateDashboardSnapshotCommand) (*dashboardsnapshots.DashboardSnapshot, error) {
|
||||
marshalledData, err := cmd.Dashboard.MarshalJSON()
|
||||
if err != nil {
|
||||
|
@ -4,16 +4,26 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
|
||||
dashsnapdb "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
)
|
||||
@ -26,8 +36,9 @@ func TestDashboardSnapshotsService(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
dsStore := dashsnapdb.ProvideStore(sqlStore, cfg)
|
||||
fakeDashboardService := &dashboards.FakeDashboardService{}
|
||||
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
s := ProvideService(dsStore, secretsService)
|
||||
s := ProvideService(dsStore, secretsService, fakeDashboardService)
|
||||
|
||||
origSecret := cfg.SecretKey
|
||||
cfg.SecretKey = "dashboard_snapshot_service_test"
|
||||
@ -79,3 +90,45 @@ func TestDashboardSnapshotsService(t *testing.T) {
|
||||
require.Equal(t, rawDashboard, decrypted)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateDashboardExists(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
dsStore := dashsnapdb.ProvideStore(sqlStore, cfg)
|
||||
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
dashboardStore, err := dashdb.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
dashSvc, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashboardStore, folderimpl.ProvideDashboardFolderStore(sqlStore), nil, nil, nil, acmock.New(), foldertest.NewFakeService(), nil)
|
||||
require.NoError(t, err)
|
||||
s := ProvideService(dsStore, secretsService, dashSvc)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("returns false when dashboard does not exist", func(t *testing.T) {
|
||||
err := s.ValidateDashboardExists(ctx, 1, "test")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, dashboards.ErrDashboardNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("returns true when dashboard exists", func(t *testing.T) {
|
||||
err := createDashboard(sqlStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.ValidateDashboardExists(ctx, 1, "test")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func createDashboard(store db.DB) error {
|
||||
return store.WithDbSession(context.Background(), func(sess *db.Session) error {
|
||||
dashboard := &dashboards.Dashboard{
|
||||
ID: 1,
|
||||
UID: "test",
|
||||
OrgID: 1,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(dashboard)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.32.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.34.0. DO NOT EDIT.
|
||||
|
||||
package dashboardsnapshots
|
||||
|
||||
@ -119,6 +119,20 @@ func (_m *MockService) SearchDashboardSnapshots(_a0 context.Context, _a1 *GetDas
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ValidateDashboardExists provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *MockService) ValidateDashboardExists(_a0 context.Context, _a1 int64, _a2 string) error {
|
||||
ret := _m.Called(_a0, _a1, _a2)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok {
|
||||
r0 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockService(t interface {
|
||||
|
Loading…
Reference in New Issue
Block a user