Authn: Add client for api keys (#60339)
* AuthN: Add functionallity to test if auth client should be used * AuthN: Add bolierplate client for api keys and register it * AuthN: Add tests for api key client * Inject service * AuthN: Update client names * ContextHandler: Set authn service * AuthN: Implement authentication for api key client * ContextHandler: Use authn service for api keys if flag is enabled * AuthN: refactor authentication method to return additional value to indicate if client could perform authentication * update prefixes * Add namespaced id to identity * AuthN: Expand the Identity struct to include required fields from signed in user * Add error for disabled service account * Add function to write error response based on errutil.Error * Add error to log * Return errors based on errutil.Error * pass error * update log message * Fix namespaced ids * Add tests * Lint
This commit is contained in:
@@ -5,23 +5,28 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/clients"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
var _ authn.Service = new(Service)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, orgService org.Service) *Service {
|
||||
func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, orgService org.Service, apikeyService apikey.Service, userService user.Service) *Service {
|
||||
s := &Service{
|
||||
log: log.New("authn.service"),
|
||||
cfg: cfg,
|
||||
clients: make(map[string]authn.Client),
|
||||
tracer: tracer,
|
||||
log: log.New("authn.service"),
|
||||
cfg: cfg,
|
||||
clients: make(map[string]authn.Client),
|
||||
tracer: tracer,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
s.clients[authn.ClientAPIKey] = clients.ProvideAPIKey(apikeyService, userService)
|
||||
|
||||
if s.cfg.AnonymousEnabled {
|
||||
s.clients[authn.ClientAnonymous] = clients.ProvideAnonymous(cfg, orgService)
|
||||
}
|
||||
@@ -34,20 +39,35 @@ type Service struct {
|
||||
cfg *setting.Cfg
|
||||
clients map[string]authn.Client
|
||||
|
||||
tracer tracing.Tracer
|
||||
tracer tracing.Tracer
|
||||
userService user.Service
|
||||
}
|
||||
|
||||
func (s *Service) Authenticate(ctx context.Context, clientName string, r *authn.Request) (*authn.Identity, error) {
|
||||
func (s *Service) Authenticate(ctx context.Context, client string, r *authn.Request) (*authn.Identity, bool, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authn.Authenticate")
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes("authn.client", clientName, attribute.Key("authn.client").String(clientName))
|
||||
span.SetAttributes("authn.client", client, attribute.Key("authn.client").String(client))
|
||||
logger := s.log.FromContext(ctx)
|
||||
|
||||
client, ok := s.clients[clientName]
|
||||
c, ok := s.clients[client]
|
||||
if !ok {
|
||||
s.log.FromContext(ctx).Warn("auth client not found", "client", clientName)
|
||||
logger.Debug("auth client not found", "client", client)
|
||||
span.AddEvents([]string{"message"}, []tracing.EventValue{{Str: "auth client is not configured"}})
|
||||
return nil, authn.ErrClientNotFound
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if !c.Test(ctx, r) {
|
||||
logger.Debug("auth client cannot handle request", "client", client)
|
||||
span.AddEvents([]string{"message"}, []tracing.EventValue{{Str: "auth client cannot handle request"}})
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
identity, err := c.Authenticate(ctx, r)
|
||||
if err != nil {
|
||||
logger.Warn("auth client could not authenticate request", "client", client, "error", err)
|
||||
span.AddEvents([]string{"message"}, []tracing.EventValue{{Str: "auth client could not authenticate request"}})
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
// FIXME: We want to perform common authentication operations here.
|
||||
@@ -58,5 +78,6 @@ func (s *Service) Authenticate(ctx context.Context, clientName string, r *authn.
|
||||
// login handler, but if we want to perform basic auth during a request (called from contexthandler) we don't
|
||||
// want a session to be created.
|
||||
|
||||
return client.Authenticate(ctx, r)
|
||||
logger.Debug("auth client successfully authenticated request", "client", client, "identity", identity)
|
||||
return identity, true, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package authnimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -17,6 +18,7 @@ func TestService_Authenticate(t *testing.T) {
|
||||
type TestCase struct {
|
||||
desc string
|
||||
clientName string
|
||||
expectedOK bool
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
@@ -24,22 +26,35 @@ func TestService_Authenticate(t *testing.T) {
|
||||
{
|
||||
desc: "should succeed with authentication for configured client",
|
||||
clientName: "fake",
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
desc: "should fail when client is not configured",
|
||||
clientName: "gitlab",
|
||||
expectedErr: authn.ErrClientNotFound,
|
||||
desc: "should return false when client is not configured",
|
||||
clientName: "gitlab",
|
||||
expectedOK: false,
|
||||
},
|
||||
{
|
||||
desc: "should return true and error when client could be used but failed to authenticate",
|
||||
clientName: "fake",
|
||||
expectedOK: true,
|
||||
expectedErr: errors.New("some error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
svc := setupTests(t, func(svc *Service) {
|
||||
svc.clients["fake"] = &authntest.FakeClient{}
|
||||
svc.clients["fake"] = &authntest.FakeClient{
|
||||
ExpectedErr: tt.expectedErr,
|
||||
ExpectedTest: tt.expectedOK,
|
||||
}
|
||||
})
|
||||
|
||||
_, err := svc.Authenticate(context.Background(), tt.clientName, &authn.Request{})
|
||||
assert.ErrorIs(t, tt.expectedErr, err)
|
||||
_, ok, err := svc.Authenticate(context.Background(), tt.clientName, &authn.Request{})
|
||||
assert.Equal(t, tt.expectedOK, ok)
|
||||
if tt.expectedErr != nil {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user