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:
linoman 2024-04-15 02:54:50 -06:00 committed by GitHub
parent 2b62167842
commit 51da96d94e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 140 additions and 5 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"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/licensing"
"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{
Cfg: cfg,
Features: features,
License: &licensing.OSSLicensingService{Cfg: cfg},
authnService: &authntest.FakeService{},
Cfg: cfg,
Features: features,
License: &licensing.OSSLicensingService{Cfg: cfg},
RenderService: &rendering.RenderingService{
Cfg: cfg,
RendererPluginManager: &fakeRendererPluginManager{},

View File

@ -12,7 +12,6 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/metrics"
"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/services/auth"
"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 {
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 {

View File

@ -491,6 +491,7 @@ func TestLoginOAuthRedirect(t *testing.T) {
oAuthInfos: oAuthInfos,
}
hs := &HTTPServer{
authnService: &authntest.FakeService{},
Cfg: cfg,
SettingsProvider: &setting.OSSImpl{Cfg: cfg},
License: &licensing.OSSLicensingService{},
@ -657,6 +658,7 @@ func TestLogoutSaml(t *testing.T) {
license.On("FeatureEnabled", "saml").Return(true)
hs := &HTTPServer{
authnService: &authntest.FakeService{},
Cfg: sc.cfg,
SettingsProvider: &setting.OSSImpl{Cfg: sc.cfg},
License: license,

View File

@ -58,6 +58,10 @@ func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn.
}, nil
}
func (a *Anonymous) IsEnabled() bool {
return a.cfg.AnonymousEnabled
}
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

View File

@ -94,6 +94,19 @@ type Service interface {
// RegisterClient will register a new authn.Client that can be used for authentication
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 {
@ -105,6 +118,8 @@ type Client interface {
Name() string
// Authenticate performs the authentication for the request
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.

View File

@ -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 {
r := &authn.Request{OrgID: identity.OrgID}
// hack to not update last seen on external syncs

View File

@ -40,6 +40,10 @@ func (f *FakeService) Authenticate(ctx context.Context, r *authn.Request) (*auth
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) 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
}
func (f FakeClient) IsEnabled() bool { return true }
func (f *FakeClient) Test(ctx context.Context, r *authn.Request) bool {
return f.ExpectedTest
}
@ -161,6 +167,8 @@ func (f FakeRedirectClient) Authenticate(ctx context.Context, r *authn.Request)
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) {
return f.ExpectedRedirect, f.ExpectedErr
}

View File

@ -20,6 +20,10 @@ func (m *MockService) Authenticate(ctx context.Context, r *authn.Request) (*auth
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) {
panic("unimplemented")
}
@ -87,6 +91,10 @@ func (m MockClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.
return nil, nil
}
func (m MockClient) IsEnabled() bool {
return true
}
func (m MockClient) Test(ctx context.Context, r *authn.Request) bool {
if m.TestFunc != nil {
return m.TestFunc(ctx, r)

View File

@ -70,6 +70,10 @@ func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
return newServiceAccountIdentity(key), nil
}
func (s *APIKey) IsEnabled() bool {
return true
}
func (s *APIKey) getAPIKey(ctx context.Context, token string) (*apikey.APIKey, error) {
fn := s.getFromToken
if !strings.HasPrefix(token, satokengen.GrafanaPrefix) {

View File

@ -38,6 +38,10 @@ func (c *Basic) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
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 {
if r.HTTPRequest == nil {
return false

View File

@ -91,6 +91,10 @@ func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*auth
return s.authenticateAsService(claims)
}
func (s *ExtendedJWT) IsEnabled() bool {
return s.cfg.ExtJWTAuth.Enabled
}
func (s *ExtendedJWT) authenticateAsUser(idTokenClaims,
accessTokenClaims *ExtendedJWTClaims) (*authn.Identity, error) {
// Only allow access policies to impersonate

View File

@ -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)
}
func (c *Form) IsEnabled() bool {
return true
}

View File

@ -20,6 +20,10 @@ func (i *IdentityClient) Name() string {
return "identity"
}
func (i *IdentityClient) IsEnabled() bool {
return true
}
func (i *IdentityClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
return i.identity, nil
}

View File

@ -139,6 +139,10 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
return id, nil
}
func (s *JWT) IsEnabled() bool {
return s.cfg.JWTAuth.Enabled
}
// remove sensitive query param
// avoid JWT URL login passing auth_token in URL
func (s *JWT) stripSensitiveParam(httpRequest *http.Request) {

View File

@ -199,6 +199,15 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
}, 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) {
var opts []oauth2.AuthCodeOption

View File

@ -507,6 +507,49 @@ func TestGenPKCECodeVerifier(t *testing.T) {
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 {
AuthCodeURLFunc func(state string, opts ...oauth2.AuthCodeOption) string
social.SocialConnector

View File

@ -107,6 +107,10 @@ func (c *Proxy) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
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.
// 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) {

View File

@ -59,6 +59,10 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
}, nil
}
func (c *Render) IsEnabled() bool {
return true
}
func (c *Render) Test(ctx context.Context, r *authn.Request) bool {
if r.HTTPRequest == nil {
return false

View File

@ -78,6 +78,10 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
return ident, nil
}
func (s *Session) IsEnabled() bool {
return true
}
func (s *Session) Test(ctx context.Context, r *authn.Request) bool {
if s.cfg.LoginCookieName == "" {
return false