mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Add IsClientEnabled and IsEnabled for the authn.Service and authn.Client interfaces (#86034)
* Add `Service. IsClientEnabled` and `Client.IsEnabled` functions * Implement `IsEnabled` function for authn clients * Implement `IsClientEnabled` function for authn services
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
|
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"github.com/grafana/grafana/pkg/services/licensing"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||||
@@ -58,9 +59,10 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
|||||||
}
|
}
|
||||||
|
|
||||||
hs := &HTTPServer{
|
hs := &HTTPServer{
|
||||||
Cfg: cfg,
|
authnService: &authntest.FakeService{},
|
||||||
Features: features,
|
Cfg: cfg,
|
||||||
License: &licensing.OSSLicensingService{Cfg: cfg},
|
Features: features,
|
||||||
|
License: &licensing.OSSLicensingService{Cfg: cfg},
|
||||||
RenderService: &rendering.RenderingService{
|
RenderService: &rendering.RenderingService{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
RendererPluginManager: &fakeRendererPluginManager{},
|
RendererPluginManager: &fakeRendererPluginManager{},
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/infra/network"
|
"github.com/grafana/grafana/pkg/infra/network"
|
||||||
"github.com/grafana/grafana/pkg/login/social"
|
|
||||||
"github.com/grafana/grafana/pkg/middleware/cookies"
|
"github.com/grafana/grafana/pkg/middleware/cookies"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
@@ -329,7 +328,7 @@ func (hs *HTTPServer) redirectURLWithErrorCookie(c *contextmodel.ReqContext, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) samlEnabled() bool {
|
func (hs *HTTPServer) samlEnabled() bool {
|
||||||
return hs.SettingsProvider.KeyValue("auth.saml", "enabled").MustBool(false) && hs.License.FeatureEnabled(social.SAMLProviderName)
|
return hs.authnService.IsClientEnabled(authn.ClientSAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) samlName() string {
|
func (hs *HTTPServer) samlName() string {
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ func TestLoginOAuthRedirect(t *testing.T) {
|
|||||||
oAuthInfos: oAuthInfos,
|
oAuthInfos: oAuthInfos,
|
||||||
}
|
}
|
||||||
hs := &HTTPServer{
|
hs := &HTTPServer{
|
||||||
|
authnService: &authntest.FakeService{},
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
SettingsProvider: &setting.OSSImpl{Cfg: cfg},
|
SettingsProvider: &setting.OSSImpl{Cfg: cfg},
|
||||||
License: &licensing.OSSLicensingService{},
|
License: &licensing.OSSLicensingService{},
|
||||||
@@ -657,6 +658,7 @@ func TestLogoutSaml(t *testing.T) {
|
|||||||
license.On("FeatureEnabled", "saml").Return(true)
|
license.On("FeatureEnabled", "saml").Return(true)
|
||||||
|
|
||||||
hs := &HTTPServer{
|
hs := &HTTPServer{
|
||||||
|
authnService: &authntest.FakeService{},
|
||||||
Cfg: sc.cfg,
|
Cfg: sc.cfg,
|
||||||
SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg},
|
SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg},
|
||||||
License: license,
|
License: license,
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn.
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Anonymous) IsEnabled() bool {
|
||||||
|
return a.cfg.AnonymousEnabled
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool {
|
func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
// If anonymous client is register it can always be used for authentication
|
// If anonymous client is register it can always be used for authentication
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -94,6 +94,19 @@ type Service interface {
|
|||||||
|
|
||||||
// RegisterClient will register a new authn.Client that can be used for authentication
|
// RegisterClient will register a new authn.Client that can be used for authentication
|
||||||
RegisterClient(c Client)
|
RegisterClient(c Client)
|
||||||
|
|
||||||
|
// IsClientEnabled returns true if the client is enabled.
|
||||||
|
//
|
||||||
|
// The client lookup follows the same formats used by the `authn` package
|
||||||
|
// constants.
|
||||||
|
//
|
||||||
|
// For OAuth clients, use the `authn.ClientWithPrefix(name)` to get the provider
|
||||||
|
// name. Append the prefix `auth.client.{providerName}`.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// - "saml" = "auth.client.saml"
|
||||||
|
// - "github" = "auth.client.github"
|
||||||
|
IsClientEnabled(client string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type IdentitySynchronizer interface {
|
type IdentitySynchronizer interface {
|
||||||
@@ -105,6 +118,8 @@ type Client interface {
|
|||||||
Name() string
|
Name() string
|
||||||
// Authenticate performs the authentication for the request
|
// Authenticate performs the authentication for the request
|
||||||
Authenticate(ctx context.Context, r *Request) (*Identity, error)
|
Authenticate(ctx context.Context, r *Request) (*Identity, error)
|
||||||
|
// IsEnabled returns the enabled status of the client
|
||||||
|
IsEnabled() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextAwareClient is an optional interface that auth client can implement.
|
// ContextAwareClient is an optional interface that auth client can implement.
|
||||||
|
|||||||
@@ -320,6 +320,15 @@ func (s *Service) RegisterClient(c authn.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) IsClientEnabled(name string) bool {
|
||||||
|
client, ok := s.clients[name]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.IsEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
|
func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
|
||||||
r := &authn.Request{OrgID: identity.OrgID}
|
r := &authn.Request{OrgID: identity.OrgID}
|
||||||
// hack to not update last seen on external syncs
|
// hack to not update last seen on external syncs
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ func (f *FakeService) Authenticate(ctx context.Context, r *authn.Request) (*auth
|
|||||||
return f.ExpectedIdentity, f.ExpectedErr
|
return f.ExpectedIdentity, f.ExpectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeService) IsClientEnabled(name string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FakeService) RegisterPostAuthHook(hook authn.PostAuthHookFn, priority uint) {}
|
func (f *FakeService) RegisterPostAuthHook(hook authn.PostAuthHookFn, priority uint) {}
|
||||||
|
|
||||||
func (f *FakeService) Login(ctx context.Context, client string, r *authn.Request) (*authn.Identity, error) {
|
func (f *FakeService) Login(ctx context.Context, client string, r *authn.Request) (*authn.Identity, error) {
|
||||||
@@ -119,6 +123,8 @@ func (f *FakeClient) Authenticate(ctx context.Context, r *authn.Request) (*authn
|
|||||||
return f.ExpectedIdentity, f.ExpectedErr
|
return f.ExpectedIdentity, f.ExpectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f FakeClient) IsEnabled() bool { return true }
|
||||||
|
|
||||||
func (f *FakeClient) Test(ctx context.Context, r *authn.Request) bool {
|
func (f *FakeClient) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
return f.ExpectedTest
|
return f.ExpectedTest
|
||||||
}
|
}
|
||||||
@@ -161,6 +167,8 @@ func (f FakeRedirectClient) Authenticate(ctx context.Context, r *authn.Request)
|
|||||||
return f.ExpectedIdentity, f.ExpectedErr
|
return f.ExpectedIdentity, f.ExpectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f FakeRedirectClient) IsEnabled() bool { return true }
|
||||||
|
|
||||||
func (f FakeRedirectClient) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redirect, error) {
|
func (f FakeRedirectClient) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redirect, error) {
|
||||||
return f.ExpectedRedirect, f.ExpectedErr
|
return f.ExpectedRedirect, f.ExpectedErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ func (m *MockService) Authenticate(ctx context.Context, r *authn.Request) (*auth
|
|||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockService) IsClientEnabled(name string) bool {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockService) Login(ctx context.Context, client string, r *authn.Request) (*authn.Identity, error) {
|
func (m *MockService) Login(ctx context.Context, client string, r *authn.Request) (*authn.Identity, error) {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
}
|
}
|
||||||
@@ -87,6 +91,10 @@ func (m MockClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m MockClient) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (m MockClient) Test(ctx context.Context, r *authn.Request) bool {
|
func (m MockClient) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
if m.TestFunc != nil {
|
if m.TestFunc != nil {
|
||||||
return m.TestFunc(ctx, r)
|
return m.TestFunc(ctx, r)
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
|
|||||||
return newServiceAccountIdentity(key), nil
|
return newServiceAccountIdentity(key), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *APIKey) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *APIKey) getAPIKey(ctx context.Context, token string) (*apikey.APIKey, error) {
|
func (s *APIKey) getAPIKey(ctx context.Context, token string) (*apikey.APIKey, error) {
|
||||||
fn := s.getFromToken
|
fn := s.getFromToken
|
||||||
if !strings.HasPrefix(token, satokengen.GrafanaPrefix) {
|
if !strings.HasPrefix(token, satokengen.GrafanaPrefix) {
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ func (c *Basic) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
|
|||||||
return c.client.AuthenticatePassword(ctx, r, username, password)
|
return c.client.AuthenticatePassword(ctx, r, username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Basic) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Basic) Test(ctx context.Context, r *authn.Request) bool {
|
func (c *Basic) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
if r.HTTPRequest == nil {
|
if r.HTTPRequest == nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*auth
|
|||||||
return s.authenticateAsService(claims)
|
return s.authenticateAsService(claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ExtendedJWT) IsEnabled() bool {
|
||||||
|
return s.cfg.ExtJWTAuth.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ExtendedJWT) authenticateAsUser(idTokenClaims,
|
func (s *ExtendedJWT) authenticateAsUser(idTokenClaims,
|
||||||
accessTokenClaims *ExtendedJWTClaims) (*authn.Identity, error) {
|
accessTokenClaims *ExtendedJWTClaims) (*authn.Identity, error) {
|
||||||
// Only allow access policies to impersonate
|
// Only allow access policies to impersonate
|
||||||
|
|||||||
@@ -38,3 +38,7 @@ func (c *Form) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ident
|
|||||||
}
|
}
|
||||||
return c.client.AuthenticatePassword(ctx, r, form.Username, form.Password)
|
return c.client.AuthenticatePassword(ctx, r, form.Username, form.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Form) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ func (i *IdentityClient) Name() string {
|
|||||||
return "identity"
|
return "identity"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *IdentityClient) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (i *IdentityClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
func (i *IdentityClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||||
return i.identity, nil
|
return i.identity, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,10 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *JWT) IsEnabled() bool {
|
||||||
|
return s.cfg.JWTAuth.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
// remove sensitive query param
|
// remove sensitive query param
|
||||||
// avoid JWT URL login passing auth_token in URL
|
// avoid JWT URL login passing auth_token in URL
|
||||||
func (s *JWT) stripSensitiveParam(httpRequest *http.Request) {
|
func (s *JWT) stripSensitiveParam(httpRequest *http.Request) {
|
||||||
|
|||||||
@@ -199,6 +199,15 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OAuth) IsEnabled() bool {
|
||||||
|
provider := c.socialService.GetOAuthInfoProvider(c.providerName)
|
||||||
|
if provider == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redirect, error) {
|
func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redirect, error) {
|
||||||
var opts []oauth2.AuthCodeOption
|
var opts []oauth2.AuthCodeOption
|
||||||
|
|
||||||
|
|||||||
@@ -507,6 +507,49 @@ func TestGenPKCECodeVerifier(t *testing.T) {
|
|||||||
assert.Len(t, verifier, 128)
|
assert.Len(t, verifier, 128)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsEnabled(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
desc string
|
||||||
|
oauthCfg *social.OAuthInfo
|
||||||
|
expected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
desc: "should return false when client is not enabled",
|
||||||
|
oauthCfg: &social.OAuthInfo{Enabled: false},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return false when client doesnt exists",
|
||||||
|
oauthCfg: nil,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return true when client is enabled",
|
||||||
|
oauthCfg: &social.OAuthInfo{Enabled: true},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
fakeSocialSvc := &socialtest.FakeSocialService{
|
||||||
|
ExpectedAuthInfoProvider: tt.oauthCfg,
|
||||||
|
}
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
c := ProvideOAuth(
|
||||||
|
social.GitHubProviderName,
|
||||||
|
cfg,
|
||||||
|
nil,
|
||||||
|
fakeSocialSvc,
|
||||||
|
&setting.OSSImpl{Cfg: cfg},
|
||||||
|
featuremgmt.WithFeatures())
|
||||||
|
assert.Equal(t, tt.expected, c.IsEnabled())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type mockConnector struct {
|
type mockConnector struct {
|
||||||
AuthCodeURLFunc func(state string, opts ...oauth2.AuthCodeOption) string
|
AuthCodeURLFunc func(state string, opts ...oauth2.AuthCodeOption) string
|
||||||
social.SocialConnector
|
social.SocialConnector
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ func (c *Proxy) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
|
|||||||
return nil, clientErr
|
return nil, clientErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) IsEnabled() bool {
|
||||||
|
return c.cfg.AuthProxy.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
// See if we have cached the user id, in that case we can fetch the signed-in user and skip sync.
|
// See if we have cached the user id, in that case we can fetch the signed-in user and skip sync.
|
||||||
// Error here means that we could not find anything in cache, so we can proceed as usual
|
// Error here means that we could not find anything in cache, so we can proceed as usual
|
||||||
func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *authn.Request) (*authn.Identity, error) {
|
func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *authn.Request) (*authn.Identity, error) {
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Render) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Render) Test(ctx context.Context, r *authn.Request) bool {
|
func (c *Render) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
if r.HTTPRequest == nil {
|
if r.HTTPRequest == nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
|
|||||||
return ident, nil
|
return ident, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Session) IsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Session) Test(ctx context.Context, r *authn.Request) bool {
|
func (s *Session) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
if s.cfg.LoginCookieName == "" {
|
if s.cfg.LoginCookieName == "" {
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user