diff --git a/pkg/services/publicdashboards/api/api.go b/pkg/services/publicdashboards/api/api.go index a40d8776009..8ac11743ed6 100644 --- a/pkg/services/publicdashboards/api/api.go +++ b/pkg/services/publicdashboards/api/api.go @@ -13,8 +13,8 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "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/validation" "github.com/grafana/grafana/pkg/web" ) @@ -102,7 +102,7 @@ func (api *Api) ListPublicDashboards(c *contextmodel.ReqContext) response.Respon func (api *Api) GetPublicDashboard(c *contextmodel.ReqContext) response.Response { // exit if we don't have a valid dashboardUid dashboardUid := web.Params(c.Req)[":dashboardUid"] - if !tokens.IsValidShortUID(dashboardUid) { + if !validation.IsValidShortUID(dashboardUid) { return response.Err(ErrPublicDashboardIdentifierNotSet.Errorf("GetPublicDashboard: no dashboard Uid for public dashboard specified")) } @@ -123,7 +123,7 @@ func (api *Api) GetPublicDashboard(c *contextmodel.ReqContext) response.Response func (api *Api) CreatePublicDashboard(c *contextmodel.ReqContext) response.Response { // exit if we don't have a valid dashboardUid dashboardUid := web.Params(c.Req)[":dashboardUid"] - if !tokens.IsValidShortUID(dashboardUid) { + if !validation.IsValidShortUID(dashboardUid) { return response.Err(ErrInvalidUid.Errorf("CreatePublicDashboard: invalid Uid %s", dashboardUid)) } @@ -155,12 +155,12 @@ func (api *Api) CreatePublicDashboard(c *contextmodel.ReqContext) response.Respo func (api *Api) UpdatePublicDashboard(c *contextmodel.ReqContext) response.Response { // exit if we don't have a valid dashboardUid dashboardUid := web.Params(c.Req)[":dashboardUid"] - if !tokens.IsValidShortUID(dashboardUid) { + if !validation.IsValidShortUID(dashboardUid) { return response.Err(ErrInvalidUid.Errorf("UpdatePublicDashboard: invalid dashboard Uid %s", dashboardUid)) } uid := web.Params(c.Req)[":uid"] - if !tokens.IsValidShortUID(uid) { + if !validation.IsValidShortUID(uid) { return response.Err(ErrInvalidUid.Errorf("UpdatePublicDashboard: invalid Uid %s", uid)) } @@ -192,7 +192,7 @@ func (api *Api) UpdatePublicDashboard(c *contextmodel.ReqContext) response.Respo // DELETE /api/dashboards/uid/:dashboardUid/public-dashboards/:uid func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Response { uid := web.Params(c.Req)[":uid"] - if !tokens.IsValidShortUID(uid) { + if !validation.IsValidShortUID(uid) { return response.Err(ErrInvalidUid.Errorf("UpdatePublicDashboard: invalid Uid %s", uid)) } diff --git a/pkg/services/publicdashboards/api/middleware.go b/pkg/services/publicdashboards/api/middleware.go index 78a42c5cf23..1805ae9c358 100644 --- a/pkg/services/publicdashboards/api/middleware.go +++ b/pkg/services/publicdashboards/api/middleware.go @@ -6,7 +6,7 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/publicdashboards" - "github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens" + "github.com/grafana/grafana/pkg/services/publicdashboards/validation" "github.com/grafana/grafana/pkg/web" ) @@ -14,7 +14,7 @@ import ( func SetPublicDashboardOrgIdOnContext(publicDashboardService publicdashboards.Service) func(c *contextmodel.ReqContext) { return func(c *contextmodel.ReqContext) { accessToken, ok := web.Params(c.Req)[":accessToken"] - if !ok || !tokens.IsValidAccessToken(accessToken) { + if !ok || !validation.IsValidAccessToken(accessToken) { return } @@ -45,7 +45,7 @@ func RequiresExistingAccessToken(publicDashboardService publicdashboards.Service return } - if !tokens.IsValidAccessToken(accessToken) { + if !validation.IsValidAccessToken(accessToken) { c.JsonApiErr(http.StatusBadRequest, "Invalid access token", nil) } diff --git a/pkg/services/publicdashboards/api/middleware_test.go b/pkg/services/publicdashboards/api/middleware_test.go index b0462107cf4..3e6f9811705 100644 --- a/pkg/services/publicdashboards/api/middleware_test.go +++ b/pkg/services/publicdashboards/api/middleware_test.go @@ -10,7 +10,7 @@ import ( contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/publicdashboards" - "github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens" + "github.com/grafana/grafana/pkg/services/publicdashboards/service" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/assert" @@ -18,7 +18,7 @@ import ( "github.com/stretchr/testify/require" ) -var validAccessToken, _ = tokens.GenerateAccessToken() +var validAccessToken, _ = service.GenerateAccessToken() func TestRequiresExistingAccessToken(t *testing.T) { tests := []struct { diff --git a/pkg/services/publicdashboards/api/query.go b/pkg/services/publicdashboards/api/query.go index 98a3172c760..4f4583d621f 100644 --- a/pkg/services/publicdashboards/api/query.go +++ b/pkg/services/publicdashboards/api/query.go @@ -8,8 +8,8 @@ import ( "github.com/grafana/grafana/pkg/api/response" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/grafana/grafana/pkg/services/publicdashboards/validation" "github.com/grafana/grafana/pkg/web" ) @@ -17,7 +17,7 @@ import ( // GET /api/public/dashboards/:accessToken func (api *Api) ViewPublicDashboard(c *contextmodel.ReqContext) response.Response { accessToken := web.Params(c.Req)[":accessToken"] - if !tokens.IsValidAccessToken(accessToken) { + if !validation.IsValidAccessToken(accessToken) { return response.Err(ErrInvalidAccessToken.Errorf("ViewPublicDashboard: invalid access token")) } @@ -55,7 +55,7 @@ func (api *Api) ViewPublicDashboard(c *contextmodel.ReqContext) response.Respons // POST /api/public/dashboard/:accessToken/panels/:panelId/query func (api *Api) QueryPublicDashboard(c *contextmodel.ReqContext) response.Response { accessToken := web.Params(c.Req)[":accessToken"] - if !tokens.IsValidAccessToken(accessToken) { + if !validation.IsValidAccessToken(accessToken) { return response.Err(ErrInvalidAccessToken.Errorf("QueryPublicDashboard: invalid access token")) } @@ -81,7 +81,7 @@ func (api *Api) QueryPublicDashboard(c *contextmodel.ReqContext) response.Respon // GET /api/public/dashboards/:accessToken/annotations func (api *Api) GetAnnotations(c *contextmodel.ReqContext) response.Response { accessToken := web.Params(c.Req)[":accessToken"] - if !tokens.IsValidAccessToken(accessToken) { + if !validation.IsValidAccessToken(accessToken) { return response.Err(ErrInvalidAccessToken.Errorf("GetAnnotations: invalid access token")) } diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index ba4a6eab5ad..d8da2b866f7 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -10,8 +10,8 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/grafana/grafana/pkg/services/publicdashboards/service" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/setting" @@ -693,7 +693,7 @@ func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardSt uid := util.GenerateShortUID() - accessToken, err := tokens.GenerateAccessToken() + accessToken, err := service.GenerateAccessToken() require.NoError(t, err) cmd := SavePublicDashboardCommand{ diff --git a/pkg/services/publicdashboards/internal/tokens/tokens.go b/pkg/services/publicdashboards/internal/tokens/tokens.go deleted file mode 100644 index 4d4f3accf0d..00000000000 --- a/pkg/services/publicdashboards/internal/tokens/tokens.go +++ /dev/null @@ -1,29 +0,0 @@ -package tokens - -import ( - "fmt" - - "github.com/google/uuid" - "github.com/grafana/grafana/pkg/util" -) - -// GenerateAccessToken generates an uuid formatted without dashes to use as access token -func GenerateAccessToken() (string, error) { - token, err := uuid.NewRandom() - if err != nil { - return "", err - } - return fmt.Sprintf("%x", token[:]), nil -} - -// IsValidAccessToken asserts that an accessToken is a valid uuid -func IsValidAccessToken(token string) bool { - _, err := uuid.Parse(token) - return err == nil -} - -// IsValidShortUID checks that the uid is not blank and contains valid -// characters. Wraps utils.IsValidShortUID -func IsValidShortUID(uid string) bool { - return uid != "" && util.IsValidShortUID(uid) -} diff --git a/pkg/services/publicdashboards/internal/tokens/tokens_test.go b/pkg/services/publicdashboards/internal/tokens/tokens_test.go deleted file mode 100644 index b04ba221f10..00000000000 --- a/pkg/services/publicdashboards/internal/tokens/tokens_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package tokens - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGenerateAccessToken(t *testing.T) { - accessToken, err := GenerateAccessToken() - - t.Run("length", func(t *testing.T) { - require.NoError(t, err) - assert.Equal(t, 32, len(accessToken)) - }) - - t.Run("no - ", func(t *testing.T) { - assert.False(t, strings.Contains("-", accessToken)) - }) -} - -func TestValidAccessToken(t *testing.T) { - t.Run("true", func(t *testing.T) { - uuid, _ := GenerateAccessToken() - assert.True(t, IsValidAccessToken(uuid)) - }) - - t.Run("false when blank", func(t *testing.T) { - assert.False(t, IsValidAccessToken("")) - }) - - t.Run("false when can't be parsed by uuid lib", func(t *testing.T) { - // too long - assert.False(t, IsValidAccessToken("0123456789012345678901234567890123456789")) - }) -} - -// we just check base cases since this wraps utils.IsValidShortUID which has -// test coverage -func TestValidUid(t *testing.T) { - t.Run("true", func(t *testing.T) { - assert.True(t, IsValidShortUID("afqrz7jZZ")) - }) - - t.Run("false when blank", func(t *testing.T) { - assert.False(t, IsValidShortUID("")) - }) - - t.Run("false when invalid chars", func(t *testing.T) { - assert.False(t, IsValidShortUID("afqrz7j%%")) - }) -} diff --git a/pkg/services/publicdashboards/public_dashboard_service_mock.go b/pkg/services/publicdashboards/public_dashboard_service_mock.go index 883c51e1b80..3fcf2bf2f87 100644 --- a/pkg/services/publicdashboards/public_dashboard_service_mock.go +++ b/pkg/services/publicdashboards/public_dashboard_service_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.14.0. DO NOT EDIT. package publicdashboards @@ -102,6 +102,29 @@ func (_m *FakePublicDashboardService) ExistsEnabledByDashboardUid(ctx context.Co return r0, r1 } +// Find provides a mock function with given fields: ctx, uid +func (_m *FakePublicDashboardService) Find(ctx context.Context, uid string) (*models.PublicDashboard, error) { + ret := _m.Called(ctx, uid) + + var r0 *models.PublicDashboard + if rf, ok := ret.Get(0).(func(context.Context, string) *models.PublicDashboard); ok { + r0 = rf(ctx, uid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.PublicDashboard) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, uid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FindAll provides a mock function with given fields: ctx, u, orgId func (_m *FakePublicDashboardService) FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]models.PublicDashboardListResponse, error) { ret := _m.Called(ctx, u, orgId) diff --git a/pkg/services/publicdashboards/publicdashboard.go b/pkg/services/publicdashboards/publicdashboard.go index b301e9be8ad..017c37f4152 100644 --- a/pkg/services/publicdashboards/publicdashboard.go +++ b/pkg/services/publicdashboards/publicdashboard.go @@ -21,6 +21,7 @@ type Service interface { FindAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error) FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error) FindAll(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error) + Find(ctx context.Context, uid string) (*PublicDashboard, error) Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) Delete(ctx context.Context, orgId int64, uid string) error diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index 812257d6850..2d557248e4e 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -3,15 +3,16 @@ package service import ( "context" "errors" + "fmt" "time" + "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/dashboards" "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/validation" "github.com/grafana/grafana/pkg/services/query" @@ -60,6 +61,14 @@ func ProvideService( } } +func (pd *PublicDashboardServiceImpl) Find(ctx context.Context, uid string) (*PublicDashboard, error) { + pubdash, err := pd.store.Find(ctx, uid) + if err != nil { + return nil, ErrInternalServerError.Errorf("Find: failed to find public dashboard%w", err) + } + return pubdash, nil +} + // FindDashboard Gets a dashboard by Uid func (pd *PublicDashboardServiceImpl) FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error) { dash, err := pd.store.FindDashboard(ctx, orgId, dashboardUid) @@ -281,7 +290,7 @@ func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context. var accessToken string for i := 0; i < 3; i++ { var err error - accessToken, err = tokens.GenerateAccessToken() + accessToken, err = GenerateAccessToken() if err != nil { continue } @@ -396,3 +405,12 @@ func publicDashboardIsEnabledChanged(existingPubdash *PublicDashboard, newPubdas isEnabledChanged := existingPubdash != nil && newPubdash.IsEnabled != existingPubdash.IsEnabled return newDashCreated || isEnabledChanged } + +// GenerateAccessToken generates an uuid formatted without dashes to use as access token +func GenerateAccessToken() (string, error) { + token, err := uuid.NewRandom() + if err != nil { + return "", err + } + return fmt.Sprintf("%x", token[:]), nil +} diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index ebc16b51d80..8a1acd1389b 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "testing" "time" @@ -18,8 +19,8 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" . "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/publicdashboards/database" - "github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/grafana/grafana/pkg/services/publicdashboards/validation" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/serviceaccounts/tests" "github.com/grafana/grafana/pkg/services/tag/tagimpl" @@ -914,7 +915,7 @@ func TestPublicDashboardServiceImpl_NewPublicDashboardAccessToken(t *testing.T) if err == nil { assert.NotEqual(t, got, tt.want, "NewPublicDashboardAccessToken(%v)", tt.args.ctx) - assert.True(t, tokens.IsValidAccessToken(got), "NewPublicDashboardAccessToken(%v)", tt.args.ctx) + assert.True(t, validation.IsValidAccessToken(got), "NewPublicDashboardAccessToken(%v)", tt.args.ctx) store.AssertNumberOfCalls(t, "FindByAccessToken", 1) } else { store.AssertNumberOfCalls(t, "FindByAccessToken", 3) @@ -1028,3 +1029,16 @@ func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardSto dash.Data.Set("uid", dash.UID) return dash } + +func TestGenerateAccessToken(t *testing.T) { + accessToken, err := GenerateAccessToken() + + t.Run("length", func(t *testing.T) { + require.NoError(t, err) + assert.Equal(t, 32, len(accessToken)) + }) + + t.Run("no - ", func(t *testing.T) { + assert.False(t, strings.Contains("-", accessToken)) + }) +} diff --git a/pkg/services/publicdashboards/validation/validation.go b/pkg/services/publicdashboards/validation/validation.go index 6f2c57c83dd..2094cf3545d 100644 --- a/pkg/services/publicdashboards/validation/validation.go +++ b/pkg/services/publicdashboards/validation/validation.go @@ -1,9 +1,11 @@ package validation import ( + "github.com/google/uuid" "github.com/grafana/grafana/pkg/services/dashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" "github.com/grafana/grafana/pkg/tsdb/legacydata" + "github.com/grafana/grafana/pkg/util" ) func ValidatePublicDashboard(dto *SavePublicDashboardDTO, dashboard *dashboards.Dashboard) error { @@ -44,3 +46,15 @@ func ValidateQueryPublicDashboardRequest(req PublicDashboardQueryDTO, pd *Public return nil } + +// IsValidAccessToken asserts that an accessToken is a valid uuid +func IsValidAccessToken(token string) bool { + _, err := uuid.Parse(token) + return err == nil +} + +// IsValidShortUID checks that the uid is not blank and contains valid +// characters. Wraps utils.IsValidShortUID +func IsValidShortUID(uid string) bool { + return uid != "" && util.IsValidShortUID(uid) +} diff --git a/pkg/services/publicdashboards/validation/validation_test.go b/pkg/services/publicdashboards/validation/validation_test.go index 4ee281e20fb..5ba9dcd8c30 100644 --- a/pkg/services/publicdashboards/validation/validation_test.go +++ b/pkg/services/publicdashboards/validation/validation_test.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/dashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -157,3 +158,35 @@ func TestValidateQueryPublicDashboardRequest(t *testing.T) { }) } } + +func TestValidAccessToken(t *testing.T) { + t.Run("true", func(t *testing.T) { + uuid := "da82510c2aa64d78a2e87fef36c58e89" + assert.True(t, IsValidAccessToken(uuid)) + }) + + t.Run("false when blank", func(t *testing.T) { + assert.False(t, IsValidAccessToken("")) + }) + + t.Run("false when can't be parsed by uuid lib", func(t *testing.T) { + // too long + assert.False(t, IsValidAccessToken("0123456789012345678901234567890123456789")) + }) +} + +// we just check base cases since this wraps utils.IsValidShortUID which has +// test coverage +func TestValidUid(t *testing.T) { + t.Run("true", func(t *testing.T) { + assert.True(t, IsValidShortUID("afqrz7jZZ")) + }) + + t.Run("false when blank", func(t *testing.T) { + assert.False(t, IsValidShortUID("")) + }) + + t.Run("false when invalid chars", func(t *testing.T) { + assert.False(t, IsValidShortUID("afqrz7j%%")) + }) +}