Authn: Identity resolvers (#85930)
* AuthN: Add NamespaceID struct. We should replace the usage of encoded namespaceID with this one * AuthN: Add optional interface that clients can implement to be able to resolve identity for a namespace * Authn: Implement IdentityResolverClient for api keys * AuthN: use idenity resolvers Co-authored-by: Misi <mgyongyosi@users.noreply.github.com>
This commit is contained in:
@@ -49,15 +49,16 @@ func ProvideService(
|
||||
sessionService auth.UserTokenService, usageStats usagestats.Service, registerer prometheus.Registerer,
|
||||
) *Service {
|
||||
s := &Service{
|
||||
log: log.New("authn.service"),
|
||||
cfg: cfg,
|
||||
clients: make(map[string]authn.Client),
|
||||
clientQueue: newQueue[authn.ContextAwareClient](),
|
||||
tracer: tracer,
|
||||
metrics: newMetrics(registerer),
|
||||
sessionService: sessionService,
|
||||
postAuthHooks: newQueue[authn.PostAuthHookFn](),
|
||||
postLoginHooks: newQueue[authn.PostLoginHookFn](),
|
||||
log: log.New("authn.service"),
|
||||
cfg: cfg,
|
||||
clients: make(map[string]authn.Client),
|
||||
clientQueue: newQueue[authn.ContextAwareClient](),
|
||||
idenityResolverClients: make(map[string]authn.IdentityResolverClient),
|
||||
tracer: tracer,
|
||||
metrics: newMetrics(registerer),
|
||||
sessionService: sessionService,
|
||||
postAuthHooks: newQueue[authn.PostAuthHookFn](),
|
||||
postLoginHooks: newQueue[authn.PostLoginHookFn](),
|
||||
}
|
||||
|
||||
usageStats.RegisterMetricsFunc(s.getUsageStats)
|
||||
@@ -71,6 +72,8 @@ type Service struct {
|
||||
clients map[string]authn.Client
|
||||
clientQueue *queue[authn.ContextAwareClient]
|
||||
|
||||
idenityResolverClients map[string]authn.IdentityResolverClient
|
||||
|
||||
tracer tracing.Tracer
|
||||
metrics *metrics
|
||||
|
||||
@@ -292,19 +295,29 @@ func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID
|
||||
// hack to not update last seen
|
||||
r.SetMeta(authn.MetaKeyIsLogin, "true")
|
||||
|
||||
identity, err := s.authenticate(ctx, clients.ProvideIdentity(namespaceID), r)
|
||||
id, err := authn.ParseNamespaceID(namespaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
identity, err := s.resolveIdenity(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.authenticate(ctx, clients.ProvideIdentity(identity), r)
|
||||
}
|
||||
|
||||
func (s *Service) RegisterClient(c authn.Client) {
|
||||
s.clients[c.Name()] = c
|
||||
|
||||
if cac, ok := c.(authn.ContextAwareClient); ok {
|
||||
s.clientQueue.insert(cac, cac.Priority())
|
||||
}
|
||||
|
||||
if rc, ok := c.(authn.IdentityResolverClient); ok {
|
||||
s.idenityResolverClients[rc.Namespace()] = rc
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
|
||||
@@ -314,6 +327,35 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er
|
||||
return s.runPostAuthHooks(ctx, identity, r)
|
||||
}
|
||||
|
||||
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
|
||||
if namespaceID.IsNamespace(authn.NamespaceUser) {
|
||||
return &authn.Identity{
|
||||
OrgID: orgID,
|
||||
ID: namespaceID.String(),
|
||||
ClientParams: authn.ClientParams{
|
||||
AllowGlobalOrg: true,
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
if namespaceID.IsNamespace(authn.NamespaceServiceAccount) {
|
||||
return &authn.Identity{
|
||||
ID: namespaceID.String(),
|
||||
OrgID: orgID,
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
resolver, ok := s.idenityResolverClients[namespaceID.Namespace()]
|
||||
if !ok {
|
||||
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Namespace())
|
||||
}
|
||||
return resolver.ResolveIdentity(ctx, orgID, namespaceID)
|
||||
}
|
||||
|
||||
func (s *Service) errorLogFunc(ctx context.Context, err error) func(msg string, ctx ...any) {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return func(msg string, ctx ...any) {}
|
||||
|
||||
@@ -383,6 +383,49 @@ func TestService_Logout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ResolveIdentity(t *testing.T) {
|
||||
t.Run("should return error for for unknown namespace", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
_, err := svc.ResolveIdentity(context.Background(), 1, "some:1")
|
||||
assert.ErrorIs(t, err, authn.ErrInvalidNamepsaceID)
|
||||
})
|
||||
|
||||
t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
_, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1")
|
||||
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
|
||||
})
|
||||
|
||||
t.Run("should resolve for user", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
identity, err := svc.ResolveIdentity(context.Background(), 1, "user:1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, identity)
|
||||
})
|
||||
|
||||
t.Run("should resolve for service account", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
identity, err := svc.ResolveIdentity(context.Background(), 1, "service-account:1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, identity)
|
||||
})
|
||||
|
||||
t.Run("should resolve for valid namespace if client is registered", func(t *testing.T) {
|
||||
svc := setupTests(t, func(svc *Service) {
|
||||
svc.RegisterClient(&authntest.MockClient{
|
||||
NamespaceFunc: func() string { return authn.NamespaceAPIKey },
|
||||
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
|
||||
return &authn.Identity{}, nil
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
identity, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, identity)
|
||||
})
|
||||
}
|
||||
|
||||
func mustParseURL(s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
@@ -395,14 +438,15 @@ func setupTests(t *testing.T, opts ...func(svc *Service)) *Service {
|
||||
t.Helper()
|
||||
|
||||
s := &Service{
|
||||
log: log.NewNopLogger(),
|
||||
cfg: setting.NewCfg(),
|
||||
clients: map[string]authn.Client{},
|
||||
clientQueue: newQueue[authn.ContextAwareClient](),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
metrics: newMetrics(nil),
|
||||
postAuthHooks: newQueue[authn.PostAuthHookFn](),
|
||||
postLoginHooks: newQueue[authn.PostLoginHookFn](),
|
||||
log: log.NewNopLogger(),
|
||||
cfg: setting.NewCfg(),
|
||||
clients: make(map[string]authn.Client),
|
||||
clientQueue: newQueue[authn.ContextAwareClient](),
|
||||
idenityResolverClients: make(map[string]authn.IdentityResolverClient),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
metrics: newMetrics(nil),
|
||||
postAuthHooks: newQueue[authn.PostAuthHookFn](),
|
||||
postLoginHooks: newQueue[authn.PostLoginHookFn](),
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
|
||||
Reference in New Issue
Block a user