mirror of
https://github.com/grafana/grafana.git
synced 2025-01-06 14:13:11 -06:00
AuthN: Add interface and function to operate on clients that supports redirects (#61905)
This commit is contained in:
parent
db51e963de
commit
50608db59a
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user