diff --git a/pkg/services/publicdashboards/api/api.go b/pkg/services/publicdashboards/api/api.go index 13285b6572a..edc9fc5f7cc 100644 --- a/pkg/services/publicdashboards/api/api.go +++ b/pkg/services/publicdashboards/api/api.go @@ -155,6 +155,12 @@ func (api *Api) CreatePublicDashboard(c *contextmodel.ReqContext) response.Respo return response.Err(ErrInvalidUid.Errorf("CreatePublicDashboard: invalid Uid %s", uid)) } + //validate accessToken + accessToken := pdDTO.AccessToken + if accessToken != "" && !validation.IsValidAccessToken(accessToken) { + return response.Err(ErrInvalidAccessToken.Errorf("CreatePublicDashboard: invalid Access Token %s", accessToken)) + } + // Always set the orgID and userID from the session dto := &SavePublicDashboardDTO{ UserId: c.UserID, diff --git a/pkg/services/publicdashboards/api/api_test.go b/pkg/services/publicdashboards/api/api_test.go index 2745d1153ab..098b186120e 100644 --- a/pkg/services/publicdashboards/api/api_test.go +++ b/pkg/services/publicdashboards/api/api_test.go @@ -448,6 +448,24 @@ func TestApiCreatePublicDashboard(t *testing.T) { ShouldCallService: true, JsonBody: `{ "uid": "123abc", "isEnabled": true}`, }, + { + Name: "returns 400 when access token is invalid", + ExpectedHttpResponse: http.StatusBadRequest, + publicDashboard: nil, + SaveDashboardErr: nil, + User: userAdmin, + ShouldCallService: false, + JsonBody: `{ "AccessToken": "123abc", "isEnabled": true }`, + }, + { + Name: "returns 200 when access token is valid", + ExpectedHttpResponse: http.StatusOK, + publicDashboard: &PublicDashboard{IsEnabled: true}, + SaveDashboardErr: nil, + User: userAdmin, + ShouldCallService: true, + JsonBody: `{ "accessToken": "d64457c699644079b50230cfefddb211", "isEnabled": true}`, + }, } for _, test := range testCases { diff --git a/pkg/services/publicdashboards/models/errors.go b/pkg/services/publicdashboards/models/errors.go index 8c30ab8b127..a7db77973ed 100644 --- a/pkg/services/publicdashboards/models/errors.go +++ b/pkg/services/publicdashboards/models/errors.go @@ -21,7 +21,8 @@ var ( ErrInvalidTimeRange = errutil.BadRequest("publicdashboards.invalidTimeRange", errutil.WithPublicMessage("Invalid time range")) ErrInvalidShareType = errutil.BadRequest("publicdashboards.invalidShareType", errutil.WithPublicMessage("Invalid share type")) ErrDashboardIsPublic = errutil.BadRequest("publicdashboards.dashboardIsPublic", errutil.WithPublicMessage("Dashboard is already public")) - ErrPublicDashboardUidExists = errutil.BadRequest("publicdashboards.uidExists", errutil.WithPublicMessage("Public Dashboard Uid exists")) + ErrPublicDashboardUidExists = errutil.BadRequest("publicdashboards.uidExists", errutil.WithPublicMessage("Public Dashboard Uid already exists")) + ErrPublicDashboardAccessTokenExists = errutil.BadRequest("publicdashboards.accessTokenExists", errutil.WithPublicMessage("Public Dashboard Access Token already exists")) ErrPublicDashboardNotEnabled = errutil.Forbidden("publicdashboards.notEnabled", errutil.WithPublicMessage("Public dashboard paused")) ) diff --git a/pkg/services/publicdashboards/models/models.go b/pkg/services/publicdashboards/models/models.go index 20e5cc7e070..55407d9a561 100644 --- a/pkg/services/publicdashboards/models/models.go +++ b/pkg/services/publicdashboards/models/models.go @@ -57,6 +57,7 @@ type PublicDashboard struct { type PublicDashboardDTO struct { Uid string `json:"uid"` + AccessToken string `json:"accessToken"` TimeSelectionEnabled *bool `json:"timeSelectionEnabled"` IsEnabled *bool `json:"isEnabled"` AnnotationsEnabled *bool `json:"annotationsEnabled"` diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index 433e4a27e9e..2ab7b16c5bb 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -429,23 +429,33 @@ func GenerateAccessToken() (string, error) { func (pd *PublicDashboardServiceImpl) newCreatePublicDashboard(ctx context.Context, dto *SavePublicDashboardDTO) (*PublicDashboard, error) { //Check if uid already exists, if none then auto generate - existingPubdash, _ := pd.store.Find(ctx, dto.PublicDashboard.Uid) - if existingPubdash != nil { - return nil, ErrPublicDashboardUidExists.Errorf("Create: public dashboard uid %s already exists", dto.PublicDashboard.Uid) - } - var err error uid := dto.PublicDashboard.Uid - if dto.PublicDashboard.Uid == "" { + + if uid != "" { + existingPubdash, _ := pd.store.Find(ctx, uid) + if existingPubdash != nil { + return nil, ErrPublicDashboardUidExists.Errorf("Create: public dashboard uid %s already exists", uid) + } + } else { uid, err = pd.NewPublicDashboardUid(ctx) if err != nil { return nil, err } } - accessToken, err := pd.NewPublicDashboardAccessToken(ctx) - if err != nil { - return nil, err + //Check if accessToken already exists, if none then auto generate + accessToken := dto.PublicDashboard.AccessToken + if accessToken != "" { + existingPubdash, _ := pd.store.FindByAccessToken(ctx, accessToken) + if existingPubdash != nil { + return nil, ErrPublicDashboardAccessTokenExists.Errorf("Create: public dashboard access token %s already exists", accessToken) + } + } else { + accessToken, err = pd.NewPublicDashboardAccessToken(ctx) + if err != nil { + return nil, err + } } isEnabled := returnValueOrDefault(dto.PublicDashboard.IsEnabled, false) diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index c0da6b79220..dd8c031b7fe 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -856,9 +856,11 @@ func TestCreatePublicDashboard(t *testing.T) { assert.Equal(t, dto.PublicDashboard.Uid, pubdash.Uid) }) - t.Run("Throws an error when pubdash with generated access token already exists", func(t *testing.T) { + t.Run("Throws an error when pubdash with given access token input already exists", func(t *testing.T) { dashboard := dashboards.NewDashboard("testDashie") pubdash := &PublicDashboard{ + Uid: "ExistingUid", + AccessToken: "ExistingAccessToken", IsEnabled: true, AnnotationsEnabled: false, DashboardUid: "NOTTHESAME", @@ -869,7 +871,7 @@ func TestCreatePublicDashboard(t *testing.T) { publicDashboardStore := &FakePublicDashboardStore{} publicDashboardStore.On("FindDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil) publicDashboardStore.On("Find", mock.Anything, mock.Anything).Return(nil, nil) - publicDashboardStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(pubdash, nil) + publicDashboardStore.On("FindByAccessToken", mock.Anything, "ExistingAccessToken").Return(pubdash, nil) publicDashboardStore.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrPublicDashboardNotFound.Errorf("")) serviceWrapper := ProvideServiceWrapper(publicDashboardStore) @@ -886,14 +888,76 @@ func TestCreatePublicDashboard(t *testing.T) { OrgID: dashboard.OrgID, UserId: 7, PublicDashboard: &PublicDashboardDTO{ - IsEnabled: &isEnabled, + AccessToken: "ExistingAccessToken", + IsEnabled: &isEnabled, }, } _, err := service.Create(context.Background(), SignedInUser, dto) require.Error(t, err) + require.Equal(t, err, ErrPublicDashboardAccessTokenExists.Errorf("Create: public dashboard access token %s already exists", dto.PublicDashboard.AccessToken)) + }) + + t.Run("Create public dashboard with given pubdash access token", func(t *testing.T) { + sqlStore := db.InitTestDB(t) + quotaService := quotatest.New(false, nil) + dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) + require.NoError(t, err) + publicdashboardStore := database.ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures()) + dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) + serviceWrapper := ProvideServiceWrapper(publicdashboardStore) + + service := &PublicDashboardServiceImpl{ + log: log.New("test.logger"), + store: publicdashboardStore, + serviceWrapper: serviceWrapper, + } + + isEnabled := true + + dto := &SavePublicDashboardDTO{ + DashboardUid: dashboard.UID, + UserId: 7, + OrgID: dashboard.OrgID, + PublicDashboard: &PublicDashboardDTO{ + AccessToken: "GivenAccessToken", + IsEnabled: &isEnabled, + }, + } + + _, err = service.Create(context.Background(), SignedInUser, dto) + require.NoError(t, err) + + pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID) + require.NoError(t, err) + + assert.Equal(t, dto.PublicDashboard.AccessToken, pubdash.AccessToken) + }) + + t.Run("Throws an error when pubdash with generated access token already exists", func(t *testing.T) { + dashboard := dashboards.NewDashboard("testDashie") + pubdash := &PublicDashboard{ + IsEnabled: true, + AnnotationsEnabled: false, + DashboardUid: "NOTTHESAME", + OrgId: dashboard.OrgID, + TimeSettings: timeSettings, + } + + publicDashboardStore := &FakePublicDashboardStore{} + publicDashboardStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(pubdash, nil) + + serviceWrapper := ProvideServiceWrapper(publicDashboardStore) + + service := &PublicDashboardServiceImpl{ + log: log.New("test.logger"), + store: publicDashboardStore, + serviceWrapper: serviceWrapper, + } + + _, err := service.NewPublicDashboardAccessToken(context.Background()) + require.Error(t, err) require.Equal(t, err, ErrInternalServerError.Errorf("failed to generate a unique accessToken for public dashboard")) - publicDashboardStore.AssertNotCalled(t, "Create") }) t.Run("Returns error if public dashboard exists", func(t *testing.T) {