diff --git a/pkg/services/anonymous/anonimpl/client.go b/pkg/services/anonymous/anonimpl/client.go index c93d4d40567..ac20182b00f 100644 --- a/pkg/services/anonymous/anonimpl/client.go +++ b/pkg/services/anonymous/anonimpl/client.go @@ -9,12 +9,20 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/anonymous" "github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util/errutil" +) + +var ( + errInvalidOrg = errutil.Unauthorized("anonymous.invalid-org") + errInvalidID = errutil.Unauthorized("anonymous.invalid-id") ) var _ authn.ContextAwareClient = new(Anonymous) +var _ authn.IdentityResolverClient = new(Anonymous) type Anonymous struct { cfg *setting.Cfg @@ -49,13 +57,7 @@ func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn. a.log.Warn("Failed to tag anonymous session", "error", err) } - return &authn.Identity{ - ID: authn.AnonymousNamespaceID, - OrgID: o.ID, - OrgName: o.Name, - OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)}, - ClientParams: authn.ClientParams{SyncPermissions: true}, - }, nil + return a.newAnonymousIdentity(o), nil } func (a *Anonymous) IsEnabled() bool { @@ -67,8 +69,26 @@ func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool { return true } -func (a *Anonymous) Priority() uint { - return 100 +func (a *Anonymous) Namespace() string { + return authn.NamespaceAnonymous.String() +} + +func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.NamespaceID) (*authn.Identity, error) { + o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.AnonymousOrgName}) + if err != nil { + return nil, err + } + + if o.ID != orgID { + return nil, errInvalidOrg.Errorf("anonymous user cannot authenticate in org %d", o.ID) + } + + // Anonymous identities should always have the same namespace id. + if namespaceID != authn.AnonymousNamespaceID { + return nil, errInvalidID + } + + return a.newAnonymousIdentity(o), nil } func (a *Anonymous) UsageStatFn(ctx context.Context) (map[string]any, error) { @@ -82,3 +102,17 @@ func (a *Anonymous) UsageStatFn(ctx context.Context) (map[string]any, error) { return m, nil } + +func (a *Anonymous) Priority() uint { + return 100 +} + +func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity { + return &authn.Identity{ + ID: authn.AnonymousNamespaceID, + OrgID: o.ID, + OrgName: o.Name, + OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)}, + ClientParams: authn.ClientParams{SyncPermissions: true}, + } +} diff --git a/pkg/services/anonymous/anonimpl/client_test.go b/pkg/services/anonymous/anonimpl/client_test.go index 7f3118d61a6..e33a36ca9fb 100644 --- a/pkg/services/anonymous/anonimpl/client_test.go +++ b/pkg/services/anonymous/anonimpl/client_test.go @@ -67,3 +67,67 @@ func TestAnonymous_Authenticate(t *testing.T) { }) } } + +func TestAnonymous_ResolveIdentity(t *testing.T) { + type TestCase struct { + desc string + cfg *setting.Cfg + orgID int64 + namespaceID authn.NamespaceID + org *org.Org + orgErr error + expectedErr error + } + + tests := []TestCase{ + { + desc: "should return error when org id is not the configured one", + org: &org.Org{ID: 2, Name: "some org"}, + cfg: &setting.Cfg{ + AnonymousOrgName: "some org", + }, + orgID: 1, + namespaceID: authn.AnonymousNamespaceID, + expectedErr: errInvalidOrg, + }, + { + desc: "should return error when namespace id does not match anonymous namespace id", + org: &org.Org{ID: 1, Name: "some org"}, + cfg: &setting.Cfg{ + AnonymousOrgName: "some org", + }, + orgID: 1, + namespaceID: authn.MustParseNamespaceID("anonymous:1"), + expectedErr: errInvalidID, + }, + { + desc: "should resolve identity", + org: &org.Org{ID: 1, Name: "some org"}, + cfg: &setting.Cfg{ + AnonymousOrgName: "some org", + }, + orgID: 1, + namespaceID: authn.AnonymousNamespaceID, + }, + } + + 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.orgErr}, + anonDeviceService: anontest.NewFakeService(), + } + + identity, err := c.ResolveIdentity(context.Background(), tt.orgID, tt.namespaceID) + if tt.expectedErr != nil { + assert.ErrorIs(t, err, tt.expectedErr) + assert.Nil(t, identity) + } else { + assert.NoError(t, err) + assert.EqualValues(t, c.newAnonymousIdentity(tt.org), identity) + } + }) + } +}