refactor public dashboards middleware testing (#55706)

This PR refactors how we add the orgId to the context on a public dashboard paths. We also split out accessToken handling into its own package and rework status code for "RequiresValidAccessToken". We will be modeling all endpoints to use these status codes going forward. Additionally, it includes a scaffold for better middleware testing and refactors existing tests to table drive tests.
pull/55878/head
Jeff Levin 3 years ago committed by GitHub
parent 609abf00d1
commit 331110bde5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      pkg/api/api.go
  2. 44
      pkg/services/publicdashboards/api/middleware.go
  3. 242
      pkg/services/publicdashboards/api/middleware_test.go
  4. 25
      pkg/services/publicdashboards/database/database.go
  5. 122
      pkg/services/publicdashboards/database/database_test.go
  6. 23
      pkg/services/publicdashboards/internal/tokens/tokens.go
  7. 38
      pkg/services/publicdashboards/internal/tokens/tokens_test.go
  8. 21
      pkg/services/publicdashboards/public_dashboard_service_mock.go
  9. 21
      pkg/services/publicdashboards/public_dashboard_store_mock.go
  10. 6
      pkg/services/publicdashboards/publicdashboard.go
  11. 14
      pkg/services/publicdashboards/service/service.go
  12. 8
      pkg/services/publicdashboards/validation/validation.go
  13. 8
      pkg/services/publicdashboards/validation/validation_test.go

@ -141,7 +141,12 @@ func (hs *HTTPServer) registerRoutes() {
}
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) {
r.Get("/public-dashboards/:accessToken", publicdashboardsapi.SetPublicDashboardFlag(), publicdashboardsapi.CountPublicDashboardRequest(), hs.Index)
r.Get("/public-dashboards/:accessToken",
publicdashboardsapi.SetPublicDashboardFlag,
publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService),
publicdashboardsapi.CountPublicDashboardRequest(),
hs.Index,
)
}
r.Get("/explore", authorize(func(c *models.ReqContext) {

@ -2,46 +2,60 @@ package api
import (
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
"github.com/grafana/grafana/pkg/web"
)
func SetPublicDashboardFlag() func(c *models.ReqContext) {
// Adds orgId to context based on org of public dashboard
func SetPublicDashboardOrgIdOnContext(publicDashboardService publicdashboards.Service) func(c *models.ReqContext) {
return func(c *models.ReqContext) {
// TODO: Find a better place to set this, or rename this function
orgIDValue := c.Req.URL.Query().Get("orgId")
orgID, err := strconv.ParseInt(orgIDValue, 10, 64)
if err == nil && orgID > 0 && orgID != c.OrgID {
c.OrgID = orgID
accessToken, ok := web.Params(c.Req)[":accessToken"]
if !ok || !tokens.IsValidAccessToken(accessToken) {
return
}
// Get public dashboard
orgId, err := publicDashboardService.GetPublicDashboardOrgId(c.Req.Context(), accessToken)
if err != nil {
return
}
c.IsPublicDashboardView = true
c.OrgID = orgId
}
}
// Adds public dashboard flag on context
func SetPublicDashboardFlag(c *models.ReqContext) {
c.IsPublicDashboardView = true
}
// Middleware to enforce that a public dashboards exists before continuing to
// handler
func RequiresValidAccessToken(publicDashboardService publicdashboards.Service) func(c *models.ReqContext) {
return func(c *models.ReqContext) {
accessToken, ok := web.Params(c.Req)[":accessToken"]
// Check access token is present on the request
if !ok || accessToken == "" {
c.JsonApiErr(http.StatusBadRequest, "Invalid access token", nil)
if !ok {
c.JsonApiErr(http.StatusBadRequest, "No access token provided", nil)
return
}
if !tokens.IsValidAccessToken(accessToken) {
c.JsonApiErr(http.StatusBadRequest, "Invalid access token", nil)
}
// Check that the access token references an enabled public dashboard
exists, err := publicDashboardService.AccessTokenExists(c.Req.Context(), accessToken)
if err != nil {
c.JsonApiErr(http.StatusInternalServerError, "Error validating access token", nil)
c.JsonApiErr(http.StatusInternalServerError, "Failed to query access token", nil)
return
}
if !exists {
c.JsonApiErr(http.StatusBadRequest, "Invalid access token", nil)
c.JsonApiErr(http.StatusNotFound, "Public dashboard not found", nil)
return
}
}

@ -1,105 +1,187 @@
package api
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"errors"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
"github.com/grafana/grafana/pkg/services/publicdashboards"
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
"github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/web"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestRequiresValidAccessToken(t *testing.T) {
t.Run("Returns 404 when access token is empty", func(t *testing.T) {
request, err := http.NewRequest("GET", "/api/public/ma/events/", nil)
require.NoError(t, err)
resp := runMiddleware(request, mockAccessTokenExistsResponse(false, nil))
require.Equal(t, http.StatusNotFound, resp.Code)
})
t.Run("Returns 200 when public dashboard with access token exists", func(t *testing.T) {
request, err := http.NewRequest("GET", "/api/public/ma/events/myAccessToken", nil)
require.NoError(t, err)
resp := runMiddleware(request, mockAccessTokenExistsResponse(true, nil))
require.Equal(t, http.StatusOK, resp.Code)
})
t.Run("Returns 400 when public dashboard with access token does not exist", func(t *testing.T) {
request, err := http.NewRequest("GET", "/api/public/ma/events/myAccessToken", nil)
require.NoError(t, err)
resp := runMiddleware(request, mockAccessTokenExistsResponse(false, nil))
require.Equal(t, http.StatusBadRequest, resp.Code)
})
t.Run("Returns 500 when public dashboard service gives an error", func(t *testing.T) {
request, err := http.NewRequest("GET", "/api/public/ma/events/myAccessToken", nil)
require.NoError(t, err)
resp := runMiddleware(request, mockAccessTokenExistsResponse(false, fmt.Errorf("error not found")))
var validAccessToken, _ = tokens.GenerateAccessToken()
require.Equal(t, http.StatusInternalServerError, resp.Code)
})
func TestRequiresValidAccessToken(t *testing.T) {
tests := []struct {
Name string
Path string
AccessTokenExists bool
AccessTokenExistsErr error
AccessToken string
ExpectedResponseCode int
}{
{
Name: "Returns 200 when public dashboard with access token exists",
Path: "/api/public/ma/events/myAccesstoken",
AccessTokenExists: true,
AccessTokenExistsErr: nil,
AccessToken: validAccessToken,
ExpectedResponseCode: http.StatusOK,
},
{
Name: "Returns 400 when access token is empty",
Path: "/api/public/ma/events/",
AccessTokenExists: false,
AccessTokenExistsErr: nil,
AccessToken: "",
ExpectedResponseCode: http.StatusBadRequest,
},
{
Name: "Returns 400 when invalid access token",
Path: "/api/public/ma/events/myAccesstoken",
AccessTokenExists: false,
AccessTokenExistsErr: nil,
AccessToken: "invalidAccessToken",
ExpectedResponseCode: http.StatusBadRequest,
},
{
Name: "Returns 404 when public dashboard with access token does not exist",
Path: "/api/public/ma/events/myAccesstoken",
AccessTokenExists: false,
AccessTokenExistsErr: nil,
AccessToken: validAccessToken,
ExpectedResponseCode: http.StatusNotFound,
},
{
Name: "Returns 500 when public dashboard service gives an error",
Path: "/api/public/ma/events/myAccesstoken",
AccessTokenExists: false,
AccessTokenExistsErr: fmt.Errorf("error not found"),
AccessToken: validAccessToken,
ExpectedResponseCode: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
publicdashboardService := &publicdashboards.FakePublicDashboardService{}
publicdashboardService.On("AccessTokenExists", mock.Anything, mock.Anything).Return(tt.AccessTokenExists, tt.AccessTokenExistsErr)
params := map[string]string{":accessToken": tt.AccessToken}
mw := RequiresValidAccessToken(publicdashboardService)
_, resp := runMw(t, nil, "GET", tt.Path, params, mw)
require.Equal(t, tt.ExpectedResponseCode, resp.Code)
})
}
}
func mockAccessTokenExistsResponse(returnArguments ...interface{}) *publicdashboardsService.PublicDashboardServiceImpl {
fakeStore := &publicdashboards.FakePublicDashboardStore{}
fakeStore.On("AccessTokenExists", mock.Anything, mock.Anything).Return(returnArguments[0], returnArguments[1])
qds := query.ProvideService(
nil,
nil,
nil,
&fakePluginRequestValidator{},
&fakeDatasources.FakeDataSourceService{},
&fakePluginClient{
QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
resp := backend.Responses{
"A": backend.DataResponse{
Error: fmt.Errorf("query failed"),
},
}
return &backend.QueryDataResponse{Responses: resp}, nil
},
func TestSetPublicDashboardOrgIdOnContext(t *testing.T) {
tests := []struct {
Name string
AccessToken string
OrgIdResp int64
ErrorResp error
ExpectedOrgId int64
}{
{
Name: "Adds orgId for enabled public dashboard",
AccessToken: validAccessToken,
OrgIdResp: 7,
ErrorResp: nil,
ExpectedOrgId: 7,
},
&fakeOAuthTokenService{},
)
return publicdashboardsService.ProvideService(setting.NewCfg(), fakeStore, qds)
{
Name: "Does not set orgId or fail with invalid accessToken",
AccessToken: "invalidAccessToken",
OrgIdResp: 0,
ErrorResp: nil,
ExpectedOrgId: 0,
},
{
Name: "Does not set orgId or fail with disabled public dashboard",
AccessToken: validAccessToken,
OrgIdResp: 0,
ErrorResp: nil,
ExpectedOrgId: 0,
},
{
Name: "Does not set orgId or fail with error querying public dashboard",
AccessToken: validAccessToken,
OrgIdResp: 0,
ErrorResp: errors.New("database error of some sort"),
ExpectedOrgId: 0,
},
{
Name: "Does not set orgId or fail with missing public dashboard",
AccessToken: validAccessToken,
OrgIdResp: 0,
ErrorResp: nil,
ExpectedOrgId: 0,
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
publicdashboardService := &publicdashboards.FakePublicDashboardService{}
publicdashboardService.On("GetPublicDashboardOrgId", mock.Anything, tt.AccessToken).Return(
tt.OrgIdResp,
tt.ErrorResp,
)
params := map[string]string{":accessToken": tt.AccessToken}
mw := SetPublicDashboardOrgIdOnContext(publicdashboardService)
ctx, _ := runMw(t, nil, "GET", "/public-dashboard/myaccesstoken", params, mw)
assert.Equal(t, tt.ExpectedOrgId, ctx.OrgID)
})
}
}
func runMiddleware(request *http.Request, pubdashService *publicdashboardsService.PublicDashboardServiceImpl) *httptest.ResponseRecorder {
recorder := httptest.NewRecorder()
m := web.New()
initCtx := &models.ReqContext{}
m.Use(func(c *web.Context) {
initCtx.Context = c
c.Req = c.Req.WithContext(ctxkey.Set(c.Req.Context(), initCtx))
func TestSetPublicDashboardFlag(t *testing.T) {
t.Run("Adds context.IsPublicDashboardView=true to request", func(t *testing.T) {
ctx := &models.ReqContext{}
SetPublicDashboardFlag(ctx)
assert.True(t, ctx.IsPublicDashboardView)
})
m.Get("/api/public/ma/events/:accessToken", RequiresValidAccessToken(pubdashService), mockValidRequestHandler)
m.ServeHTTP(recorder, request)
return recorder
}
func mockValidRequestHandler(c *models.ReqContext) {
resp := make(map[string]interface{})
resp["message"] = "Valid request"
c.JSON(http.StatusOK, resp)
// This is a helper to test middleware. It handles creating a
// proper models.ReqContext, setting web parameters, executing middleware, and
// returning a response. Response will default to result of
// httptest.NewRecorder() return value and will only change if modified by the
// middlware as this will no accept a handler method
func runMw(t *testing.T, ctx *models.ReqContext, httpmethod string, path string, webparams map[string]string, mw func(c *models.ReqContext)) (*models.ReqContext, *httptest.ResponseRecorder) {
// create valid request context and set 0 values if they don't exist
if ctx == nil {
ctx = &models.ReqContext{}
}
if ctx.Context == nil {
ctx.Context = &web.Context{}
}
if ctx.SignedInUser == nil {
ctx.SignedInUser = &user.SignedInUser{}
}
// create request and add params
request, err := http.NewRequest(httpmethod, path, nil)
require.NoError(t, err)
request = web.SetURLParams(request, webparams)
ctx.Req = request
// setup response recorder to return
response := httptest.NewRecorder()
ctx.Context.Resp = web.NewResponseWriter("GET", response)
// run middleware
mw(ctx)
// return result
return ctx, response
}

@ -53,7 +53,8 @@ func (d *PublicDashboardStoreImpl) GetDashboard(ctx context.Context, dashboardUi
return dashboard, err
}
// Retrieves public dashboard configuration
// Retrieves public dashboard. This method makes 2 queries to the db so that in the
// even that the underlying dashboard is missing it will return a 404.
func (d *PublicDashboardStoreImpl) GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error) {
if accessToken == "" {
return nil, nil, ErrPublicDashboardIdentifierNotSet
@ -181,7 +182,7 @@ func (d *PublicDashboardStoreImpl) SavePublicDashboardConfig(ctx context.Context
return err
}
// updates existing public dashboard configuration
// Updates existing public dashboard configuration
func (d *PublicDashboardStoreImpl) UpdatePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error {
err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
timeSettingsJSON, err := json.Marshal(cmd.PublicDashboard.TimeSettings)
@ -206,6 +207,7 @@ func (d *PublicDashboardStoreImpl) UpdatePublicDashboardConfig(ctx context.Conte
return err
}
// Responds true if public dashboard for a dashboard exists and isEnabled
func (d *PublicDashboardStoreImpl) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
hasPublicDashboard := false
err := d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
@ -224,6 +226,8 @@ func (d *PublicDashboardStoreImpl) PublicDashboardEnabled(ctx context.Context, d
return hasPublicDashboard, err
}
// Responds true if accessToken exists and isEnabled. May be renamed in the
// future
func (d *PublicDashboardStoreImpl) AccessTokenExists(ctx context.Context, accessToken string) (bool, error) {
hasPublicDashboard := false
err := d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
@ -241,3 +245,20 @@ func (d *PublicDashboardStoreImpl) AccessTokenExists(ctx context.Context, access
return hasPublicDashboard, err
}
// Responds with OrgId from if exists and isEnabled.
func (d *PublicDashboardStoreImpl) GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error) {
var orgId int64
err := d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
sql := "SELECT org_id FROM dashboard_public WHERE access_token=? AND is_enabled=true"
_, err := dbSession.SQL(sql, accessToken).Get(&orgId)
if err != nil {
return err
}
return err
})
return orgId, err
}

@ -52,8 +52,8 @@ func TestIntegrationGetDashboard(t *testing.T) {
})
}
// GetPublicDashboard
func TestIntegrationGetPublicDashboard(t *testing.T) {
// AccessTokenExists
func TestIntegrationAccessTokenExists(t *testing.T) {
var sqlStore *sqlstore.SQLStore
var dashboardStore *dashboardsDB.DashboardStore
var publicdashboardStore *PublicDashboardStoreImpl
@ -87,6 +87,28 @@ func TestIntegrationGetPublicDashboard(t *testing.T) {
require.True(t, res)
})
t.Run("AccessTokenExists will return false when IsEnabled=false", func(t *testing.T) {
setup()
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
PublicDashboard: PublicDashboard{
IsEnabled: false,
Uid: "abc123",
DashboardUid: savedDashboard.Uid,
OrgId: savedDashboard.OrgId,
CreatedAt: time.Now(),
CreatedBy: 7,
AccessToken: "accessToken",
},
})
require.NoError(t, err)
res, err := publicdashboardStore.AccessTokenExists(context.Background(), "accessToken")
require.NoError(t, err)
require.False(t, res)
})
t.Run("AccessTokenExists will return false when no public dashboard has matching access token", func(t *testing.T) {
setup()
@ -95,6 +117,21 @@ func TestIntegrationGetPublicDashboard(t *testing.T) {
require.NoError(t, err)
require.False(t, res)
})
}
// PublicDashboardEnabled
func TestIntegrationPublicDashboardEnabled(t *testing.T) {
var sqlStore *sqlstore.SQLStore
var dashboardStore *dashboardsDB.DashboardStore
var publicdashboardStore *PublicDashboardStoreImpl
var savedDashboard *models.Dashboard
setup := func() {
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
publicdashboardStore = ProvideStore(sqlStore)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
}
t.Run("PublicDashboardEnabled Will return true when dashboard has at least one enabled public dashboard", func(t *testing.T) {
setup()
@ -139,6 +176,21 @@ func TestIntegrationGetPublicDashboard(t *testing.T) {
require.False(t, res)
})
}
// GetPublicDashboard
func TestIntegrationGetPublicDashboard(t *testing.T) {
var sqlStore *sqlstore.SQLStore
var dashboardStore *dashboardsDB.DashboardStore
var publicdashboardStore *PublicDashboardStoreImpl
var savedDashboard *models.Dashboard
setup := func() {
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
publicdashboardStore = ProvideStore(sqlStore)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
}
t.Run("returns PublicDashboard and Dashboard", func(t *testing.T) {
setup()
@ -310,6 +362,7 @@ func TestIntegrationSavePublicDashboardConfig(t *testing.T) {
})
}
// UpdatePublicDashboardConfig
func TestIntegrationUpdatePublicDashboard(t *testing.T) {
var sqlStore *sqlstore.SQLStore
var dashboardStore *dashboardsDB.DashboardStore
@ -389,6 +442,71 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
})
}
// GetPublicDashboardOrgId
func TestIntegrationGetPublicDashboardOrgId(t *testing.T) {
var sqlStore *sqlstore.SQLStore
var dashboardStore *dashboardsDB.DashboardStore
var publicdashboardStore *PublicDashboardStoreImpl
var savedDashboard *models.Dashboard
setup := func() {
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
publicdashboardStore = ProvideStore(sqlStore)
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
}
t.Run("GetPublicDashboardOrgId will OrgId when enabled", func(t *testing.T) {
setup()
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
PublicDashboard: PublicDashboard{
IsEnabled: true,
Uid: "abc123",
DashboardUid: savedDashboard.Uid,
OrgId: savedDashboard.OrgId,
CreatedAt: time.Now(),
CreatedBy: 7,
AccessToken: "accessToken",
},
})
require.NoError(t, err)
orgId, err := publicdashboardStore.GetPublicDashboardOrgId(context.Background(), "accessToken")
require.NoError(t, err)
assert.Equal(t, savedDashboard.OrgId, orgId)
})
t.Run("GetPublicDashboardOrgId will return 0 when IsEnabled=false", func(t *testing.T) {
setup()
err := publicdashboardStore.SavePublicDashboardConfig(context.Background(), SavePublicDashboardConfigCommand{
PublicDashboard: PublicDashboard{
IsEnabled: false,
Uid: "abc123",
DashboardUid: savedDashboard.Uid,
OrgId: savedDashboard.OrgId,
CreatedAt: time.Now(),
CreatedBy: 7,
AccessToken: "accessToken",
},
})
require.NoError(t, err)
orgId, err := publicdashboardStore.GetPublicDashboardOrgId(context.Background(), "accessToken")
require.NoError(t, err)
assert.NotEqual(t, savedDashboard.OrgId, orgId)
})
t.Run("GetPublicDashboardOrgId will return 0 when no public dashboard has matching access token", func(t *testing.T) {
setup()
orgId, err := publicdashboardStore.GetPublicDashboardOrgId(context.Background(), "nonExistentAccessToken")
require.NoError(t, err)
assert.NotEqual(t, savedDashboard.OrgId, orgId)
})
}
// helper function insertTestDashboard
func insertTestDashboard(t *testing.T, dashboardStore *dashboardsDB.DashboardStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()

@ -0,0 +1,23 @@
package tokens
import (
"fmt"
"github.com/google/uuid"
)
// generates a 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
}
// asserts that an accessToken is a valid uuid
func IsValidAccessToken(token string) bool {
_, err := uuid.Parse(token)
return err == nil
}

@ -0,0 +1,38 @@
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"))
})
}

@ -168,6 +168,27 @@ func (_m *FakePublicDashboardService) GetPublicDashboardConfig(ctx context.Conte
return r0, r1
}
// GetPublicDashboardOrgId provides a mock function with given fields: ctx, accessToken
func (_m *FakePublicDashboardService) GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error) {
ret := _m.Called(ctx, accessToken)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok {
r0 = rf(ctx, accessToken)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, accessToken)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetQueryDataResponse provides a mock function with given fields: ctx, skipCache, reqDTO, panelId, accessToken
func (_m *FakePublicDashboardService) GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO publicdashboardsmodels.PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
ret := _m.Called(ctx, skipCache, reqDTO, panelId, accessToken)

@ -161,6 +161,27 @@ func (_m *FakePublicDashboardStore) GetPublicDashboardConfig(ctx context.Context
return r0, r1
}
// GetPublicDashboardOrgId provides a mock function with given fields: ctx, accessToken
func (_m *FakePublicDashboardStore) GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error) {
ret := _m.Called(ctx, accessToken)
var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok {
r0 = rf(ctx, accessToken)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, accessToken)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PublicDashboardEnabled provides a mock function with given fields: ctx, dashboardUid
func (_m *FakePublicDashboardStore) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
ret := _m.Called(ctx, dashboardUid)

@ -16,10 +16,11 @@ import (
type Service interface {
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) (*user.SignedInUser, error)
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error)
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error)
GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error)
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
SavePublicDashboardConfig(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardConfigDTO) (*PublicDashboard, error)
@ -28,11 +29,12 @@ type Service interface {
//go:generate mockery --name Store --structname FakePublicDashboardStore --inpackage --filename public_dashboard_store_mock.go
type Store interface {
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
GenerateNewPublicDashboardUid(ctx context.Context) (string, error)
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
GetPublicDashboardByUid(ctx context.Context, uid string) (*PublicDashboard, error)
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error)
PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error)
SavePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error
UpdatePublicDashboardConfig(ctx context.Context, cmd SavePublicDashboardConfigCommand) error

@ -5,13 +5,13 @@ import (
"fmt"
"time"
"github.com/google/uuid"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
"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/queries"
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
@ -147,7 +147,7 @@ func (pd *PublicDashboardServiceImpl) savePublicDashboardConfig(ctx context.Cont
return "", err
}
accessToken, err := GenerateAccessToken()
accessToken, err := tokens.GenerateAccessToken()
if err != nil {
return "", err
}
@ -292,14 +292,8 @@ func (pd *PublicDashboardServiceImpl) AccessTokenExists(ctx context.Context, acc
return pd.store.AccessTokenExists(ctx, accessToken)
}
// generates a 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
func (pd *PublicDashboardServiceImpl) GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error) {
return pd.store.GetPublicDashboardOrgId(ctx, accessToken)
}
// intervalMS and maxQueryData values are being calculated on the frontend for regular dashboards

@ -4,12 +4,12 @@ import (
"fmt"
"github.com/grafana/grafana/pkg/models"
publicDashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
)
func ValidateSavePublicDashboard(dto *publicDashboardModels.SavePublicDashboardConfigDTO, dashboard *models.Dashboard) error {
func ValidateSavePublicDashboard(dto *SavePublicDashboardConfigDTO, dashboard *models.Dashboard) error {
if hasTemplateVariables(dashboard) {
return publicDashboardModels.ErrPublicDashboardHasTemplateVariables
return ErrPublicDashboardHasTemplateVariables
}
return nil
@ -21,7 +21,7 @@ func hasTemplateVariables(dashboard *models.Dashboard) bool {
return len(templateVariables) > 0
}
func ValidateQueryPublicDashboardRequest(req publicDashboardModels.PublicDashboardQueryDTO) error {
func ValidateQueryPublicDashboardRequest(req PublicDashboardQueryDTO) error {
if req.IntervalMs < 0 {
return fmt.Errorf("intervalMS should be greater than 0")
}

@ -5,7 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/stretchr/testify/require"
)
@ -22,10 +22,10 @@ func TestValidateSavePublicDashboard(t *testing.T) {
}`)
dashboardData, _ := simplejson.NewJson(templateVars)
dashboard := models.NewDashboardFromJson(dashboardData)
dto := &publicdashboardModels.SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
dto := &SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
err := ValidateSavePublicDashboard(dto, dashboard)
require.ErrorContains(t, err, publicdashboardModels.ErrPublicDashboardHasTemplateVariables.Reason)
require.ErrorContains(t, err, ErrPublicDashboardHasTemplateVariables.Reason)
})
t.Run("Returns no validation error when dashboard has no template variables", func(t *testing.T) {
@ -36,7 +36,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
}`)
dashboardData, _ := simplejson.NewJson(templateVars)
dashboard := models.NewDashboardFromJson(dashboardData)
dto := &publicdashboardModels.SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
dto := &SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
err := ValidateSavePublicDashboard(dto, dashboard)
require.NoError(t, err)

Loading…
Cancel
Save