From 15e34505e2213b47a47d922a0cd306e071565d12 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Fri, 5 May 2023 17:17:18 +0200 Subject: [PATCH] Render analytics identifiers (#67860) * Append analytics identifier upon authenticate session * Add id and module upon syncing user to identity * Add authModule & id to `IdentityFromSignedInUser` * Allow req calls in test to use basic auth * Add `intercom_secret` to grafana config in tests * Add test for analytics render in html view --- pkg/services/authn/authn.go | 2 + .../authn/authnimpl/sync/user_sync.go | 2 + pkg/services/contexthandler/contexthandler.go | 24 ++++++++- pkg/tests/testinfra/testinfra.go | 5 ++ pkg/tests/web/index_view_test.go | 51 +++++++++++++++++-- 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 02eef4fe10b..500507620ee 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -326,6 +326,8 @@ func IdentityFromSignedInUser(id string, usr *user.SignedInUser, params ClientPa Teams: usr.Teams, ClientParams: params, Permissions: usr.Permissions, + AuthModule: usr.ExternalAuthModule, + AuthID: usr.ExternalAuthID, } } diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index b4b38e2dbcc..20c120f262f 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -392,6 +392,8 @@ func syncSignedInUserToIdentity(usr *user.SignedInUser, identity *authn.Identity identity.LastSeenAt = usr.LastSeenAt identity.IsDisabled = usr.IsDisabled identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin + identity.AuthID = usr.ExternalAuthID + identity.AuthModule = usr.ExternalAuthModule } func shouldUpdateLastSeen(t time.Time) bool { diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index b0b96273094..5c3760af292 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -3,6 +3,9 @@ package contexthandler import ( "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "net/http" @@ -110,6 +113,25 @@ func FromContext(c context.Context) *contextmodel.ReqContext { return nil } +func hashUserIdentifier(identifier string, secret string) string { + key := []byte(secret) + h := hmac.New(sha256.New, key) + h.Write([]byte(identifier)) + return hex.EncodeToString(h.Sum(nil)) +} + +func setSignedInUser(reqContext *contextmodel.ReqContext, identity *authn.Identity, intercomSecret string) { + reqContext.SignedInUser = identity.SignedInUser() + if identity.AuthID != "" { + reqContext.SignedInUser.Analytics.Identifier = identity.AuthID + } else { + reqContext.SignedInUser.Analytics.Identifier = identity.Email + "@" + setting.AppUrl + } + if intercomSecret != "" { + reqContext.SignedInUser.Analytics.IntercomIdentifier = hashUserIdentifier(identity.AuthID, intercomSecret) + } +} + // Middleware provides a middleware to initialize the request context. func (h *ContextHandler) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -151,7 +173,7 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler { reqContext.LookupTokenErr = err } else { reqContext.UserToken = identity.SessionToken - reqContext.SignedInUser = identity.SignedInUser() + setSignedInUser(reqContext, identity, h.Cfg.IntercomSecret) reqContext.IsSignedIn = !identity.IsAnonymous reqContext.AllowAnonymous = identity.IsAnonymous reqContext.IsRenderCall = identity.AuthModule == login.RenderModule diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index b4302fc482a..535cd1ac89e 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -216,6 +216,11 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) { _, err = rbacSect.NewKey("permission_cache", "false") require.NoError(t, err) + analyticsSect, err := cfg.NewSection("analytics") + require.NoError(t, err) + _, err = analyticsSect.NewKey("intercom_secret", "intercom_secret_at_config") + require.NoError(t, err) + getOrCreateSection := func(name string) (*ini.Section, error) { section, err := cfg.GetSection(name) if err != nil { diff --git a/pkg/tests/web/index_view_test.go b/pkg/tests/web/index_view_test.go index 8286abde819..7a17a816dff 100644 --- a/pkg/tests/web/index_view_test.go +++ b/pkg/tests/web/index_view_test.go @@ -1,6 +1,7 @@ package web import ( + "encoding/json" "fmt" "io" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/tests/testinfra" ) @@ -27,7 +29,7 @@ func TestIntegrationIndexView(t *testing.T) { addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath) // nolint:bodyclose - resp, html := makeRequest(t, addr) + resp, html := makeRequest(t, addr, "", "") assert.Regexp(t, `script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' 'nonce-[^']+';object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src \* data:;base-uri 'self';connect-src 'self' grafana.com ws://localhost:3000/ wss://localhost:3000/;manifest-src 'self';media-src 'none';form-action 'self';`, resp.Header.Get("Content-Security-Policy")) assert.Regexp(t, `