mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: Validate access token not to be duplicated and add retries. (#56755)
PublicDashboards: Validate access token not to be duplicated and add retries.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
|
||||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||||
@@ -137,6 +138,38 @@ func (d *PublicDashboardStoreImpl) GenerateNewPublicDashboardUid(ctx context.Con
|
|||||||
return uid, nil
|
return uid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates a new unique access token for a new public dashboard
|
||||||
|
func (d *PublicDashboardStoreImpl) GenerateNewPublicDashboardAccessToken(ctx context.Context) (string, error) {
|
||||||
|
var accessToken string
|
||||||
|
|
||||||
|
err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
var err error
|
||||||
|
accessToken, err = tokens.GenerateAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := sess.Get(&PublicDashboard{AccessToken: accessToken})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrPublicDashboardFailedGenerateAccessToken
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieves public dashboard configuration by Uid
|
// Retrieves public dashboard configuration by Uid
|
||||||
func (d *PublicDashboardStoreImpl) GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error) {
|
func (d *PublicDashboardStoreImpl) GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error) {
|
||||||
if uid == "" {
|
if uid == "" {
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ var (
|
|||||||
Reason: "failed to generate unique public dashboard id",
|
Reason: "failed to generate unique public dashboard id",
|
||||||
StatusCode: 500,
|
StatusCode: 500,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardFailedGenerateAccesstoken = PublicDashboardErr{
|
ErrPublicDashboardFailedGenerateAccessToken = PublicDashboardErr{
|
||||||
Reason: "failed to public dashboard access token",
|
Reason: "failed to create public dashboard",
|
||||||
StatusCode: 500,
|
StatusCode: 500,
|
||||||
}
|
}
|
||||||
ErrPublicDashboardNotFound = PublicDashboardErr{
|
ErrPublicDashboardNotFound = PublicDashboardErr{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.12.1. DO NOT EDIT.
|
// Code generated by mockery v2.14.0. DO NOT EDIT.
|
||||||
|
|
||||||
package publicdashboards
|
package publicdashboards
|
||||||
|
|
||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
publicdashboardsmodels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
publicdashboardsmodels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||||
|
|
||||||
testing "testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FakePublicDashboardStore is an autogenerated mock type for the Store type
|
// FakePublicDashboardStore is an autogenerated mock type for the Store type
|
||||||
@@ -39,6 +37,27 @@ func (_m *FakePublicDashboardStore) AccessTokenExists(ctx context.Context, acces
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateNewPublicDashboardAccessToken provides a mock function with given fields: ctx
|
||||||
|
func (_m *FakePublicDashboardStore) GenerateNewPublicDashboardAccessToken(ctx context.Context) (string, error) {
|
||||||
|
ret := _m.Called(ctx)
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
|
||||||
|
r0 = rf(ctx)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||||
|
r1 = rf(ctx)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateNewPublicDashboardUid provides a mock function with given fields: ctx
|
// GenerateNewPublicDashboardUid provides a mock function with given fields: ctx
|
||||||
func (_m *FakePublicDashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) {
|
func (_m *FakePublicDashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) {
|
||||||
ret := _m.Called(ctx)
|
ret := _m.Called(ctx)
|
||||||
@@ -254,8 +273,13 @@ func (_m *FakePublicDashboardStore) UpdatePublicDashboardConfig(ctx context.Cont
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFakePublicDashboardStore creates a new instance of FakePublicDashboardStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
type mockConstructorTestingTNewFakePublicDashboardStore interface {
|
||||||
func NewFakePublicDashboardStore(t testing.TB) *FakePublicDashboardStore {
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFakePublicDashboardStore creates a new instance of FakePublicDashboardStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewFakePublicDashboardStore(t mockConstructorTestingTNewFakePublicDashboardStore) *FakePublicDashboardStore {
|
||||||
mock := &FakePublicDashboardStore{}
|
mock := &FakePublicDashboardStore{}
|
||||||
mock.Mock.Test(t)
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ type Store interface {
|
|||||||
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
|
||||||
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
|
||||||
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
|
||||||
|
GenerateNewPublicDashboardAccessToken(ctx context.Context) (string, error)
|
||||||
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
|
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
|
||||||
GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error)
|
GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error)
|
||||||
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
|
|
||||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||||
"github.com/grafana/grafana/pkg/services/publicdashboards/queries"
|
"github.com/grafana/grafana/pkg/services/publicdashboards/queries"
|
||||||
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
|
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
|
||||||
@@ -153,7 +152,7 @@ func (pd *PublicDashboardServiceImpl) savePublicDashboardConfig(ctx context.Cont
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, err := tokens.GenerateAccessToken()
|
accessToken, err := pd.store.GenerateNewPublicDashboardAccessToken(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,38 @@ func TestSavePublicDashboard(t *testing.T) {
|
|||||||
_, err := service.SavePublicDashboardConfig(context.Background(), SignedInUser, dto)
|
_, err := service.SavePublicDashboardConfig(context.Background(), SignedInUser, dto)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Pubdash access token generation throws an error and pubdash is not persisted", func(t *testing.T) {
|
||||||
|
dashboard := models.NewDashboard("testDashie")
|
||||||
|
|
||||||
|
publicDashboardStore := &FakePublicDashboardStore{}
|
||||||
|
publicDashboardStore.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil)
|
||||||
|
publicDashboardStore.On("GetPublicDashboardByUid", mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
|
publicDashboardStore.On("GenerateNewPublicDashboardUid", mock.Anything).Return("an-uid", nil)
|
||||||
|
publicDashboardStore.On("GenerateNewPublicDashboardAccessToken", mock.Anything).Return("", ErrPublicDashboardFailedGenerateAccessToken)
|
||||||
|
|
||||||
|
service := &PublicDashboardServiceImpl{
|
||||||
|
log: log.New("test.logger"),
|
||||||
|
store: publicDashboardStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &SavePublicDashboardConfigDTO{
|
||||||
|
DashboardUid: "an-id",
|
||||||
|
OrgId: 8,
|
||||||
|
UserId: 7,
|
||||||
|
PublicDashboard: &PublicDashboard{
|
||||||
|
IsEnabled: true,
|
||||||
|
DashboardUid: "NOTTHESAME",
|
||||||
|
OrgId: 9999999,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := service.SavePublicDashboardConfig(context.Background(), SignedInUser, dto)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, err, ErrPublicDashboardFailedGenerateAccessToken)
|
||||||
|
publicDashboardStore.AssertNotCalled(t, "SavePublicDashboardConfig")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdatePublicDashboard(t *testing.T) {
|
func TestUpdatePublicDashboard(t *testing.T) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const publicDashboardApi = createApi({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: params.payload,
|
data: params.payload,
|
||||||
}),
|
}),
|
||||||
|
extraOptions: { maxRetries: 0 },
|
||||||
async onQueryStarted({ dashboard, payload }, { dispatch, queryFulfilled }) {
|
async onQueryStarted({ dashboard, payload }, { dispatch, queryFulfilled }) {
|
||||||
const { data } = await queryFulfilled;
|
const { data } = await queryFulfilled;
|
||||||
dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved')));
|
dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved')));
|
||||||
|
|||||||
Reference in New Issue
Block a user