PublicDashboards: moved tokens service and new repository method (#61806)

This commit is contained in:
Ezequiel Victorero 2023-01-27 11:20:22 -03:00 committed by GitHub
parent 89ef62f163
commit a128944471
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 125 additions and 105 deletions

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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"))
}

View File

@ -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{

View File

@ -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)
}

View File

@ -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%%"))
})
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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))
})
}

View File

@ -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)
}

View File

@ -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%%"))
})
}