diff --git a/pkg/services/publicdashboards/api/api.go b/pkg/services/publicdashboards/api/api.go index de10509003f..13285b6572a 100644 --- a/pkg/services/publicdashboards/api/api.go +++ b/pkg/services/publicdashboards/api/api.go @@ -149,6 +149,12 @@ func (api *Api) CreatePublicDashboard(c *contextmodel.ReqContext) response.Respo return response.Err(ErrBadRequest.Errorf("CreatePublicDashboard: bad request data %v", err)) } + //validate uid + uid := pdDTO.Uid + if uid != "" && !validation.IsValidShortUID(uid) { + return response.Err(ErrInvalidUid.Errorf("CreatePublicDashboard: invalid Uid %s", uid)) + } + // 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 5e1b1705333..2745d1153ab 100644 --- a/pkg/services/publicdashboards/api/api_test.go +++ b/pkg/services/publicdashboards/api/api_test.go @@ -391,6 +391,7 @@ func TestApiCreatePublicDashboard(t *testing.T) { SaveDashboardErr error User *user.SignedInUser ShouldCallService bool + JsonBody string }{ { Name: "returns 500 when not persisted", @@ -399,6 +400,7 @@ func TestApiCreatePublicDashboard(t *testing.T) { SaveDashboardErr: ErrInternalServerError.Errorf(""), User: userAdmin, ShouldCallService: true, + JsonBody: `{ "isPublic": true }`, }, { Name: "returns 404 when dashboard not found", @@ -407,6 +409,7 @@ func TestApiCreatePublicDashboard(t *testing.T) { SaveDashboardErr: ErrDashboardNotFound.Errorf(""), User: userAdmin, ShouldCallService: true, + JsonBody: `{ "isPublic": true }`, }, { Name: "returns 200 when update persists RBAC on", @@ -416,6 +419,7 @@ func TestApiCreatePublicDashboard(t *testing.T) { SaveDashboardErr: nil, User: userAdmin, ShouldCallService: true, + JsonBody: `{ "isPublic": true }`, }, { Name: "returns 403 when no permissions RBAC on", @@ -424,6 +428,25 @@ func TestApiCreatePublicDashboard(t *testing.T) { SaveDashboardErr: nil, User: userNoRBACPerms, ShouldCallService: false, + JsonBody: `{ "isPublic": true }`, + }, + { + Name: "returns 400 when uid is invalid", + ExpectedHttpResponse: http.StatusBadRequest, + publicDashboard: nil, + SaveDashboardErr: nil, + User: userAdmin, + ShouldCallService: false, + JsonBody: `{ "uid": "*", "isEnabled": true }`, + }, + { + Name: "returns 200 when uid is valid", + ExpectedHttpResponse: http.StatusOK, + publicDashboard: &PublicDashboard{IsEnabled: true}, + SaveDashboardErr: nil, + User: userAdmin, + ShouldCallService: true, + JsonBody: `{ "uid": "123abc", "isEnabled": true}`, }, } @@ -452,7 +475,7 @@ func TestApiCreatePublicDashboard(t *testing.T) { testServer, http.MethodPost, "/api/dashboards/uid/1/public-dashboards", - strings.NewReader(`{ "isPublic": true }`), + strings.NewReader(test.JsonBody), t, ) diff --git a/pkg/services/publicdashboards/models/errors.go b/pkg/services/publicdashboards/models/errors.go index 58909ea9934..8c30ab8b127 100644 --- a/pkg/services/publicdashboards/models/errors.go +++ b/pkg/services/publicdashboards/models/errors.go @@ -21,6 +21,7 @@ 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")) 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 a41efadd642..20e5cc7e070 100644 --- a/pkg/services/publicdashboards/models/models.go +++ b/pkg/services/publicdashboards/models/models.go @@ -56,6 +56,7 @@ type PublicDashboard struct { } type PublicDashboardDTO struct { + Uid string `json:"uid"` 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 c6727257afe..433e4a27e9e 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -428,9 +428,19 @@ func GenerateAccessToken() (string, error) { } func (pd *PublicDashboardServiceImpl) newCreatePublicDashboard(ctx context.Context, dto *SavePublicDashboardDTO) (*PublicDashboard, error) { - uid, err := pd.NewPublicDashboardUid(ctx) - if err != nil { - return nil, err + //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 == "" { + uid, err = pd.NewPublicDashboardUid(ctx) + if err != nil { + return nil, err + } } accessToken, err := pd.NewPublicDashboardAccessToken(ctx) diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index 94b10a81a33..a332509324f 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -780,6 +780,82 @@ func TestCreatePublicDashboard(t *testing.T) { assert.Equal(t, dashboard.OrgID, pubdash.OrgId) }) + t.Run("Throws an error when given pubdash uid already exists", func(t *testing.T) { + dashboard := dashboards.NewDashboard("testDashie") + pubdash := &PublicDashboard{ + Uid: "ExistingUid", + IsEnabled: true, + AnnotationsEnabled: false, + DashboardUid: "NOTTHESAME", + OrgId: dashboard.OrgID, + TimeSettings: timeSettings, + } + + publicDashboardStore := &FakePublicDashboardStore{} + publicDashboardStore.On("FindDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil) + publicDashboardStore.On("Find", mock.Anything, "ExistingUid").Return(pubdash, nil) + publicDashboardStore.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrPublicDashboardNotFound.Errorf("")) + + serviceWrapper := ProvideServiceWrapper(publicDashboardStore) + + service := &PublicDashboardServiceImpl{ + log: log.New("test.logger"), + store: publicDashboardStore, + serviceWrapper: serviceWrapper, + } + + isEnabled := true + dto := &SavePublicDashboardDTO{ + DashboardUid: "an-id", + OrgID: dashboard.OrgID, + UserId: 7, + PublicDashboard: &PublicDashboardDTO{ + Uid: "ExistingUid", + IsEnabled: &isEnabled, + }, + } + + _, err := service.Create(context.Background(), SignedInUser, dto) + require.Error(t, err) + require.Equal(t, err, ErrPublicDashboardUidExists.Errorf("Create: public dashboard uid %s already exists", dto.PublicDashboard.Uid)) + }) + + t.Run("Create public dashboard with given pubdash uid", 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{ + Uid: "GivenUid", + 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.Uid, pubdash.Uid) + }) + t.Run("Throws an error when pubdash with generated access token already exists", func(t *testing.T) { dashboard := dashboards.NewDashboard("testDashie") pubdash := &PublicDashboard{