diff --git a/pkg/api/api.go b/pkg/api/api.go index c38ed35c7b1..18fc62a9d84 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -670,7 +670,7 @@ func (hs *HTTPServer) registerRoutes() { adminRoute.Post("/ldap/sync/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersSync)), routing.Wrap(hs.PostSyncUserWithLDAP)) adminRoute.Get("/ldap/:username", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersRead)), routing.Wrap(hs.GetUserFromLDAP)) adminRoute.Get("/ldap/status", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), routing.Wrap(hs.GetLDAPStatus)) - }) + }, reqSignedIn) // Administering users r.Group("/api/admin/users", func(adminUserRoute routing.RouteRegister) { @@ -688,7 +688,7 @@ func (hs *HTTPServer) registerRoutes() { adminUserRoute.Post("/:id/logout", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser)) adminUserRoute.Get("/:id/auth-tokens", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens)) adminUserRoute.Post("/:id/revoke-auth-token", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), routing.Wrap(hs.AdminRevokeUserAuthToken)) - }) + }, reqSignedIn) // rendering r.Get("/render/*", reqSignedIn, hs.RenderToPng) diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index abb74face6c..aeef297dc16 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -2,6 +2,7 @@ package middleware import ( "errors" + "net/http" "net/url" "regexp" "strconv" @@ -37,7 +38,7 @@ func accessForbidden(c *models.ReqContext) { func notAuthorized(c *models.ReqContext) { if c.IsApiRequest() { - c.JsonApiErr(401, "Unauthorized", nil) + c.WriteErrOrFallback(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), c.LookupTokenErr) return } diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 4ebfd517ba3..6767542e2a0 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -52,8 +52,8 @@ type PostAuthHookFn func(ctx context.Context, identity *Identity, r *Request) er type PostLoginHookFn func(ctx context.Context, identity *Identity, r *Request, err error) type Service interface { - // Authenticate authenticates a request using the specified client. - Authenticate(ctx context.Context, client string, r *Request) (*Identity, bool, error) + // Authenticate authenticates a request + Authenticate(ctx context.Context, r *Request) (*Identity, error) // RegisterPostAuthHook registers a hook that is called after a successful authentication. RegisterPostAuthHook(hook PostAuthHookFn) // Login authenticates a request and creates a session on successful authentication. @@ -65,10 +65,18 @@ type Service interface { } type Client interface { + // Name returns the name of a client + Name() string // Authenticate performs the authentication for the request Authenticate(ctx context.Context, r *Request) (*Identity, error) +} + +type ContextAwareClient interface { + Client // Test should return true if client can be used to authenticate request Test(ctx context.Context, r *Request) bool + // Priority for the client, a lower number means higher priority + Priority() uint } type RedirectClient interface { @@ -132,6 +140,8 @@ type Identity struct { // Namespace* constants. For example, "user:1" or "api-key:1". // If the entity is not found in the DB or this entity is non-persistent, this field will be empty. ID string + // IsAnonymous + IsAnonymous bool // Login is the short hand identifier of the entity. Should be unique. Login string // Name is the display name of the entity. It is not guaranteed to be unique. @@ -171,11 +181,6 @@ func (i *Identity) Role() org.RoleType { return i.OrgRoles[i.OrgID] } -// IsAnonymous will return true if no ID is set on the identity -func (i *Identity) IsAnonymous() bool { - return i.ID == "" -} - // TODO: improve error handling func (i *Identity) NamespacedID() (string, int64) { var ( @@ -222,7 +227,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser { Email: i.Email, OrgCount: i.OrgCount, IsGrafanaAdmin: isGrafanaAdmin, - IsAnonymous: i.IsAnonymous(), + IsAnonymous: i.IsAnonymous, IsDisabled: i.IsDisabled, HelpFlags1: i.HelpFlags1, LastSeenAt: i.LastSeenAt, diff --git a/pkg/services/authn/authnimpl/priority_queue.go b/pkg/services/authn/authnimpl/priority_queue.go new file mode 100644 index 00000000000..d06ae02de90 --- /dev/null +++ b/pkg/services/authn/authnimpl/priority_queue.go @@ -0,0 +1,34 @@ +package authnimpl + +func newQueue[T any]() *queue[T] { + return &queue[T]{items: []queueItem[T]{}} +} + +type queue[T any] struct { + items []queueItem[T] +} + +type queueItem[T any] struct { + v T + p uint +} + +func (q *queue[T]) insert(v T, p uint) { + // no items in the queue so we just add it + if len(q.items) == 0 { + q.items = append(q.items, queueItem[T]{v, p}) + return + } + + // find the position in the queue the item should be placed based on priority + for i, item := range q.items { + if p < item.p { + q.items = append(q.items[:i+1], q.items[i:]...) + q.items[i] = queueItem[T]{v, p} + return + } + } + + // item did not have higher priority then what is in the queue currently, so we need to add it to the end + q.items = append(q.items, queueItem[T]{v, p}) +} diff --git a/pkg/services/authn/authnimpl/priority_queue_test.go b/pkg/services/authn/authnimpl/priority_queue_test.go new file mode 100644 index 00000000000..119e3a9e5b8 --- /dev/null +++ b/pkg/services/authn/authnimpl/priority_queue_test.go @@ -0,0 +1,48 @@ +package authnimpl + +import ( + "testing" + + "github.com/grafana/grafana/pkg/services/authn/authntest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/authn" +) + +func TestQueue(t *testing.T) { + type testCase struct { + desc string + clients []authn.ContextAwareClient + expectedOrder []string + } + + tests := []testCase{ + { + desc: "expect correct order", + clients: []authn.ContextAwareClient{ + &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 1}, + &authntest.FakeClient{ExpectedName: "5", ExpectedPriority: 5}, + &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3}, + &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 2}, + &authntest.FakeClient{ExpectedName: "4", ExpectedPriority: 4}, + }, + expectedOrder: []string{"1", "2", "3", "4", "5"}, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + q := newQueue[authn.ContextAwareClient]() + for _, c := range tt.clients { + q.insert(c, c.Priority()) + } + + require.Len(t, q.items, len(tt.expectedOrder)) + + for i := range q.items { + assert.Equal(t, q.items[i].v.Name(), tt.expectedOrder[i]) + } + }) + } +} diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 937624a1f16..3bddf2f45bb 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -5,6 +5,7 @@ import ( "net/http" "strconv" + "github.com/hashicorp/go-multierror" "go.opentelemetry.io/otel/attribute" "github.com/grafana/grafana/pkg/infra/log" @@ -34,7 +35,8 @@ const ( ) var ( - errDisabledIdentity = errutil.NewBase(errutil.StatusUnauthorized, "identity.disabled") + errCantAuthenticateReq = errutil.NewBase(errutil.StatusUnauthorized, "auth.unauthorized") + errDisabledIdentity = errutil.NewBase(errutil.StatusUnauthorized, "identity.disabled") ) // make sure service implements authn.Service interface @@ -55,20 +57,23 @@ func ProvideService( log: log.New("authn.service"), cfg: cfg, clients: make(map[string]authn.Client), + clientQueue: newQueue[authn.ContextAwareClient](), tracer: tracer, sessionService: sessionService, postAuthHooks: []authn.PostAuthHookFn{}, } - s.clients[authn.ClientRender] = clients.ProvideRender(userService, renderService) - s.clients[authn.ClientAPIKey] = clients.ProvideAPIKey(apikeyService, userService) + s.RegisterClient(clients.ProvideRender(userService, renderService)) + s.RegisterClient(clients.ProvideAPIKey(apikeyService, userService)) - sessionClient := clients.ProvideSession(sessionService, userService, cfg.LoginCookieName, cfg.LoginMaxLifetime) - s.clients[authn.ClientSession] = sessionClient - s.RegisterPostAuthHook(sessionClient.RefreshTokenHook) + if cfg.LoginCookieName != "" { + sessionClient := clients.ProvideSession(sessionService, userService, cfg.LoginCookieName, cfg.LoginMaxLifetime) + s.RegisterClient(sessionClient) + s.RegisterPostAuthHook(sessionClient.RefreshTokenHook) + } if s.cfg.AnonymousEnabled { - s.clients[authn.ClientAnonymous] = clients.ProvideAnonymous(cfg, orgService) + s.RegisterClient(clients.ProvideAnonymous(cfg, orgService)) } var proxyClients []authn.ProxyClient @@ -89,11 +94,11 @@ func ProvideService( if len(passwordClients) > 0 { passwordClient := clients.ProvidePassword(loginAttempts, passwordClients...) if s.cfg.BasicAuthEnabled { - s.clients[authn.ClientBasic] = clients.ProvideBasic(passwordClient) + s.RegisterClient(clients.ProvideBasic(passwordClient)) } // FIXME (kalleep): Remove the global variable and stick it into cfg if !setting.DisableLoginForm { - s.clients[authn.ClientForm] = clients.ProvideForm(passwordClient) + s.RegisterClient(clients.ProvideForm(passwordClient)) } } @@ -102,12 +107,12 @@ func ProvideService( if err != nil { s.log.Error("failed to configure auth proxy", "err", err) } else { - s.clients[authn.ClientProxy] = proxy + s.RegisterClient(proxy) } } if s.cfg.JWTAuthEnabled { - s.clients[authn.ClientJWT] = clients.ProvideJWT(jwtService, cfg) + s.RegisterClient(clients.ProvideJWT(jwtService, cfg)) } // FIXME (jguer): move to User package @@ -126,9 +131,11 @@ func ProvideService( } type Service struct { - log log.Logger - cfg *setting.Cfg - clients map[string]authn.Client + log log.Logger + cfg *setting.Cfg + + clients map[string]authn.Client + clientQueue *queue[authn.ContextAwareClient] tracer tracing.Tracer sessionService auth.UserTokenService @@ -139,42 +146,54 @@ type Service struct { postLoginHooks []authn.PostLoginHookFn } -func (s *Service) Authenticate(ctx context.Context, client string, r *authn.Request) (*authn.Identity, bool, error) { - c, ok := s.clients[client] - if !ok { - return nil, false, nil - } - - if !c.Test(ctx, r) { - return nil, false, nil - } - +func (s *Service) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { ctx, span := s.tracer.Start(ctx, "authn.Authenticate") defer span.End() - span.SetAttributes("authn.client", client, attribute.Key("authn.client").String(client)) + var authErr error + for _, item := range s.clientQueue.items { + if item.v.Test(ctx, r) { + identity, err := s.authenticate(ctx, item.v, r) + if err != nil { + s.log.Warn("failed to authenticate", "client", item.v.Name(), "err", err) + authErr = multierror.Append(authErr, err) + // try next + continue + } + + if identity != nil { + return identity, nil + } + } + } + + if authErr != nil { + return nil, authErr + } + + return nil, errCantAuthenticateReq.Errorf("cannot authenticate request") +} + +func (s *Service) authenticate(ctx context.Context, c authn.Client, r *authn.Request) (*authn.Identity, error) { r.OrgID = orgIDFromRequest(r) identity, err := c.Authenticate(ctx, r) if err != nil { - s.log.FromContext(ctx).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 + s.log.FromContext(ctx).Warn("auth client could not authenticate request", "client", c.Name(), "error", err) + return nil, err } - // FIXME (kalleep): Handle disabled identities - for _, hook := range s.postAuthHooks { if err := hook(ctx, identity, r); err != nil { s.log.FromContext(ctx).Warn("post auth hook failed", "error", err, "id", identity) - return nil, false, err + return nil, err } } if identity.IsDisabled { - return nil, true, errDisabledIdentity.Errorf("identity is disabled") + return nil, errDisabledIdentity.Errorf("identity is disabled") } - return identity, true, nil + return identity, nil } func (s *Service) RegisterPostAuthHook(hook authn.PostAuthHookFn) { @@ -182,18 +201,18 @@ func (s *Service) RegisterPostAuthHook(hook authn.PostAuthHookFn) { } func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (identity *authn.Identity, err error) { - var ok bool - identity, ok, err = s.Authenticate(ctx, client, r) - if !ok { - return nil, authn.ErrClientNotConfigured.Errorf("client not configured: %s", client) - } - defer func() { for _, hook := range s.postLoginHooks { hook(ctx, identity, r, err) } }() + c, ok := s.clients[client] + if !ok { + return nil, authn.ErrClientNotConfigured.Errorf("client not configured: %s", client) + } + + identity, err = s.authenticate(ctx, c, r) if err != nil { return nil, err } @@ -242,6 +261,13 @@ func (s *Service) RedirectURL(ctx context.Context, client string, r *authn.Reque return redirectClient.RedirectURL(ctx, 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()) + } +} + func orgIDFromRequest(r *authn.Request) int64 { if r.HTTPRequest == nil { return 0 diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index 20c95c40b78..21fb1f50d90 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -8,76 +8,106 @@ import ( "net/url" "testing" - "github.com/grafana/grafana/pkg/services/auth" - "github.com/grafana/grafana/pkg/services/auth/authtest" - "github.com/grafana/grafana/pkg/services/user" "github.com/stretchr/testify/assert" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/auth/authtest" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" + "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) func TestService_Authenticate(t *testing.T) { type TestCase struct { - desc string - clientName string - clientErr error - clientIdentity *authn.Identity - expectedOK bool - expectedErr error + desc string + clients []authn.Client + expectedIdentity *authn.Identity + expectedErrors []error } - var clientErr = errors.New("some err") + var ( + firstErr = errors.New("first") + lastErr = errors.New("last") + ) tests := []TestCase{ { - desc: "should succeed with authentication for configured client", - clientIdentity: &authn.Identity{}, - clientName: "fake", - expectedOK: true, + desc: "should succeed with authentication for configured client", + clients: []authn.Client{ + &authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "user:1"}}, + }, + expectedIdentity: &authn.Identity{ID: "user:1"}, }, { - desc: "should return false when client is not configured", - clientName: "gitlab", - expectedOK: false, + desc: "should succeed with authentication for second client when first test fail", + clients: []authn.Client{ + &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 1, ExpectedTest: false}, + &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 2, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "user:2"}}, + }, + expectedIdentity: &authn.Identity{ID: "user:2"}, }, { - desc: "should return true and error when client could be used but failed to authenticate", - clientName: "fake", - expectedOK: true, - clientErr: clientErr, - expectedErr: clientErr, + desc: "should succeed with authentication for third client when error happened in first", + clients: []authn.Client{ + &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false}, + &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")}, + &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "user:3"}}, + }, + expectedIdentity: &authn.Identity{ID: "user:3"}, }, { - desc: "should return error if identity is disabled", - clientName: "fake", - clientIdentity: &authn.Identity{IsDisabled: true}, - expectedOK: true, - expectedErr: errDisabledIdentity, + desc: "should return error when no client could authenticate the request", + clients: []authn.Client{ + &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false}, + &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: false}, + &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: false}, + }, + expectedErrors: []error{errCantAuthenticateReq}, + }, + { + desc: "should return all errors in chain", + clients: []authn.Client{ + &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false}, + &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: firstErr}, + &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedErr: lastErr}, + }, + expectedErrors: []error{firstErr, lastErr}, + }, + { + desc: "should return error on disabled identity", + clients: []authn.Client{ + &authntest.FakeClient{ExpectedName: "1", ExpectedTest: true, ExpectedIdentity: &authn.Identity{IsDisabled: true}}, + }, + expectedErrors: []error{errDisabledIdentity}, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { svc := setupTests(t, func(svc *Service) { - svc.clients["fake"] = &authntest.FakeClient{ - ExpectedIdentity: tt.clientIdentity, - ExpectedErr: tt.clientErr, - ExpectedTest: tt.expectedOK, + for _, c := range tt.clients { + svc.RegisterClient(c) } }) - _, ok, err := svc.Authenticate(context.Background(), tt.clientName, &authn.Request{}) - assert.Equal(t, tt.expectedOK, ok) - assert.ErrorIs(t, err, tt.expectedErr) + identity, err := svc.Authenticate(context.Background(), &authn.Request{}) + if len(tt.expectedErrors) == 0 { + assert.NoError(t, err) + assert.EqualValues(t, tt.expectedIdentity, identity) + } else { + for _, e := range tt.expectedErrors { + assert.ErrorIs(t, err, e) + } + assert.Nil(t, identity) + } }) } } -func TestService_AuthenticateOrgID(t *testing.T) { +func TestService_Authenticate_OrgID(t *testing.T) { type TestCase struct { desc string req *authn.Request @@ -123,18 +153,16 @@ func TestService_AuthenticateOrgID(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { var calledWith int64 s := setupTests(t, func(svc *Service) { - svc.clients["fake"] = authntest.MockClient{ + svc.RegisterClient(authntest.MockClient{ AuthenticateFunc: func(ctx context.Context, r *authn.Request) (*authn.Identity, error) { calledWith = r.OrgID return &authn.Identity{}, nil }, - TestFunc: func(ctx context.Context, r *authn.Request) bool { - return true - }, - } + TestFunc: func(ctx context.Context, r *authn.Request) bool { return true }, + }) }) - _, _, _ = s.Authenticate(context.Background(), "fake", tt.req) + _, _ = s.Authenticate(context.Background(), tt.req) assert.Equal(t, tt.expectedOrgID, calledWith) }) } @@ -157,7 +185,7 @@ func TestService_Login(t *testing.T) { tests := []TestCase{ { - desc: "should authenticate and create session for valid request", + desc: "should login for valid request", client: "fake", expectedClientOK: true, expectedClientIdentity: &authn.Identity{ @@ -169,12 +197,12 @@ func TestService_Login(t *testing.T) { }, }, { - desc: "should not authenticate with invalid client", + desc: "should not login with invalid client", client: "invalid", expectedErr: authn.ErrClientNotConfigured, }, { - desc: "should not authenticate non user identity", + desc: "should not login non user identity", client: "fake", expectedClientOK: true, expectedClientIdentity: &authn.Identity{ID: "apikey:1"}, @@ -185,11 +213,12 @@ func TestService_Login(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { s := setupTests(t, func(svc *Service) { - svc.clients["fake"] = &authntest.FakeClient{ + svc.RegisterClient(&authntest.FakeClient{ + ExpectedName: "fake", ExpectedErr: tt.expectedClientErr, ExpectedTest: tt.expectedClientOK, ExpectedIdentity: tt.expectedClientIdentity, - } + }) svc.sessionService = &authtest.FakeUserAuthTokenService{ CreateTokenProvider: func(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*auth.UserToken, error) { if tt.expectedSessionErr != nil { @@ -240,10 +269,8 @@ func TestService_RedirectURL(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { service := setupTests(t, func(svc *Service) { - svc.clients["redirect"] = authntest.FakeRedirectClient{ - ExpectedURL: tt.expectedURL, - } - svc.clients["non-redirect"] = &authntest.FakeClient{} + svc.RegisterClient(authntest.FakeRedirectClient{ExpectedName: "redirect", ExpectedURL: tt.expectedURL}) + svc.RegisterClient(&authntest.FakeClient{ExpectedName: "non-redirect"}) }) u, err := service.RedirectURL(context.Background(), tt.client, nil) @@ -265,10 +292,11 @@ 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{}, - tracer: tracing.InitializeTracerForTest(), + log: log.NewNopLogger(), + cfg: setting.NewCfg(), + clientQueue: newQueue[authn.ContextAwareClient](), + clients: map[string]authn.Client{}, + tracer: tracing.InitializeTracerForTest(), } for _, o := range opts { diff --git a/pkg/services/authn/authntest/fake.go b/pkg/services/authn/authntest/fake.go index 7f64b016294..a048bf6a091 100644 --- a/pkg/services/authn/authntest/fake.go +++ b/pkg/services/authn/authntest/fake.go @@ -10,14 +10,20 @@ type FakeService struct { authn.Service } -var _ authn.Client = new(FakeClient) +var _ authn.ContextAwareClient = new(FakeClient) type FakeClient struct { + ExpectedName string ExpectedErr error ExpectedTest bool + ExpectedPriority uint ExpectedIdentity *authn.Identity } +func (f *FakeClient) Name() string { + return f.ExpectedName +} + func (f *FakeClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { return f.ExpectedIdentity, f.ExpectedErr } @@ -26,6 +32,10 @@ func (f *FakeClient) Test(ctx context.Context, r *authn.Request) bool { return f.ExpectedTest } +func (f *FakeClient) Priority() uint { + return f.ExpectedPriority +} + var _ authn.PasswordClient = new(FakePasswordClient) type FakePasswordClient struct { @@ -42,18 +52,18 @@ var _ authn.RedirectClient = new(FakeRedirectClient) type FakeRedirectClient struct { ExpectedErr error ExpectedURL string - ExpectedOK bool + ExpectedName string ExpectedIdentity *authn.Identity } +func (f FakeRedirectClient) Name() string { + return f.ExpectedName +} + func (f FakeRedirectClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { return f.ExpectedIdentity, f.ExpectedErr } -func (f FakeRedirectClient) Test(ctx context.Context, r *authn.Request) bool { - return f.ExpectedOK -} - func (f FakeRedirectClient) RedirectURL(ctx context.Context, r *authn.Request) (string, error) { return f.ExpectedURL, f.ExpectedErr } diff --git a/pkg/services/authn/authntest/mock.go b/pkg/services/authn/authntest/mock.go index 7bb7f1c92df..98f5ae5c6c2 100644 --- a/pkg/services/authn/authntest/mock.go +++ b/pkg/services/authn/authntest/mock.go @@ -6,11 +6,20 @@ import ( "github.com/grafana/grafana/pkg/services/authn" ) -var _ authn.Client = new(MockClient) +var _ authn.ContextAwareClient = new(MockClient) type MockClient struct { + NameFunc func() string AuthenticateFunc func(ctx context.Context, r *authn.Request) (*authn.Identity, error) TestFunc func(ctx context.Context, r *authn.Request) bool + PriorityFunc func() uint +} + +func (m MockClient) Name() string { + if m.NameFunc != nil { + return m.NameFunc() + } + return "" } func (m MockClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { @@ -27,6 +36,13 @@ func (m MockClient) Test(ctx context.Context, r *authn.Request) bool { return false } +func (m MockClient) Priority() uint { + if m.PriorityFunc != nil { + return m.PriorityFunc() + } + return 0 +} + var _ authn.ProxyClient = new(MockProxyClient) type MockProxyClient struct { diff --git a/pkg/services/authn/clients/anonymous.go b/pkg/services/authn/clients/anonymous.go index e8d6b54c7ec..e37b0ebb394 100644 --- a/pkg/services/authn/clients/anonymous.go +++ b/pkg/services/authn/clients/anonymous.go @@ -9,7 +9,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -var _ authn.Client = new(Anonymous) +var _ authn.ContextAwareClient = new(Anonymous) func ProvideAnonymous(cfg *setting.Cfg, orgService org.Service) *Anonymous { return &Anonymous{ @@ -25,6 +25,10 @@ type Anonymous struct { orgService org.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 { @@ -33,6 +37,7 @@ func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn. } return &authn.Identity{ + IsAnonymous: true, OrgID: o.ID, OrgName: o.Name, OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)}, @@ -44,3 +49,7 @@ 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 +} diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index 958cdf7fb63..6efb52a24ea 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -23,7 +23,7 @@ var ( errAPIKeyRevoked = errutil.NewBase(errutil.StatusUnauthorized, "api-key.revoked", errutil.WithPublicMessage("Revoked API key")) ) -var _ authn.Client = new(APIKey) +var _ authn.ContextAwareClient = new(APIKey) func ProvideAPIKey(apiKeyService apikey.Service, userService user.Service) *APIKey { return &APIKey{ @@ -39,6 +39,10 @@ type APIKey struct { apiKeyService apikey.Service } +func (s *APIKey) Name() string { + return authn.ClientAPIKey +} + func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r)) if err != nil { @@ -133,6 +137,10 @@ func (s *APIKey) Test(ctx context.Context, r *authn.Request) bool { return looksLikeApiKey(getTokenFromRequest(r)) } +func (s *APIKey) Priority() uint { + return 30 +} + func looksLikeApiKey(token string) bool { return token != "" } diff --git a/pkg/services/authn/clients/basic.go b/pkg/services/authn/clients/basic.go index 4c8c0c529cf..5e37640179e 100644 --- a/pkg/services/authn/clients/basic.go +++ b/pkg/services/authn/clients/basic.go @@ -13,7 +13,7 @@ var ( errDecodingBasicAuthHeader = errutil.NewBase(errutil.StatusBadRequest, "basic-auth.invalid-header", errutil.WithPublicMessage("Invalid Basic Auth Header")) ) -var _ authn.Client = new(Basic) +var _ authn.ContextAwareClient = new(Basic) func ProvideBasic(client authn.PasswordClient) *Basic { return &Basic{client} @@ -23,6 +23,10 @@ type Basic struct { client authn.PasswordClient } +func (c *Basic) Name() string { + return authn.ClientBasic +} + func (c *Basic) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { username, password, err := util.DecodeBasicAuthHeader(getBasicAuthHeaderFromRequest(r)) if err != nil { @@ -36,6 +40,10 @@ func (c *Basic) Test(ctx context.Context, r *authn.Request) bool { return looksLikeBasicAuthRequest(r) } +func (c *Basic) Priority() uint { + return 40 +} + func looksLikeBasicAuthRequest(r *authn.Request) bool { return getBasicAuthHeaderFromRequest(r) != "" } diff --git a/pkg/services/authn/clients/form.go b/pkg/services/authn/clients/form.go index f8e4de8bdb0..2195892cca3 100644 --- a/pkg/services/authn/clients/form.go +++ b/pkg/services/authn/clients/form.go @@ -27,16 +27,14 @@ type loginForm struct { Password string `json:"password" binding:"Required"` } -func (f *Form) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { +func (c *Form) Name() string { + return authn.ClientForm +} + +func (c *Form) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { form := loginForm{} if err := web.Bind(r.HTTPRequest, &form); err != nil { return nil, errBadForm.Errorf("failed to parse request: %w", err) } - return f.client.AuthenticatePassword(ctx, r, form.Username, form.Password) -} - -func (f *Form) Test(ctx context.Context, r *authn.Request) bool { - // FIXME: How should we detect this?? - // Maybe create client test interface and not all clients has to implement this?? - return true + return c.client.AuthenticatePassword(ctx, r, form.Username, form.Password) } diff --git a/pkg/services/authn/clients/jwt.go b/pkg/services/authn/clients/jwt.go index 6c9b18bb272..728198dbd41 100644 --- a/pkg/services/authn/clients/jwt.go +++ b/pkg/services/authn/clients/jwt.go @@ -20,7 +20,7 @@ import ( "github.com/grafana/grafana/pkg/util/errutil" ) -var _ authn.Client = new(JWT) +var _ authn.ContextAwareClient = new(JWT) var ( ErrJWTInvalid = errutil.NewBase(errutil.StatusUnauthorized, @@ -45,6 +45,10 @@ type JWT struct { jwtService auth.JWTVerifierService } +func (s *JWT) Name() string { + return authn.ClientJWT +} + func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { jwtToken := s.retrieveToken(r.HTTPRequest) @@ -157,6 +161,10 @@ func (s *JWT) Test(ctx context.Context, r *authn.Request) bool { return true } +func (s *JWT) Priority() uint { + return 20 +} + const roleGrafanaAdmin = "GrafanaAdmin" func (s *JWT) extractRoleAndAdmin(claims map[string]interface{}) (org.RoleType, bool) { diff --git a/pkg/services/authn/clients/proxy.go b/pkg/services/authn/clients/proxy.go index 1f8a7d0ad69..c608df4d859 100644 --- a/pkg/services/authn/clients/proxy.go +++ b/pkg/services/authn/clients/proxy.go @@ -29,7 +29,7 @@ var ( errInvalidProxyHeader = errutil.NewBase(errutil.StatusInternal, "auth-proxy.invalid-proxy-header") ) -var _ authn.Client = new(Proxy) +var _ authn.ContextAwareClient = new(Proxy) func ProvideProxy(cfg *setting.Cfg, clients ...authn.ProxyClient) (*Proxy, error) { list, err := parseAcceptList(cfg.AuthProxyWhitelist) @@ -45,6 +45,10 @@ type Proxy struct { acceptedIPs []*net.IPNet } +func (c *Proxy) Name() string { + return authn.ClientProxy +} + func (c *Proxy) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { if !c.isAllowedIP(r) { return nil, errNotAcceptedIP.Errorf("request ip is not in the configured accept list") @@ -75,6 +79,10 @@ func (c *Proxy) Test(ctx context.Context, r *authn.Request) bool { return len(getProxyHeader(r, c.cfg.AuthProxyHeaderName, c.cfg.AuthProxyHeadersEncoded)) != 0 } +func (c *Proxy) Priority() uint { + return 50 +} + func (c *Proxy) isAllowedIP(r *authn.Request) bool { if len(c.acceptedIPs) == 0 { return true diff --git a/pkg/services/authn/clients/render.go b/pkg/services/authn/clients/render.go index 86dd8e21850..44df3ecc4ed 100644 --- a/pkg/services/authn/clients/render.go +++ b/pkg/services/authn/clients/render.go @@ -2,8 +2,10 @@ package clients import ( "context" + "time" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/user" @@ -18,7 +20,7 @@ const ( renderCookieName = "renderKey" ) -var _ authn.Client = new(Render) +var _ authn.ContextAwareClient = new(Render) func ProvideRender(userService user.Service, renderService rendering.Service) *Render { return &Render{userService, renderService} @@ -29,6 +31,10 @@ type Render struct { renderService rendering.Service } +func (c *Render) Name() string { + return authn.ClientRender +} + func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { key := getRenderKey(r) renderUsr, ok := c.renderService.GetRenderUser(ctx, key) @@ -36,20 +42,25 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide return nil, ErrInvalidRenderKey.Errorf("found no render user for key: %s", key) } + var identity *authn.Identity if renderUsr.UserID <= 0 { - return &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 0), - OrgID: renderUsr.OrgID, - OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, - ClientParams: authn.ClientParams{}, - }, nil + identity = &authn.Identity{ + ID: authn.NamespacedID(authn.NamespaceUser, 0), + OrgID: renderUsr.OrgID, + OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, + } + } else { + usr, err := c.userService.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{UserID: renderUsr.UserID, OrgID: renderUsr.OrgID}) + if err != nil { + return nil, err + } + + identity = authn.IdentityFromSignedInUser(authn.NamespacedID(authn.NamespaceUser, usr.UserID), usr, authn.ClientParams{}) } - usr, err := c.userService.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{UserID: renderUsr.UserID, OrgID: renderUsr.OrgID}) - if err != nil { - return nil, err - } - return authn.IdentityFromSignedInUser(authn.NamespacedID(authn.NamespaceUser, usr.UserID), usr, authn.ClientParams{}), nil + identity.LastSeenAt = time.Now() + identity.AuthModule = login.RenderModule + return identity, nil } func (c *Render) Test(ctx context.Context, r *authn.Request) bool { @@ -59,6 +70,10 @@ func (c *Render) Test(ctx context.Context, r *authn.Request) bool { return getRenderKey(r) != "" } +func (c *Render) Priority() uint { + return 10 +} + func getRenderKey(r *authn.Request) string { cookie, err := r.HTTPRequest.Cookie(renderCookieName) if err != nil { diff --git a/pkg/services/authn/clients/render_test.go b/pkg/services/authn/clients/render_test.go index 63b283dadf0..5a69914b05b 100644 --- a/pkg/services/authn/clients/render_test.go +++ b/pkg/services/authn/clients/render_test.go @@ -4,14 +4,17 @@ import ( "context" "net/http" "testing" + "time" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" - "github.com/stretchr/testify/assert" ) func TestRender_Authenticate(t *testing.T) { @@ -35,9 +38,10 @@ func TestRender_Authenticate(t *testing.T) { }, }, expectedIdentity: &authn.Identity{ - ID: "user:0", - OrgID: 1, - OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, + ID: "user:0", + OrgID: 1, + OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, + AuthModule: login.RenderModule, }, expectedRenderUsr: &rendering.RenderUser{ OrgID: 1, @@ -59,6 +63,7 @@ func TestRender_Authenticate(t *testing.T) { OrgName: "test", OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, IsGrafanaAdmin: boolPtr(false), + AuthModule: login.RenderModule, }, expectedRenderUsr: &rendering.RenderUser{ OrgID: 1, @@ -97,6 +102,8 @@ func TestRender_Authenticate(t *testing.T) { assert.Nil(t, identity) } else { assert.NoError(t, err) + // ignore LastSeenAt + identity.LastSeenAt = time.Time{} assert.EqualValues(t, *tt.expectedIdentity, *identity) } }) diff --git a/pkg/services/authn/clients/session.go b/pkg/services/authn/clients/session.go index 1b80078114c..8c26fb60332 100644 --- a/pkg/services/authn/clients/session.go +++ b/pkg/services/authn/clients/session.go @@ -15,7 +15,7 @@ import ( "github.com/grafana/grafana/pkg/web" ) -var _ authn.Client = new(Session) +var _ authn.ContextAwareClient = new(Session) func ProvideSession(sessionService auth.UserTokenService, userService user.Service, cookieName string, maxLifetime time.Duration) *Session { @@ -36,16 +36,8 @@ type Session struct { log log.Logger } -func (s *Session) Test(ctx context.Context, r *authn.Request) bool { - if s.loginCookieName == "" { - return false - } - - if _, err := r.HTTPRequest.Cookie(s.loginCookieName); err != nil { - return false - } - - return true +func (s *Session) Name() string { + return authn.ClientSession } func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { @@ -79,6 +71,22 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id return identity, nil } +func (s *Session) Test(ctx context.Context, r *authn.Request) bool { + if s.loginCookieName == "" { + return false + } + + if _, err := r.HTTPRequest.Cookie(s.loginCookieName); err != nil { + return false + } + + return true +} + +func (s *Session) Priority() uint { + return 60 +} + func (s *Session) RefreshTokenHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { if identity.SessionToken == nil { return nil diff --git a/pkg/services/contexthandler/auth_jwt.go b/pkg/services/contexthandler/auth_jwt.go index 4386471c12d..9944d240bae 100644 --- a/pkg/services/contexthandler/auth_jwt.go +++ b/pkg/services/contexthandler/auth_jwt.go @@ -12,8 +12,6 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models/roletype" authJWT "github.com/grafana/grafana/pkg/services/auth/jwt" - "github.com/grafana/grafana/pkg/services/authn" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" ) @@ -25,27 +23,6 @@ const ( ) func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64) bool { - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(ctx.Req.Context(), - authn.ClientJWT, - &authn.Request{HTTPRequest: ctx.Req, Resp: ctx.Resp, OrgID: orgId}) - if !ok { - return false - } - - newCtx := WithAuthHTTPHeader(ctx.Req.Context(), h.Cfg.JWTAuthHeaderName) - *ctx.Req = *ctx.Req.WithContext(newCtx) - - if err != nil { - ctx.WriteErr(err) - return true - } - - ctx.SignedInUser = identity.SignedInUser() - ctx.IsSignedIn = true - return true - } - if !h.Cfg.JWTAuthEnabled || h.Cfg.JWTAuthHeaderName == "" { return false } diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index d2b306b3c99..f2c49b26bf9 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -131,44 +131,62 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler { reqContext.Logger = reqContext.Logger.New("traceID", traceID) } - const headerName = "X-Grafana-Org-Id" - orgID := int64(0) - orgIDHeader := reqContext.Req.Header.Get(headerName) - if orgIDHeader != "" { - id, err := strconv.ParseInt(orgIDHeader, 10, 64) - if err == nil { - orgID = id + if h.features.IsEnabled(featuremgmt.FlagAuthnService) { + identity, err := h.authnService.Authenticate(ctx, &authn.Request{HTTPRequest: reqContext.Req, Resp: reqContext.Resp}) + if err != nil { + if errors.Is(err, auth.ErrUserTokenNotFound) || errors.Is(err, auth.ErrInvalidSessionToken) { + // Burn the cookie in case of invalid, expired or missing token + reqContext.Resp.Before(h.deleteInvalidCookieEndOfRequestFunc(reqContext)) + } + // Hack: set all errors on LookupTokenErr, so we can check it in auth middlewares + reqContext.LookupTokenErr = err } else { - reqContext.Logger.Debug("Received invalid header", "header", headerName, "value", orgIDHeader) + reqContext.IsSignedIn = true + reqContext.UserToken = identity.SessionToken + reqContext.SignedInUser = identity.SignedInUser() + reqContext.AllowAnonymous = identity.IsAnonymous + reqContext.IsRenderCall = identity.AuthModule == login.RenderModule + // FIXME (kallep): Add auth headers used to context } - } - - queryParameters, err := url.ParseQuery(reqContext.Req.URL.RawQuery) - if err != nil { - reqContext.Logger.Error("Failed to parse query parameters", "error", err) - } - if queryParameters.Has("targetOrgId") { - targetOrg, err := strconv.ParseInt(queryParameters.Get("targetOrgId"), 10, 64) - if err == nil { - orgID = targetOrg - } else { - reqContext.Logger.Error("Invalid target organization ID", "error", err) + } else { + const headerName = "X-Grafana-Org-Id" + orgID := int64(0) + orgIDHeader := reqContext.Req.Header.Get(headerName) + if orgIDHeader != "" { + id, err := strconv.ParseInt(orgIDHeader, 10, 64) + if err == nil { + orgID = id + } else { + reqContext.Logger.Debug("Received invalid header", "header", headerName, "value", orgIDHeader) + } } - } - // the order in which these are tested are important - // look for api key in Authorization header first - // then init session and look for userId in session - // then look for api key in session (special case for render calls via api) - // then test if anonymous access is enabled - switch { - case h.initContextWithRenderAuth(reqContext): - case h.initContextWithJWT(reqContext, orgID): - case h.initContextWithAPIKey(reqContext): - case h.initContextWithBasicAuth(reqContext, orgID): - case h.initContextWithAuthProxy(reqContext, orgID): - case h.initContextWithToken(reqContext, orgID): - case h.initContextWithAnonymousUser(reqContext): + queryParameters, err := url.ParseQuery(reqContext.Req.URL.RawQuery) + if err != nil { + reqContext.Logger.Error("Failed to parse query parameters", "error", err) + } + if queryParameters.Has("targetOrgId") { + targetOrg, err := strconv.ParseInt(queryParameters.Get("targetOrgId"), 10, 64) + if err == nil { + orgID = targetOrg + } else { + reqContext.Logger.Error("Invalid target organization ID", "error", err) + } + } + // the order in which these are tested are important + // look for api key in Authorization header first + // then init session and look for userId in session + // then look for api key in session (special case for render calls via api) + // then test if anonymous access is enabled + switch { + case h.initContextWithRenderAuth(reqContext): + case h.initContextWithJWT(reqContext, orgID): + case h.initContextWithAPIKey(reqContext): + case h.initContextWithBasicAuth(reqContext, orgID): + case h.initContextWithAuthProxy(reqContext, orgID): + case h.initContextWithToken(reqContext, orgID): + case h.initContextWithAnonymousUser(reqContext): + } } reqContext.Logger = reqContext.Logger.New("userId", reqContext.UserID, "orgId", reqContext.OrgID, "uname", reqContext.Login) @@ -201,20 +219,9 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler { } func (h *ContextHandler) initContextWithAnonymousUser(reqContext *models.ReqContext) bool { - ctx, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAnonymousUser") + _, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAnonymousUser") defer span.End() - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(ctx, authn.ClientAnonymous, &authn.Request{HTTPRequest: reqContext.Req}) - if !ok || err != nil { - return false - } - reqContext.SignedInUser = identity.SignedInUser() - reqContext.IsSignedIn = false - reqContext.AllowAnonymous = true - return true - } - if !h.Cfg.AnonymousEnabled { return false } @@ -276,26 +283,6 @@ func (h *ContextHandler) getAPIKey(ctx context.Context, keyString string) (*apik } func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bool { - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), authn.ClientAPIKey, &authn.Request{HTTPRequest: reqContext.Req}) - if !ok { - return false - } - - // include auth header in context - ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization") - *reqContext.Req = *reqContext.Req.WithContext(ctx) - - if err != nil { - reqContext.WriteErr(err) - return true - } - - reqContext.IsSignedIn = true - reqContext.SignedInUser = identity.SignedInUser() - return true - } - header := reqContext.Req.Header.Get("Authorization") parts := strings.SplitN(header, " ", 2) var keyString string @@ -405,26 +392,6 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo } func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext, orgID int64) bool { - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), authn.ClientBasic, &authn.Request{HTTPRequest: reqContext.Req}) - if !ok { - return false - } - - // include auth header in context - ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization") - *reqContext.Req = *reqContext.Req.WithContext(ctx) - - if err != nil { - reqContext.WriteErr(err) - return true - } - - reqContext.IsSignedIn = true - reqContext.SignedInUser = identity.SignedInUser() - return true - } - if !h.Cfg.BasicAuthEnabled { return false } @@ -485,29 +452,6 @@ func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext, } func (h *ContextHandler) initContextWithToken(reqContext *models.ReqContext, orgID int64) bool { - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), - authn.ClientSession, &authn.Request{HTTPRequest: reqContext.Req, Resp: reqContext.Resp}) - if !ok { - return false - } - - if err != nil { - if errors.Is(err, auth.ErrUserTokenNotFound) || errors.Is(err, auth.ErrInvalidSessionToken) { - // Burn the cookie in case of invalid, expired or missing token - reqContext.Resp.Before(h.deleteInvalidCookieEndOfRequestFunc(reqContext)) - } - - reqContext.LookupTokenErr = err - return false - } - - reqContext.IsSignedIn = true - reqContext.SignedInUser = identity.SignedInUser() - reqContext.UserToken = identity.SessionToken - return true - } - if h.Cfg.LoginCookieName == "" { return false } @@ -633,24 +577,6 @@ func (h *ContextHandler) rotateEndOfRequestFunc(reqContext *models.ReqContext) w } func (h *ContextHandler) initContextWithRenderAuth(reqContext *models.ReqContext) bool { - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), authn.ClientRender, &authn.Request{HTTPRequest: reqContext.Req}) - if !ok { - return false - } - - if err != nil { - reqContext.WriteErr(err) - return true - } - - reqContext.IsSignedIn = true - reqContext.IsRenderCall = true - reqContext.LastSeenAt = time.Now() - reqContext.SignedInUser = identity.SignedInUser() - return true - } - key := reqContext.GetCookie("renderKey") if key == "" { return false @@ -717,29 +643,6 @@ func (h *ContextHandler) handleError(ctx *models.ReqContext, err error, statusCo } func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext, orgID int64) bool { - if h.features.IsEnabled(featuremgmt.FlagAuthnService) { - identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), authn.ClientProxy, &authn.Request{HTTPRequest: reqContext.Req, Resp: reqContext.Resp}) - if !ok { - return false - } - - if err != nil { - reqContext.WriteErr(err) - return true - } - - ctx := WithAuthHTTPHeader(reqContext.Req.Context(), h.Cfg.AuthProxyHeaderName) - for _, header := range h.Cfg.AuthProxyHeaders { - if header != "" { - ctx = WithAuthHTTPHeader(ctx, header) - } - } - - *reqContext.Req = *reqContext.Req.WithContext(ctx) - reqContext.IsSignedIn = true - reqContext.SignedInUser = identity.SignedInUser() - return true - } username := reqContext.Req.Header.Get(h.Cfg.AuthProxyHeaderName) logger := log.New("auth.proxy") diff --git a/pkg/services/login/authinfo.go b/pkg/services/login/authinfo.go index c88aa92c7dd..01476e31ef2 100644 --- a/pkg/services/login/authinfo.go +++ b/pkg/services/login/authinfo.go @@ -22,6 +22,7 @@ const ( LDAPAuthModule = "ldap" AuthProxyAuthModule = "authproxy" JWTModule = "jwt" + RenderModule = "render" ) func GetAuthProviderLabel(authModule string) string {