diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4318d0b5e47..c696564a34a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -518,17 +518,18 @@ lerna.json @grafana/frontend-ops # Grafana authentication and authorization /pkg/login/ @grafana/grafana-authnz-team /pkg/services/accesscontrol/ @grafana/grafana-authnz-team +/pkg/services/anonymous/ @grafana/grafana-authnz-team /pkg/services/auth/ @grafana/grafana-authnz-team +/pkg/services/authn/ @grafana/grafana-authnz-team /pkg/services/dashboards/accesscontrol.go @grafana/grafana-authnz-team /pkg/services/datasources/permissions/ @grafana/grafana-authnz-team /pkg/services/guardian/ @grafana/grafana-authnz-team /pkg/services/ldap/ @grafana/grafana-authnz-team /pkg/services/login/ @grafana/grafana-authnz-team -/pkg/services/oauthtoken/ @grafana/grafana-authnz-team -/pkg/services/teamguardian/ @grafana/grafana-authnz-team -/pkg/services/serviceaccounts/ @grafana/grafana-authnz-team /pkg/services/loginattempt/ @grafana/grafana-authnz-team -/pkg/services/authn/ @grafana/grafana-authnz-team +/pkg/services/oauthtoken/ @grafana/grafana-authnz-team +/pkg/services/serviceaccounts/ @grafana/grafana-authnz-team +/pkg/services/teamguardian/ @grafana/grafana-authnz-team # Support bundles /public/app/features/support-bundles/ @grafana/grafana-authnz-team diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index c42edbefe70..f9887d07c1c 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -24,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" + "github.com/grafana/grafana/pkg/services/anonymous/anontest" "github.com/grafana/grafana/pkg/services/auth/authtest" "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/authn/authntest" @@ -205,7 +206,10 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginservice.LoginServiceMock{}, &usertest.FakeUserService{}, sqlStore, service.NewLDAPFakeService()) loginService := &logintest.LoginServiceFake{} authenticator := &logintest.AuthenticatorFake{} - ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(), nil, featuremgmt.WithFeatures(), &authntest.FakeService{}) + ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, + remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, + authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(), + nil, featuremgmt.WithFeatures(), &authntest.FakeService{}, &anontest.FakeAnonymousSessionService{}) return ctxHdlr } diff --git a/pkg/infra/remotecache/database_storage.go b/pkg/infra/remotecache/database_storage.go index 59c5b96fee1..2d2ed1d34ca 100644 --- a/pkg/infra/remotecache/database_storage.go +++ b/pkg/infra/remotecache/database_storage.go @@ -148,6 +148,22 @@ func (dc *databaseCache) Delete(ctx context.Context, key string) error { }) } +func (dc *databaseCache) Count(ctx context.Context, prefix string) (int64, error) { + res := int64(0) + err := dc.SQLStore.WithDbSession(ctx, func(session *db.Session) error { + sql := "SELECT COUNT(*) FROM cache_data WHERE cache_key LIKE ?" + + _, err := session.SQL(sql, prefix+"%").Get(&res) + if err != nil { + return err + } + + return nil + }) + + return res, err +} + // CacheData is the struct representing the table in the database type CacheData struct { CacheKey string diff --git a/pkg/infra/remotecache/database_storage_test.go b/pkg/infra/remotecache/database_storage_test.go index 6a595c5ff7f..57769a05647 100644 --- a/pkg/infra/remotecache/database_storage_test.go +++ b/pkg/infra/remotecache/database_storage_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -77,3 +78,43 @@ func TestSecondSet(t *testing.T) { err = db.Set(context.Background(), "killa-gorilla", obj, 0) assert.Equal(t, err, nil) } + +func TestDatabaseStorageCount(t *testing.T) { + sqlstore := db.InitTestDB(t) + + db := &databaseCache{ + SQLStore: sqlstore, + codec: &gobCodec{}, + log: log.New("remotecache.database"), + } + + obj := &CacheableStruct{String: "foolbar"} + + // set time.now to 2 weeks ago + var err error + getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) } + err = db.Set(context.Background(), "pref-key1", obj, 1000*time.Second) + require.NoError(t, err) + + err = db.Set(context.Background(), "pref-key2", obj, 1000*time.Second) + require.NoError(t, err) + + err = db.Set(context.Background(), "pref-key3", obj, 1000*time.Second) + require.NoError(t, err) + + // insert object that should never expire + err = db.Set(context.Background(), "pref-key4", obj, 0) + require.NoError(t, err) + + getTime = time.Now + err = db.Set(context.Background(), "pref-key5", obj, 1000*time.Second) + require.NoError(t, err) + + // run GC + db.internalRunGC() + + // try to read values + n, errC := db.Count(context.Background(), "pref-") + require.NoError(t, errC) + assert.Equal(t, int64(2), n) +} diff --git a/pkg/infra/remotecache/memcached_storage.go b/pkg/infra/remotecache/memcached_storage.go index 339c7bf6324..8fc3d0aa387 100644 --- a/pkg/infra/remotecache/memcached_storage.go +++ b/pkg/infra/remotecache/memcached_storage.go @@ -12,6 +12,8 @@ import ( const memcachedCacheType = "memcached" +var ErrNotImplemented = errors.New("count not implemented") + type memcachedStorage struct { c *memcache.Client codec codec @@ -84,6 +86,10 @@ func (s *memcachedStorage) GetByteArray(ctx context.Context, key string) ([]byte return memcachedItem.Value, nil } +func (s *memcachedStorage) Count(ctx context.Context, prefix string) (int64, error) { + return 0, ErrNotImplemented +} + // Delete delete a key from the cache func (s *memcachedStorage) Delete(ctx context.Context, key string) error { return s.c.Delete(key) diff --git a/pkg/infra/remotecache/memcached_storage_integration_test.go b/pkg/infra/remotecache/memcached_storage_integration_test.go index a9f130593e5..87fffe182eb 100644 --- a/pkg/infra/remotecache/memcached_storage_integration_test.go +++ b/pkg/infra/remotecache/memcached_storage_integration_test.go @@ -13,4 +13,5 @@ func TestMemcachedCacheStorage(t *testing.T) { opts := &setting.RemoteCacheOptions{Name: memcachedCacheType, ConnStr: "localhost:11211"} client := createTestClient(t, opts, nil) runTestsForClient(t, client) + runCountTestsForClient(t, opts, nil) } diff --git a/pkg/infra/remotecache/redis_storage.go b/pkg/infra/remotecache/redis_storage.go index 2f424756855..6d386b592f8 100644 --- a/pkg/infra/remotecache/redis_storage.go +++ b/pkg/infra/remotecache/redis_storage.go @@ -134,3 +134,12 @@ func (s *redisStorage) Delete(ctx context.Context, key string) error { cmd := s.c.Del(ctx, key) return cmd.Err() } + +func (s *redisStorage) Count(ctx context.Context, prefix string) (int64, error) { + cmd := s.c.Keys(ctx, prefix+"*") + if cmd.Err() != nil { + return 0, cmd.Err() + } + + return int64(len(cmd.Val())), nil +} diff --git a/pkg/infra/remotecache/redis_storage_integration_test.go b/pkg/infra/remotecache/redis_storage_integration_test.go index d393186f89e..d360feb3ce8 100644 --- a/pkg/infra/remotecache/redis_storage_integration_test.go +++ b/pkg/infra/remotecache/redis_storage_integration_test.go @@ -14,4 +14,5 @@ func TestRedisCacheStorage(t *testing.T) { opts := &setting.RemoteCacheOptions{Name: redisCacheType, ConnStr: "addr=localhost:6379"} client := createTestClient(t, opts, nil) runTestsForClient(t, client) + runCountTestsForClient(t, opts, nil) } diff --git a/pkg/infra/remotecache/remotecache.go b/pkg/infra/remotecache/remotecache.go index 6e37d0426ee..a5193283641 100644 --- a/pkg/infra/remotecache/remotecache.go +++ b/pkg/infra/remotecache/remotecache.go @@ -69,6 +69,10 @@ type CacheStorage interface { // Delete object from cache Delete(ctx context.Context, key string) error + + // Count returns the number of items in the cache. + // Optionaly a prefix can be provided to only count items with that prefix + Count(ctx context.Context, prefix string) (int64, error) } // RemoteCache allows Grafana to cache data outside its own process @@ -108,6 +112,11 @@ func (ds *RemoteCache) Delete(ctx context.Context, key string) error { return ds.client.Delete(ctx, key) } +// Count returns the number of items in the cache. +func (ds *RemoteCache) Count(ctx context.Context, prefix string) (int64, error) { + return ds.client.Count(ctx, prefix) +} + // Run starts the backend processes for cache clients. func (ds *RemoteCache) Run(ctx context.Context) error { // create new interface if more clients need GC jobs @@ -214,3 +223,7 @@ func (pcs *prefixCacheStorage) SetByteArray(ctx context.Context, key string, val func (pcs *prefixCacheStorage) Delete(ctx context.Context, key string) error { return pcs.cache.Delete(ctx, pcs.prefix+key) } + +func (pcs *prefixCacheStorage) Count(ctx context.Context, prefix string) (int64, error) { + return pcs.cache.Count(ctx, pcs.prefix) +} diff --git a/pkg/infra/remotecache/remotecache_test.go b/pkg/infra/remotecache/remotecache_test.go index 7e0947c75d7..502f08946c4 100644 --- a/pkg/infra/remotecache/remotecache_test.go +++ b/pkg/infra/remotecache/remotecache_test.go @@ -44,6 +44,7 @@ func TestCachedBasedOnConfig(t *testing.T) { client := createTestClient(t, cfg.RemoteCacheOptions, db.InitTestDB(t)) runTestsForClient(t, client) + runCountTestsForClient(t, cfg.RemoteCacheOptions, db.InitTestDB(t)) } func TestInvalidCacheTypeReturnsError(t *testing.T) { @@ -56,6 +57,37 @@ func runTestsForClient(t *testing.T, client CacheStorage) { canNotFetchExpiredItems(t, client) } +func runCountTestsForClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore db.DB) { + client := createTestClient(t, opts, sqlstore) + expectError := false + if opts.Name == memcachedCacheType { + expectError = true + } + + t.Run("can count items", func(t *testing.T) { + cacheableStruct := CacheableStruct{String: "hej", Int64: 2000} + + err := client.Set(context.Background(), "pref-key1", cacheableStruct, 0) + require.NoError(t, err) + + err = client.Set(context.Background(), "pref-key2", cacheableStruct, 0) + require.NoError(t, err) + + err = client.Set(context.Background(), "key3-not-pref", cacheableStruct, 0) + require.NoError(t, err) + + n, errC := client.Count(context.Background(), "pref-") + if expectError { + require.ErrorIs(t, ErrNotImplemented, errC) + assert.Equal(t, int64(0), n) + return + } + + require.NoError(t, errC) + assert.Equal(t, int64(2), n) + }) +} + func canPutGetAndDeleteCachedObjects(t *testing.T, client CacheStorage) { cacheableStruct := CacheableStruct{String: "hej", Int64: 2000} diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index d893a92b26a..5e274440a47 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login" + "github.com/grafana/grafana/pkg/services/anonymous/anontest" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey/apikeytest" "github.com/grafana/grafana/pkg/services/auth" @@ -959,7 +960,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg, mockSQLStore *dbtest.Fake loginService, apiKeyService, authenticator, userService, orgService, oauthTokenService, featuremgmt.WithFeatures(featuremgmt.FlagAccessTokenExpirationCheck), - &authntest.FakeService{}) + &authntest.FakeService{}, &anontest.FakeAnonymousSessionService{}) } type fakeRenderService struct { diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 0bd56d37fb5..13d870ae48b 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -218,6 +218,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, + wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), loginservice.ProvideService, wire.Bind(new(login.Service), new(*loginservice.Implementation)), authinfoservice.ProvideAuthInfoService, diff --git a/pkg/server/wireexts_oss.go b/pkg/server/wireexts_oss.go index 98a89b73d54..c0b5b974cff 100644 --- a/pkg/server/wireexts_oss.go +++ b/pkg/server/wireexts_oss.go @@ -13,6 +13,8 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" + "github.com/grafana/grafana/pkg/services/anonymous" + "github.com/grafana/grafana/pkg/services/anonymous/anonimpl" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/authimpl" "github.com/grafana/grafana/pkg/services/datasources" @@ -43,6 +45,8 @@ var wireExtsBasicSet = wire.NewSet( authimpl.ProvideUserAuthTokenService, wire.Bind(new(auth.UserTokenService), new(*authimpl.UserAuthTokenService)), wire.Bind(new(auth.UserTokenBackgroundService), new(*authimpl.UserAuthTokenService)), + anonimpl.ProvideAnonymousSessionService, + wire.Bind(new(anonymous.Service), new(*anonimpl.AnonSessionService)), licensing.ProvideService, wire.Bind(new(licensing.Licensing), new(*licensing.OSSLicensingService)), setting.ProvideProvider, diff --git a/pkg/services/anonymous/anonimpl/impl.go b/pkg/services/anonymous/anonimpl/impl.go new file mode 100644 index 00000000000..0006b5d5db7 --- /dev/null +++ b/pkg/services/anonymous/anonimpl/impl.go @@ -0,0 +1,91 @@ +package anonimpl + +import ( + "context" + "encoding/hex" + "fmt" + "hash/fnv" + "net/http" + "strings" + "time" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/network" + "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/web" +) + +const thirtyDays = 30 * 24 * time.Hour +const anonCachePrefix = "anon-session" + +type AnonSession struct { + ip string + userAgent string +} + +func (a *AnonSession) Key() (string, error) { + key := strings.Builder{} + key.WriteString(a.ip) + key.WriteString(a.userAgent) + + hash := fnv.New128a() + if _, err := hash.Write([]byte(key.String())); err != nil { + return "", fmt.Errorf("failed to write to hash: %w", err) + } + + return strings.Join([]string{anonCachePrefix, hex.EncodeToString(hash.Sum(nil))}, ":"), nil +} + +type AnonSessionService struct { + remoteCache remotecache.CacheStorage + log log.Logger +} + +func ProvideAnonymousSessionService(remoteCache remotecache.CacheStorage, usageStats usagestats.Service) *AnonSessionService { + a := &AnonSessionService{ + remoteCache: remoteCache, + log: log.New("anonymous-session-service"), + } + + usageStats.RegisterMetricsFunc(a.UsageStatFn) + + return a +} + +func (a *AnonSessionService) UsageStatFn(ctx context.Context) (map[string]interface{}, error) { + sessionCount, err := a.remoteCache.Count(ctx, anonCachePrefix) + if err != nil { + return nil, nil + } + + return map[string]interface{}{ + "stats.anonymous.session.count": sessionCount, + }, nil +} + +func (a *AnonSessionService) TagSession(ctx context.Context, httpReq *http.Request) error { + addr := web.RemoteAddr(httpReq) + ip, err := network.GetIPFromAddress(addr) + if err != nil { + a.log.Debug("failed to parse ip from address", "addr", addr) + return nil + } + + clientIPStr := ip.String() + if len(ip) == 0 { + clientIPStr = "" + } + + anonSession := &AnonSession{ + ip: clientIPStr, + userAgent: httpReq.UserAgent(), + } + + key, err := anonSession.Key() + if err != nil { + return err + } + + return a.remoteCache.Set(ctx, key, key, thirtyDays) +} diff --git a/pkg/services/anonymous/anontest/fake.go b/pkg/services/anonymous/anontest/fake.go new file mode 100644 index 00000000000..d71050ab20f --- /dev/null +++ b/pkg/services/anonymous/anontest/fake.go @@ -0,0 +1,13 @@ +package anontest + +import ( + "context" + "net/http" +) + +type FakeAnonymousSessionService struct { +} + +func (f *FakeAnonymousSessionService) TagSession(ctx context.Context, httpReq *http.Request) error { + return nil +} diff --git a/pkg/services/anonymous/service.go b/pkg/services/anonymous/service.go new file mode 100644 index 00000000000..d84eea6a169 --- /dev/null +++ b/pkg/services/anonymous/service.go @@ -0,0 +1,10 @@ +package anonymous + +import ( + "context" + "net/http" +) + +type Service interface { + TagSession(context.Context, *http.Request) error +} diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 95eb83c3d79..18f8fb3044b 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/go-multierror" "go.opentelemetry.io/otel/attribute" - "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/network" "github.com/grafana/grafana/pkg/infra/remotecache" @@ -16,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/anonymous" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" @@ -54,7 +54,7 @@ func ProvideService( apikeyService apikey.Service, userService user.Service, jwtService auth.JWTVerifierService, usageStats usagestats.Service, - kvstore kvstore.KVStore, + anonSessionService anonymous.Service, userProtectionService login.UserProtectionService, loginAttempts loginattempt.Service, quotaService quota.Service, authInfoService login.AuthInfoService, renderService rendering.Service, @@ -83,7 +83,7 @@ func ProvideService( } if s.cfg.AnonymousEnabled { - s.RegisterClient(clients.ProvideAnonymous(cfg, orgService, kvstore)) + s.RegisterClient(clients.ProvideAnonymous(cfg, orgService, anonSessionService)) } var proxyClients []authn.ProxyClient diff --git a/pkg/services/authn/clients/anonymous.go b/pkg/services/authn/clients/anonymous.go index 41ec764567d..84f2c7a7f30 100644 --- a/pkg/services/authn/clients/anonymous.go +++ b/pkg/services/authn/clients/anonymous.go @@ -4,8 +4,8 @@ import ( "context" "strings" - "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/anonymous" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" @@ -13,18 +13,20 @@ import ( var _ authn.ContextAwareClient = new(Anonymous) -func ProvideAnonymous(cfg *setting.Cfg, orgService org.Service, _ kvstore.KVStore) *Anonymous { +func ProvideAnonymous(cfg *setting.Cfg, orgService org.Service, anonSessionService anonymous.Service) *Anonymous { return &Anonymous{ - cfg: cfg, - log: log.New("authn.anonymous"), - orgService: orgService, + cfg: cfg, + log: log.New("authn.anonymous"), + orgService: orgService, + anonSessionService: anonSessionService, } } type Anonymous struct { - cfg *setting.Cfg - log log.Logger - orgService org.Service + cfg *setting.Cfg + log log.Logger + orgService org.Service + anonSessionService anonymous.Service } func (a *Anonymous) Name() string { @@ -38,6 +40,17 @@ func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn. return nil, err } + go func() { + defer func() { + if err := recover(); err != nil { + a.log.Warn("tag anon session panic", "err", err) + } + }() + if err := a.anonSessionService.TagSession(ctx, r.HTTPRequest); err != nil { + a.log.Warn("Failed to tag anonymous session", "error", err) + } + }() + return &authn.Identity{ IsAnonymous: true, OrgID: o.ID, diff --git a/pkg/services/authn/clients/anonymous_test.go b/pkg/services/authn/clients/anonymous_test.go index 70867622d9b..20cf7ea6a30 100644 --- a/pkg/services/authn/clients/anonymous_test.go +++ b/pkg/services/authn/clients/anonymous_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/anonymous/anontest" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" @@ -45,9 +46,10 @@ func TestAnonymous_Authenticate(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { c := Anonymous{ - cfg: tt.cfg, - log: log.NewNopLogger(), - orgService: &orgtest.FakeOrgService{ExpectedOrg: tt.org, ExpectedError: tt.err}, + cfg: tt.cfg, + log: log.NewNopLogger(), + orgService: &orgtest.FakeOrgService{ExpectedOrg: tt.org, ExpectedError: tt.err}, + anonSessionService: &anontest.FakeAnonymousSessionService{}, } identity, err := c.Authenticate(context.Background(), &authn.Request{}) diff --git a/pkg/services/contexthandler/auth_proxy_test.go b/pkg/services/contexthandler/auth_proxy_test.go index 064cd08ca1f..af9f1a13602 100644 --- a/pkg/services/contexthandler/auth_proxy_test.go +++ b/pkg/services/contexthandler/auth_proxy_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/anonymous/anontest" "github.com/grafana/grafana/pkg/services/auth/authtest" "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/authn/authntest" @@ -116,7 +117,8 @@ func getContextHandler(t *testing.T) *ContextHandler { return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator, - &userService, orgService, nil, featuremgmt.WithFeatures(), &authntest.FakeService{}) + &userService, orgService, nil, featuremgmt.WithFeatures(), + &authntest.FakeService{}, &anontest.FakeAnonymousSessionService{}) } type fakeAuthenticator struct{} diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index e7f93205193..c3dac19a650 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" loginpkg "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/middleware/cookies" + "github.com/grafana/grafana/pkg/services/anonymous" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/jwt" @@ -53,48 +54,50 @@ func ProvideService(cfg *setting.Cfg, tokenService auth.UserTokenService, jwtSer tracer tracing.Tracer, authProxy *authproxy.AuthProxy, loginService login.Service, apiKeyService apikey.Service, authenticator loginpkg.Authenticator, userService user.Service, orgService org.Service, oauthTokenService oauthtoken.OAuthTokenService, features *featuremgmt.FeatureManager, - authnService authn.Service, + authnService authn.Service, anonSessionService anonymous.Service, ) *ContextHandler { return &ContextHandler{ - Cfg: cfg, - AuthTokenService: tokenService, - JWTAuthService: jwtService, - RemoteCache: remoteCache, - RenderService: renderService, - SQLStore: sqlStore, - tracer: tracer, - authProxy: authProxy, - authenticator: authenticator, - loginService: loginService, - apiKeyService: apiKeyService, - userService: userService, - orgService: orgService, - oauthTokenService: oauthTokenService, - features: features, - authnService: authnService, - singleflight: new(singleflight.Group), + Cfg: cfg, + AuthTokenService: tokenService, + JWTAuthService: jwtService, + RemoteCache: remoteCache, + RenderService: renderService, + SQLStore: sqlStore, + tracer: tracer, + authProxy: authProxy, + authenticator: authenticator, + loginService: loginService, + apiKeyService: apiKeyService, + userService: userService, + orgService: orgService, + oauthTokenService: oauthTokenService, + features: features, + authnService: authnService, + anonSessionService: anonSessionService, + singleflight: new(singleflight.Group), } } // ContextHandler is a middleware. type ContextHandler struct { - Cfg *setting.Cfg - AuthTokenService auth.UserTokenService - JWTAuthService auth.JWTVerifierService - RemoteCache *remotecache.RemoteCache - RenderService rendering.Service - SQLStore db.DB - tracer tracing.Tracer - authProxy *authproxy.AuthProxy - authenticator loginpkg.Authenticator - loginService login.Service - apiKeyService apikey.Service - userService user.Service - orgService org.Service - oauthTokenService oauthtoken.OAuthTokenService - features *featuremgmt.FeatureManager - authnService authn.Service - singleflight *singleflight.Group + Cfg *setting.Cfg + AuthTokenService auth.UserTokenService + JWTAuthService auth.JWTVerifierService + RemoteCache *remotecache.RemoteCache + RenderService rendering.Service + SQLStore db.DB + tracer tracing.Tracer + authProxy *authproxy.AuthProxy + authenticator loginpkg.Authenticator + loginService login.Service + apiKeyService apikey.Service + userService user.Service + orgService org.Service + oauthTokenService oauthtoken.OAuthTokenService + features *featuremgmt.FeatureManager + authnService authn.Service + singleflight *singleflight.Group + anonSessionService anonymous.Service // GetTime returns the current time. // Stubbable by tests. GetTime func() time.Time @@ -234,6 +237,17 @@ func (h *ContextHandler) initContextWithAnonymousUser(reqContext *contextmodel.R return false } + go func() { + defer func() { + if err := recover(); err != nil { + reqContext.Logger.Warn("tag anon session panic", "err", err) + } + }() + if err := h.anonSessionService.TagSession(reqContext.Req.Context(), reqContext.Req); err != nil { + reqContext.Logger.Warn("Failed to tag anonymous session", "error", err) + } + }() + reqContext.IsSignedIn = false reqContext.AllowAnonymous = true reqContext.SignedInUser = &user.SignedInUser{IsAnonymous: true}