AuthN: Add interface and function to operate on clients that supports redirects (#61905)

This commit is contained in:
Karl Persson 2023-01-23 11:54:38 +01:00 committed by GitHub
parent db51e963de
commit 50608db59a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 0 deletions

View File

@ -60,6 +60,8 @@ type Service interface {
Login(ctx context.Context, client string, r *Request) (*Identity, error)
// RegisterPostLoginHook registers a hook that that is called after a login request.
RegisterPostLoginHook(hook PostLoginHookFn)
// RedirectURL will generate url that we can use to initiate auth flow for supported clients.
RedirectURL(ctx context.Context, client string, r *Request) (string, error)
}
type Client interface {
@ -69,6 +71,11 @@ type Client interface {
Test(ctx context.Context, r *Request) bool
}
type RedirectClient interface {
Client
RedirectURL(ctx context.Context, r *Request) (string, error)
}
type PasswordClient interface {
AuthenticatePassword(ctx context.Context, r *Request, username, password string) (*Identity, error)
}

View File

@ -29,6 +29,10 @@ import (
"github.com/grafana/grafana/pkg/web"
)
const (
attributeKeyClient = "authn.client"
)
var (
errDisabledIdentity = errutil.NewBase(errutil.StatusUnauthorized, "identity.disabled")
)
@ -220,6 +224,24 @@ func (s *Service) RegisterPostLoginHook(hook authn.PostLoginHookFn) {
s.postLoginHooks = append(s.postLoginHooks, hook)
}
func (s *Service) RedirectURL(ctx context.Context, client string, r *authn.Request) (string, error) {
ctx, span := s.tracer.Start(ctx, "authn.RedirectURL")
defer span.End()
span.SetAttributes(attributeKeyClient, client, attribute.Key(attributeKeyClient).String(client))
c, ok := s.clients[client]
if !ok {
return "", authn.ErrClientNotConfigured.Errorf("client not configured: %s", client)
}
redirectClient, ok := c.(authn.RedirectClient)
if !ok {
return "", authn.ErrUnsupportedClient.Errorf("client does not support generating redirect url: %s", client)
}
return redirectClient.RedirectURL(ctx, r)
}
func orgIDFromRequest(r *authn.Request) int64 {
if r.HTTPRequest == nil {
return 0

View File

@ -210,6 +210,49 @@ func TestService_Login(t *testing.T) {
}
}
func TestService_RedirectURL(t *testing.T) {
type testCase struct {
desc string
client string
expectedURL string
expectedErr error
}
tests := []testCase{
{
desc: "should generate url for valid redirect client",
client: "redirect",
expectedURL: "https://localhost/redirect",
expectedErr: nil,
},
{
desc: "should return error on non existing client",
client: "non-existing",
expectedErr: authn.ErrClientNotConfigured,
},
{
desc: "should return error when client don't support the redirect interface",
client: "non-redirect",
expectedErr: authn.ErrUnsupportedClient,
},
}
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{}
})
u, err := service.RedirectURL(context.Background(), tt.client, nil)
assert.ErrorIs(t, err, tt.expectedErr)
assert.Equal(t, tt.expectedURL, u)
})
}
}
func mustParseURL(s string) *url.URL {
u, err := url.Parse(s)
if err != nil {

View File

@ -36,3 +36,24 @@ type FakePasswordClient struct {
func (f FakePasswordClient) AuthenticatePassword(ctx context.Context, r *authn.Request, username, password string) (*authn.Identity, error) {
return f.ExpectedIdentity, f.ExpectedErr
}
var _ authn.RedirectClient = new(FakeRedirectClient)
type FakeRedirectClient struct {
ExpectedErr error
ExpectedURL string
ExpectedOK bool
ExpectedIdentity *authn.Identity
}
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
}

View File

@ -3,6 +3,7 @@ package authn
import "github.com/grafana/grafana/pkg/util/errutil"
var (
ErrUnsupportedClient = errutil.NewBase(errutil.StatusBadRequest, "auth.client.unsupported")
ErrClientNotConfigured = errutil.NewBase(errutil.StatusBadRequest, "auth.client.notConfigured")
ErrUnsupportedIdentity = errutil.NewBase(errutil.StatusNotImplemented, "auth.identity.unsupported")
)