diff --git a/pkg/api/api.go b/pkg/api/api.go index 88f432c206c..da1efa5067f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -187,6 +187,15 @@ func (hs *HTTPServer) registerRoutes() { publicdashboardsapi.CountPublicDashboardRequest(), hs.Index, ) + + r.Get("/bootdata/:accessToken", + reqNoAuth, + hs.PublicDashboardsApi.Middleware.HandleView, + publicdashboardsapi.SetPublicDashboardAccessToken, + publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService), + publicdashboardsapi.CountPublicDashboardRequest(), + hs.GetBootdata, + ) } r.Get("/explore", authorize(ac.EvalPermission(ac.ActionDatasourcesExplore)), hs.Index) diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index f730bfba376..943509024d3 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -22,6 +22,7 @@ import ( fswebassets "github.com/grafana/grafana/pkg/services/frontend/webassets" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/licensing" + publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" ) @@ -164,6 +165,11 @@ func (s *frontendService) registerRoutes(m *web.Mux) { // uses cache busting to ensure requests aren't cached. s.routeGet(m, "/-/fe-boot-error", s.handleBootError) + s.routeGet(m, "/public-dashboards/:accessToken", + publicdashboardsapi.SetPublicDashboardAccessToken, + s.index.HandleRequest, + ) + // All other requests return index.html s.routeGet(m, "/*", s.index.HandleRequest) } diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index 22d04234a36..e87ca894d20 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -45,6 +45,8 @@ type IndexViewData struct { // Nonce is a cryptographic identifier for use with Content Security Policy. Nonce string + + PublicDashboardAccessToken string } // Templates setup. @@ -138,9 +140,12 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http. return } + reqCtx := contexthandler.FromContext(ctx) + // TODO -- restructure so the static stuff is under one variable and the rest is dynamic data := p.data // copy everything data.Nonce = nonce + data.PublicDashboardAccessToken = reqCtx.PublicDashboardAccessToken if data.CSPEnabled { data.CSPContent = middleware.ReplacePolicyVariables(p.data.CSPContent, p.data.AppSubUrl, data.Nonce) @@ -150,7 +155,6 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http. writer.Header().Set("Content-Security-Policy-Report-Only", policy) } - reqCtx := contexthandler.FromContext(ctx) p.runIndexDataHooks(reqCtx, &data) writer.Header().Set("Content-Type", "text/html; charset=UTF-8") diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index b0364fcbac9..198b8216189 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -188,6 +188,7 @@ // Wrap in an IIFE to avoid polluting the global scope. Intentionally global-scope properties // are explicitly assigned to the `window` object. (() => { + const publicDashboardAccessToken = [[.PublicDashboardAccessToken]] // Grafana can only fail to load once // However, it can fail to load in multiple different places // To avoid double reporting the error, we use this boolean to check if we've already failed @@ -271,9 +272,15 @@ async function fetchBootData() { const queryParams = new URLSearchParams(window.location.search); + let path = '/bootdata'; + // call a special bootdata url with the public access token + // this is needed to set the access token and correct org for public dashboards on the ST backend + if (publicDashboardAccessToken) { + path += `/${publicDashboardAccessToken}`; + } // pass the search params through to the bootdata request // this allows for overriding the theme/language etc - const bootDataUrl = new URL('/bootdata', window.location.origin); + const bootDataUrl = new URL(path, window.location.origin); for (const [key, value] of queryParams.entries()) { bootDataUrl.searchParams.append(key, value); } diff --git a/public/app/features/dashboard/routes.ts b/public/app/features/dashboard/routes.ts index 84e7d514b99..57702d1a35d 100644 --- a/public/app/features/dashboard/routes.ts +++ b/public/app/features/dashboard/routes.ts @@ -24,6 +24,7 @@ export const getPublicDashboardRoutes = (): RouteDescriptor[] => { { path: '/public-dashboards/:accessToken', pageClass: 'page-dashboard', + allowAnonymous: true, routeName: DashboardRoutes.Public, chromeless: true, component: SafeDynamicImport(