mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
Snapshots: Viewers can not create a Snapshot (#84952)
This commit is contained in:
parent
629a8808e0
commit
c57c033522
@ -11,6 +11,7 @@ import (
|
||||
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@ -68,12 +69,27 @@ func (hs *HTTPServer) GetSharingOptions(c *contextmodel.ReqContext) {
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) CreateDashboardSnapshot(c *contextmodel.ReqContext) {
|
||||
cmd := dashboardsnapshots.CreateDashboardSnapshotCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
c.JsonApiErr(http.StatusBadRequest, "bad request data", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Do not check permissions when the instance snapshot public mode is enabled
|
||||
if !hs.Cfg.SnapshotPublicMode {
|
||||
evaluator := ac.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(cmd.Dashboard.GetNestedString("uid")))
|
||||
if canSave, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canSave {
|
||||
c.JsonApiErr(http.StatusForbidden, "forbidden", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dashboardsnapshots.CreateDashboardSnapshot(c, dashboardsnapshot.SnapshotSharingOptions{
|
||||
SnapshotsEnabled: hs.Cfg.SnapshotEnabled,
|
||||
ExternalEnabled: hs.Cfg.ExternalEnabled,
|
||||
ExternalSnapshotName: hs.Cfg.ExternalSnapshotName,
|
||||
ExternalSnapshotURL: hs.Cfg.ExternalSnapshotUrl,
|
||||
}, hs.dashboardsnapshotsService)
|
||||
}, cmd, hs.dashboardsnapshotsService)
|
||||
}
|
||||
|
||||
// GET /api/snapshots/:key
|
||||
|
@ -269,6 +269,13 @@ func (b *SnapshotsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
||||
fmt.Sprintf("user orgId does not match namespace (%d != %d)", info.OrgID, user.OrgID), nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := dashboardsnapshots.CreateDashboardSnapshotCommand{}
|
||||
if err := web.Bind(wrap.Req, &cmd); err != nil {
|
||||
wrap.JsonApiErr(http.StatusBadRequest, "bad request data", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts, err := b.options(info.Value)
|
||||
if err != nil {
|
||||
wrap.JsonApiErr(http.StatusBadRequest, "error getting options", err)
|
||||
@ -276,7 +283,7 @@ func (b *SnapshotsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
||||
}
|
||||
|
||||
// Use the existing snapshot service
|
||||
dashboardsnapshots.CreateDashboardSnapshot(wrap, opts.Spec, b.service)
|
||||
dashboardsnapshots.CreateDashboardSnapshot(wrap, opts.Spec, cmd, b.service)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
//go:generate mockery --name Service --structname MockService --inpackage --filename service_mock.go
|
||||
@ -34,19 +33,14 @@ var client = &http.Client{
|
||||
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
||||
}
|
||||
|
||||
func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.SnapshotSharingOptions, svc Service) {
|
||||
func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.SnapshotSharingOptions, cmd CreateDashboardSnapshotCommand, svc Service) {
|
||||
if !cfg.SnapshotsEnabled {
|
||||
c.JsonApiErr(http.StatusForbidden, "Dashboard Snapshots are disabled", nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := CreateDashboardSnapshotCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
c.JsonApiErr(http.StatusBadRequest, "bad request data", err)
|
||||
return
|
||||
}
|
||||
if cmd.Name == "" {
|
||||
cmd.Name = "Unnamed snapshot"
|
||||
if cmd.DashboardCreateCommand.Name == "" {
|
||||
cmd.DashboardCreateCommand.Name = "Unnamed snapshot"
|
||||
}
|
||||
|
||||
userID, err := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
|
||||
@ -66,7 +60,7 @@ func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.S
|
||||
return
|
||||
}
|
||||
|
||||
if cmd.External {
|
||||
if cmd.DashboardCreateCommand.External {
|
||||
if !cfg.ExternalEnabled {
|
||||
c.JsonApiErr(http.StatusForbidden, "External dashboard creation is disabled", nil)
|
||||
return
|
||||
@ -83,11 +77,11 @@ func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.S
|
||||
cmd.DeleteKey = resp.DeleteKey
|
||||
cmd.ExternalURL = resp.Url
|
||||
cmd.ExternalDeleteURL = resp.DeleteUrl
|
||||
cmd.Dashboard = &common.Unstructured{}
|
||||
cmd.DashboardCreateCommand.Dashboard = &common.Unstructured{}
|
||||
|
||||
metrics.MApiDashboardSnapshotExternal.Inc()
|
||||
} else {
|
||||
cmd.Dashboard.SetNestedField(originalDashboardURL, "snapshot", "originalUrl")
|
||||
cmd.DashboardCreateCommand.Dashboard.SetNestedField(originalDashboardURL, "snapshot", "originalUrl")
|
||||
|
||||
if cmd.Key == "" {
|
||||
var err error
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.32.0. DO NOT EDIT.
|
||||
|
||||
package dashboardsnapshots
|
||||
|
||||
@ -18,6 +18,10 @@ func (_m *MockService) CreateDashboardSnapshot(_a0 context.Context, _a1 *CreateD
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 *DashboardSnapshot
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *CreateDashboardSnapshotCommand) (*DashboardSnapshot, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *CreateDashboardSnapshotCommand) *DashboardSnapshot); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
@ -26,7 +30,6 @@ func (_m *MockService) CreateDashboardSnapshot(_a0 context.Context, _a1 *CreateD
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *CreateDashboardSnapshotCommand) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
@ -69,6 +72,10 @@ func (_m *MockService) GetDashboardSnapshot(_a0 context.Context, _a1 *GetDashboa
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 *DashboardSnapshot
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardSnapshotQuery) (*DashboardSnapshot, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardSnapshotQuery) *DashboardSnapshot); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
@ -77,7 +84,6 @@ func (_m *MockService) GetDashboardSnapshot(_a0 context.Context, _a1 *GetDashboa
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *GetDashboardSnapshotQuery) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
@ -92,6 +98,10 @@ func (_m *MockService) SearchDashboardSnapshots(_a0 context.Context, _a1 *GetDas
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 DashboardSnapshotsList
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardSnapshotsQuery) (DashboardSnapshotsList, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardSnapshotsQuery) DashboardSnapshotsList); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
@ -100,7 +110,6 @@ func (_m *MockService) SearchDashboardSnapshots(_a0 context.Context, _a1 *GetDas
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *GetDashboardSnapshotsQuery) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
@ -110,13 +119,12 @@ func (_m *MockService) SearchDashboardSnapshots(_a0 context.Context, _a1 *GetDas
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewMockService interface {
|
||||
// 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 {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewMockService(t mockConstructorTestingTNewMockService) *MockService {
|
||||
}) *MockService {
|
||||
mock := &MockService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
|
@ -46,12 +46,13 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
|
||||
const { dashboardRef, panelRef } = this.state;
|
||||
|
||||
const tabs: SceneShareTab[] = [new ShareLinkTab({ dashboardRef, panelRef, modalRef: this.getRef() })];
|
||||
const dashboard = getDashboardSceneFor(this);
|
||||
|
||||
if (!panelRef) {
|
||||
tabs.push(new ShareExportTab({ dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
if (contextSrv.isSignedIn && config.snapshotEnabled) {
|
||||
if (contextSrv.isSignedIn && config.snapshotEnabled && dashboard.canEditDashboard()) {
|
||||
tabs.push(new ShareSnapshotTab({ panelRef, dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
|
@ -29,11 +29,11 @@ export function addPanelShareTab(tab: ShareModalTabModel) {
|
||||
customPanelTabs.push(tab);
|
||||
}
|
||||
|
||||
function getTabs(panel?: PanelModel, activeTab?: string) {
|
||||
function getTabs(canEditDashboard: boolean, panel?: PanelModel, activeTab?: string) {
|
||||
const linkLabel = t('share-modal.tab-title.link', 'Link');
|
||||
const tabs: ShareModalTabModel[] = [{ label: linkLabel, value: shareDashboardType.link, component: ShareLink }];
|
||||
|
||||
if (contextSrv.isSignedIn && config.snapshotEnabled) {
|
||||
if (contextSrv.isSignedIn && config.snapshotEnabled && canEditDashboard) {
|
||||
const snapshotLabel = t('share-modal.tab-title.snapshot', 'Snapshot');
|
||||
tabs.push({ label: snapshotLabel, value: shareDashboardType.snapshot, component: ShareSnapshot });
|
||||
}
|
||||
@ -86,7 +86,7 @@ interface State {
|
||||
}
|
||||
|
||||
function getInitialState(props: Props): State {
|
||||
const { tabs, activeTab } = getTabs(props.panel, props.activeTab);
|
||||
const { tabs, activeTab } = getTabs(props.dashboard.canEditDashboard(), props.panel, props.activeTab);
|
||||
|
||||
return {
|
||||
tabs,
|
||||
@ -116,7 +116,8 @@ class UnthemedShareModal extends React.Component<Props, State> {
|
||||
const { panel } = this.props;
|
||||
const { activeTab } = this.state;
|
||||
const title = panel ? t('share-modal.panel.title', 'Share Panel') : t('share-modal.dashboard.title', 'Share');
|
||||
const tabs = getTabs(this.props.panel, this.state.activeTab).tabs;
|
||||
const canEditDashboard = this.props.dashboard.canEditDashboard();
|
||||
const tabs = getTabs(canEditDashboard, this.props.panel, this.state.activeTab).tabs;
|
||||
|
||||
return (
|
||||
<ModalTabsHeader
|
||||
|
Loading…
Reference in New Issue
Block a user