Anon: Scaffold anon service (#74744)
* remove API tagging method and authed tagging * add anonstore move debug to after cache change test order fix issue where mysql trims to second * add old device cleanup lint utc-ize everything trim whitespace * remove dangling setting * Add delete devices * Move anonymous authnclient to anonimpl * Add simple post login hook * move registration of Background Service cleanup * add updated_at index * do not untag device if login err * add delete device integration test
This commit is contained in:
@@ -16,7 +16,6 @@ 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"
|
||||
@@ -64,7 +63,6 @@ func ProvideService(
|
||||
apikeyService apikey.Service, userService user.Service,
|
||||
jwtService auth.JWTVerifierService,
|
||||
usageStats usagestats.Service,
|
||||
anonDeviceService anonymous.Service,
|
||||
userProtectionService login.UserProtectionService,
|
||||
loginAttempts loginattempt.Service, quotaService quota.Service,
|
||||
authInfoService login.AuthInfoService, renderService rendering.Service,
|
||||
@@ -91,11 +89,7 @@ func ProvideService(
|
||||
s.RegisterClient(clients.ProvideAPIKey(apikeyService, userService))
|
||||
|
||||
if cfg.LoginCookieName != "" {
|
||||
s.RegisterClient(clients.ProvideSession(cfg, sessionService, features, anonDeviceService))
|
||||
}
|
||||
|
||||
if s.cfg.AnonymousEnabled {
|
||||
s.RegisterClient(clients.ProvideAnonymous(cfg, orgService, anonDeviceService))
|
||||
s.RegisterClient(clients.ProvideSession(cfg, sessionService, features))
|
||||
}
|
||||
|
||||
var proxyClients []authn.ProxyClient
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ authn.ContextAwareClient = new(Anonymous)
|
||||
|
||||
const timeoutTag = 2 * time.Minute
|
||||
|
||||
func ProvideAnonymous(cfg *setting.Cfg, orgService org.Service, anonDeviceService anonymous.Service) *Anonymous {
|
||||
return &Anonymous{
|
||||
cfg: cfg,
|
||||
log: log.New("authn.anonymous"),
|
||||
orgService: orgService,
|
||||
anonDeviceService: anonDeviceService,
|
||||
}
|
||||
}
|
||||
|
||||
type Anonymous struct {
|
||||
cfg *setting.Cfg
|
||||
log log.Logger
|
||||
orgService org.Service
|
||||
anonDeviceService anonymous.Service
|
||||
}
|
||||
|
||||
func (a *Anonymous) Name() string {
|
||||
return authn.ClientAnonymous
|
||||
}
|
||||
|
||||
func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||
o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.AnonymousOrgName})
|
||||
if err != nil {
|
||||
a.log.FromContext(ctx).Error("Failed to find organization", "name", a.cfg.AnonymousOrgName, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReqCopy := &http.Request{}
|
||||
if r.HTTPRequest != nil && r.HTTPRequest.Header != nil {
|
||||
// avoid r.HTTPRequest.Clone(context.Background()) as we do not require a full clone
|
||||
httpReqCopy.Header = r.HTTPRequest.Header.Clone()
|
||||
httpReqCopy.RemoteAddr = r.HTTPRequest.RemoteAddr
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
a.log.Warn("Tag anon session panic", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
newCtx, cancel := context.WithTimeout(context.Background(), timeoutTag)
|
||||
defer cancel()
|
||||
if err := a.anonDeviceService.TagDevice(newCtx, httpReqCopy, anonymous.AnonDevice); err != nil {
|
||||
a.log.Warn("Failed to tag anonymous session", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return &authn.Identity{
|
||||
IsAnonymous: true,
|
||||
OrgID: o.ID,
|
||||
OrgName: o.Name,
|
||||
OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)},
|
||||
ClientParams: authn.ClientParams{SyncPermissions: true},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool {
|
||||
// If anonymous client is register it can always be used for authentication
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *Anonymous) Priority() uint {
|
||||
return 100
|
||||
}
|
||||
|
||||
func (a *Anonymous) UsageStatFn(ctx context.Context) (map[string]any, error) {
|
||||
m := map[string]any{}
|
||||
|
||||
// Add stats about anonymous auth
|
||||
m["stats.anonymous.customized_role.count"] = 0
|
||||
if !strings.EqualFold(a.cfg.AnonymousOrgRole, "Viewer") {
|
||||
m["stats.anonymous.customized_role.count"] = 1
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestAnonymous_Authenticate(t *testing.T) {
|
||||
type TestCase struct {
|
||||
desc string
|
||||
org *org.Org
|
||||
cfg *setting.Cfg
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []TestCase{
|
||||
{
|
||||
desc: "should success with valid org configured",
|
||||
org: &org.Org{ID: 1, Name: "some org"},
|
||||
cfg: &setting.Cfg{
|
||||
AnonymousOrgName: "some org",
|
||||
AnonymousOrgRole: "Viewer",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return error if any error occurs during org lookup",
|
||||
err: fmt.Errorf("some error"),
|
||||
cfg: &setting.Cfg{
|
||||
AnonymousOrgName: "some org",
|
||||
AnonymousOrgRole: "Viewer",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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},
|
||||
anonDeviceService: &anontest.FakeAnonymousSessionService{},
|
||||
}
|
||||
|
||||
identity, err := c.Authenticate(context.Background(), &authn.Request{})
|
||||
if err != nil {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, identity)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, true, identity.ID == "")
|
||||
assert.Equal(t, tt.org.ID, identity.OrgID)
|
||||
assert.Equal(t, tt.org.Name, identity.OrgName)
|
||||
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(identity.Role()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,11 @@ package clients
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/network"
|
||||
"github.com/grafana/grafana/pkg/services/anonymous"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -21,24 +19,20 @@ var _ authn.HookClient = new(Session)
|
||||
var _ authn.ContextAwareClient = new(Session)
|
||||
|
||||
func ProvideSession(cfg *setting.Cfg, sessionService auth.UserTokenService,
|
||||
features *featuremgmt.FeatureManager, anonDeviceService anonymous.Service) *Session {
|
||||
features *featuremgmt.FeatureManager) *Session {
|
||||
return &Session{
|
||||
cfg: cfg,
|
||||
features: features,
|
||||
sessionService: sessionService,
|
||||
log: log.New(authn.ClientSession),
|
||||
anonDeviceService: anonDeviceService,
|
||||
tagDevices: cfg.TagAuthedDevices,
|
||||
cfg: cfg,
|
||||
features: features,
|
||||
sessionService: sessionService,
|
||||
log: log.New(authn.ClientSession),
|
||||
}
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
cfg *setting.Cfg
|
||||
features *featuremgmt.FeatureManager
|
||||
sessionService auth.UserTokenService
|
||||
log log.Logger
|
||||
tagDevices bool
|
||||
anonDeviceService anonymous.Service
|
||||
cfg *setting.Cfg
|
||||
features *featuremgmt.FeatureManager
|
||||
sessionService auth.UserTokenService
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (s *Session) Name() string {
|
||||
@@ -67,29 +61,6 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
|
||||
}
|
||||
}
|
||||
|
||||
if s.tagDevices {
|
||||
// Tag authed devices
|
||||
httpReqCopy := &http.Request{}
|
||||
if r.HTTPRequest != nil && r.HTTPRequest.Header != nil {
|
||||
// avoid r.HTTPRequest.Clone(context.Background()) as we do not require a full clone
|
||||
httpReqCopy.Header = r.HTTPRequest.Header.Clone()
|
||||
httpReqCopy.RemoteAddr = r.HTTPRequest.RemoteAddr
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
s.log.Warn("Tag anon session panic", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
newCtx, cancel := context.WithTimeout(context.Background(), timeoutTag)
|
||||
defer cancel()
|
||||
if err := s.anonDeviceService.TagDevice(newCtx, httpReqCopy, anonymous.AuthedDevice); err != nil {
|
||||
s.log.Warn("Failed to tag anonymous session", "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceUser, token.UserId),
|
||||
SessionToken: token,
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/anonymous/anontest"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
@@ -30,7 +29,7 @@ func TestSession_Test(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LoginCookieName = ""
|
||||
cfg.LoginMaxLifetime = 20 * time.Second
|
||||
s := ProvideSession(cfg, &authtest.FakeUserAuthTokenService{}, featuremgmt.WithFeatures(), &anontest.FakeAnonymousSessionService{})
|
||||
s := ProvideSession(cfg, &authtest.FakeUserAuthTokenService{}, featuremgmt.WithFeatures())
|
||||
|
||||
disabled := s.Test(context.Background(), &authn.Request{HTTPRequest: validHTTPReq})
|
||||
assert.False(t, disabled)
|
||||
@@ -146,7 +145,7 @@ func TestSession_Authenticate(t *testing.T) {
|
||||
cfg.LoginCookieName = cookieName
|
||||
cfg.TokenRotationIntervalMinutes = 10
|
||||
cfg.LoginMaxLifetime = 20 * time.Second
|
||||
s := ProvideSession(cfg, tt.fields.sessionService, tt.fields.features, &anontest.FakeAnonymousSessionService{})
|
||||
s := ProvideSession(cfg, tt.fields.sessionService, tt.fields.features)
|
||||
|
||||
got, err := s.Authenticate(context.Background(), tt.args.r)
|
||||
require.True(t, (err != nil) == tt.wantErr, err)
|
||||
@@ -186,7 +185,7 @@ func TestSession_Hook(t *testing.T) {
|
||||
token.UnhashedToken = "new-token"
|
||||
return true, token, nil
|
||||
},
|
||||
}, featuremgmt.WithFeatures(), &anontest.FakeAnonymousSessionService{})
|
||||
}, featuremgmt.WithFeatures())
|
||||
|
||||
sampleID := &authn.Identity{
|
||||
SessionToken: &auth.UserToken{
|
||||
@@ -220,7 +219,7 @@ func TestSession_Hook(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should not rotate token with feature flag", func(t *testing.T) {
|
||||
s := ProvideSession(setting.NewCfg(), nil, featuremgmt.WithFeatures(featuremgmt.FlagClientTokenRotation), &anontest.FakeAnonymousSessionService{})
|
||||
s := ProvideSession(setting.NewCfg(), nil, featuremgmt.WithFeatures(featuremgmt.FlagClientTokenRotation))
|
||||
|
||||
req := &authn.Request{}
|
||||
identity := &authn.Identity{}
|
||||
|
||||
Reference in New Issue
Block a user