mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
ae1a85b5ad
commit
15e34505e2
@ -326,6 +326,8 @@ func IdentityFromSignedInUser(id string, usr *user.SignedInUser, params ClientPa
|
|||||||
Teams: usr.Teams,
|
Teams: usr.Teams,
|
||||||
ClientParams: params,
|
ClientParams: params,
|
||||||
Permissions: usr.Permissions,
|
Permissions: usr.Permissions,
|
||||||
|
AuthModule: usr.ExternalAuthModule,
|
||||||
|
AuthID: usr.ExternalAuthID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,6 +392,8 @@ func syncSignedInUserToIdentity(usr *user.SignedInUser, identity *authn.Identity
|
|||||||
identity.LastSeenAt = usr.LastSeenAt
|
identity.LastSeenAt = usr.LastSeenAt
|
||||||
identity.IsDisabled = usr.IsDisabled
|
identity.IsDisabled = usr.IsDisabled
|
||||||
identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin
|
identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin
|
||||||
|
identity.AuthID = usr.ExternalAuthID
|
||||||
|
identity.AuthModule = usr.ExternalAuthModule
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldUpdateLastSeen(t time.Time) bool {
|
func shouldUpdateLastSeen(t time.Time) bool {
|
||||||
|
@ -3,6 +3,9 @@ package contexthandler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -110,6 +113,25 @@ func FromContext(c context.Context) *contextmodel.ReqContext {
|
|||||||
return nil
|
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.
|
// Middleware provides a middleware to initialize the request context.
|
||||||
func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
|
func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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
|
reqContext.LookupTokenErr = err
|
||||||
} else {
|
} else {
|
||||||
reqContext.UserToken = identity.SessionToken
|
reqContext.UserToken = identity.SessionToken
|
||||||
reqContext.SignedInUser = identity.SignedInUser()
|
setSignedInUser(reqContext, identity, h.Cfg.IntercomSecret)
|
||||||
reqContext.IsSignedIn = !identity.IsAnonymous
|
reqContext.IsSignedIn = !identity.IsAnonymous
|
||||||
reqContext.AllowAnonymous = identity.IsAnonymous
|
reqContext.AllowAnonymous = identity.IsAnonymous
|
||||||
reqContext.IsRenderCall = identity.AuthModule == login.RenderModule
|
reqContext.IsRenderCall = identity.AuthModule == login.RenderModule
|
||||||
|
@ -216,6 +216,11 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
|
|||||||
_, err = rbacSect.NewKey("permission_cache", "false")
|
_, err = rbacSect.NewKey("permission_cache", "false")
|
||||||
require.NoError(t, err)
|
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) {
|
getOrCreateSection := func(name string) (*ini.Section, error) {
|
||||||
section, err := cfg.GetSection(name)
|
section, err := cfg.GetSection(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +29,7 @@ func TestIntegrationIndexView(t *testing.T) {
|
|||||||
addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath)
|
addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath)
|
||||||
|
|
||||||
// nolint:bodyclose
|
// 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, `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, `<script nonce="[^"]+"`, html)
|
assert.Regexp(t, `<script nonce="[^"]+"`, html)
|
||||||
})
|
})
|
||||||
@ -37,20 +39,59 @@ func TestIntegrationIndexView(t *testing.T) {
|
|||||||
addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath)
|
addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath)
|
||||||
|
|
||||||
// nolint:bodyclose
|
// nolint:bodyclose
|
||||||
resp, html := makeRequest(t, addr)
|
resp, html := makeRequest(t, addr, "", "")
|
||||||
|
|
||||||
assert.Empty(t, resp.Header.Get("Content-Security-Policy"))
|
assert.Empty(t, resp.Header.Get("Content-Security-Policy"))
|
||||||
assert.Regexp(t, `<script nonce=""`, html)
|
assert.Regexp(t, `<script nonce=""`, html)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Test the exposed user data contains the analytics identifiers", func(t *testing.T) {
|
||||||
|
grafDir, cfgPath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
|
EnableFeatureToggles: []string{"authnService"},
|
||||||
|
})
|
||||||
|
|
||||||
|
addr, store := testinfra.StartGrafana(t, grafDir, cfgPath)
|
||||||
|
createdUser := testinfra.CreateUser(t, store, user.CreateUserCommand{
|
||||||
|
Login: "admin",
|
||||||
|
Password: "admin",
|
||||||
|
Email: "admin@grafana.com",
|
||||||
|
OrgID: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// insert user_auth relationship
|
||||||
|
query := fmt.Sprintf(`INSERT INTO "user_auth" ("user_id", "auth_module", "auth_id", "created") VALUES ('%d', 'oauth_grafana_com', 'test-id-oauth-grafana', '2023-03-13 14:08:11')`, createdUser.ID)
|
||||||
|
_, err := store.GetEngine().Exec(query)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// nolint:bodyclose
|
||||||
|
response, html := makeRequest(t, addr, "admin", "admin")
|
||||||
|
assert.Equal(t, 200, response.StatusCode)
|
||||||
|
|
||||||
|
// parse User.Analytics HTML view into user.AnalyticsSettings model
|
||||||
|
parsedHTML := strings.Split(html, "analytics\":")[1]
|
||||||
|
parsedHTML = strings.Split(parsedHTML, "},\n")[0]
|
||||||
|
|
||||||
|
var analyticsSettings user.AnalyticsSettings
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(parsedHTML), &analyticsSettings))
|
||||||
|
|
||||||
|
require.Equal(t, "test-id-oauth-grafana", analyticsSettings.Identifier)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRequest(t *testing.T, addr string) (*http.Response, string) {
|
func makeRequest(t *testing.T, addr, username, passwowrd string) (*http.Response, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
u := fmt.Sprintf("http://%s", addr)
|
u := fmt.Sprintf("http://%s", addr)
|
||||||
t.Logf("Making GET request to %s", u)
|
t.Logf("Making GET request to %s", u)
|
||||||
// nolint:gosec
|
|
||||||
resp, err := http.Get(u)
|
request, err := http.NewRequest("GET", u, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if username != "" && passwowrd != "" {
|
||||||
|
request.SetBasicAuth(username, passwowrd)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(request)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, resp)
|
require.NotNil(t, resp)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
Loading…
Reference in New Issue
Block a user