mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
parent
ef9a71f483
commit
2de72c1c39
@ -34,10 +34,12 @@ type ClientParams struct {
|
|||||||
type PostAuthHookFn func(ctx context.Context, identity *Identity, r *Request) error
|
type PostAuthHookFn func(ctx context.Context, identity *Identity, r *Request) error
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// RegisterPostAuthHook registers a hook that is called after a successful authentication.
|
|
||||||
RegisterPostAuthHook(hook PostAuthHookFn)
|
|
||||||
// Authenticate authenticates a request using the specified client.
|
// Authenticate authenticates a request using the specified client.
|
||||||
Authenticate(ctx context.Context, client string, r *Request) (*Identity, bool, error)
|
Authenticate(ctx context.Context, client string, r *Request) (*Identity, bool, error)
|
||||||
|
// Login authenticates a request and creates a session on successful authentication.
|
||||||
|
Login(ctx context.Context, client string, r *Request) (*Identity, error)
|
||||||
|
// RegisterPostAuthHook registers a hook that is called after a successful authentication.
|
||||||
|
RegisterPostAuthHook(hook PostAuthHookFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
|
@ -5,7 +5,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/network"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/apikey"
|
"github.com/grafana/grafana/pkg/services/apikey"
|
||||||
@ -20,7 +23,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
"github.com/grafana/grafana/pkg/services/rendering"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// make sure service implements authn.Service interface
|
// make sure service implements authn.Service interface
|
||||||
@ -39,6 +42,7 @@ func ProvideService(
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
clients: make(map[string]authn.Client),
|
clients: make(map[string]authn.Client),
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
|
sessionService: sessionService,
|
||||||
postAuthHooks: []authn.PostAuthHookFn{},
|
postAuthHooks: []authn.PostAuthHookFn{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +85,12 @@ type Service struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
clients map[string]authn.Client
|
clients map[string]authn.Client
|
||||||
|
|
||||||
|
tracer tracing.Tracer
|
||||||
|
sessionService auth.UserTokenService
|
||||||
|
|
||||||
// postAuthHooks are called after a successful authentication. They can modify the identity.
|
// postAuthHooks are called after a successful authentication. They can modify the identity.
|
||||||
postAuthHooks []authn.PostAuthHookFn
|
postAuthHooks []authn.PostAuthHookFn
|
||||||
tracer tracing.Tracer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Authenticate(ctx context.Context, client string, r *authn.Request) (*authn.Identity, bool, error) {
|
func (s *Service) Authenticate(ctx context.Context, client string, r *authn.Request) (*authn.Identity, bool, error) {
|
||||||
@ -117,6 +124,40 @@ func (s *Service) Authenticate(ctx context.Context, client string, r *authn.Requ
|
|||||||
return identity, true, nil
|
return identity, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (*authn.Identity, error) {
|
||||||
|
identity, ok, err := s.Authenticate(ctx, client, r)
|
||||||
|
if !ok {
|
||||||
|
return nil, authn.ErrClientNotConfigured.Errorf("client not configured: %s", client)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace, id := identity.NamespacedID()
|
||||||
|
|
||||||
|
// Login is only supported for users
|
||||||
|
if namespace != authn.NamespaceUser {
|
||||||
|
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := web.RemoteAddr(r.HTTPRequest)
|
||||||
|
ip, err := network.GetIPFromAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Debug("failed to parse ip from address", "addr", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionToken, err := s.sessionService.CreateToken(ctx, &user.User{ID: id}, ip, r.HTTPRequest.UserAgent())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: add login hooks to replace the one used in HookService
|
||||||
|
|
||||||
|
identity.SessionToken = sessionToken
|
||||||
|
return identity, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) RegisterPostAuthHook(hook authn.PostAuthHookFn) {
|
func (s *Service) RegisterPostAuthHook(hook authn.PostAuthHookFn) {
|
||||||
s.postAuthHooks = append(s.postAuthHooks, hook)
|
s.postAuthHooks = append(s.postAuthHooks, hook)
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,14 @@ package authnimpl
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
@ -124,6 +128,76 @@ func TestService_AuthenticateOrgID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestService_Login(t *testing.T) {
|
||||||
|
type TestCase struct {
|
||||||
|
desc string
|
||||||
|
client string
|
||||||
|
|
||||||
|
expectedClientOK bool
|
||||||
|
expectedClientErr error
|
||||||
|
expectedClientIdentity *authn.Identity
|
||||||
|
|
||||||
|
expectedSessionErr error
|
||||||
|
|
||||||
|
expectedErr error
|
||||||
|
expectedIdentity *authn.Identity
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []TestCase{
|
||||||
|
{
|
||||||
|
desc: "should authenticate and create session for valid request",
|
||||||
|
client: "fake",
|
||||||
|
expectedClientOK: true,
|
||||||
|
expectedClientIdentity: &authn.Identity{
|
||||||
|
ID: "user:1",
|
||||||
|
},
|
||||||
|
expectedIdentity: &authn.Identity{
|
||||||
|
ID: "user:1",
|
||||||
|
SessionToken: &auth.UserToken{UserId: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should not authenticate with invalid client",
|
||||||
|
client: "invalid",
|
||||||
|
expectedErr: authn.ErrClientNotConfigured,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should not authenticate non user identity",
|
||||||
|
client: "fake",
|
||||||
|
expectedClientOK: true,
|
||||||
|
expectedClientIdentity: &authn.Identity{ID: "apikey:1"},
|
||||||
|
expectedErr: authn.ErrUnsupportedIdentity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
s := setupTests(t, func(svc *Service) {
|
||||||
|
svc.clients["fake"] = &authntest.FakeClient{
|
||||||
|
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 {
|
||||||
|
return nil, tt.expectedSessionErr
|
||||||
|
}
|
||||||
|
return &auth.UserToken{UserId: user.ID}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
identity, err := s.Login(context.Background(), tt.client, &authn.Request{HTTPRequest: &http.Request{
|
||||||
|
Header: map[string][]string{},
|
||||||
|
URL: &url.URL{},
|
||||||
|
}})
|
||||||
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
assert.EqualValues(t, tt.expectedIdentity, identity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mustParseURL(s string) *url.URL {
|
func mustParseURL(s string) *url.URL {
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/loginattempt"
|
"github.com/grafana/grafana/pkg/services/loginattempt"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/grafana/grafana/pkg/util/errutil"
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -54,7 +55,7 @@ func (c *Basic) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
|
|||||||
}
|
}
|
||||||
if errors.Is(err, errInvalidPassword) {
|
if errors.Is(err, errInvalidPassword) {
|
||||||
// only add login attempt if identity was found but the provided password was invalid
|
// only add login attempt if identity was found but the provided password was invalid
|
||||||
_ = c.loginAttempts.Add(ctx, username, r.HTTPRequest.RemoteAddr)
|
_ = c.loginAttempts.Add(ctx, username, web.RemoteAddr(r.HTTPRequest))
|
||||||
}
|
}
|
||||||
return nil, errBasicAuthCredentials.Errorf("failed to authenticate identity: %w", err)
|
return nil, errBasicAuthCredentials.Errorf("failed to authenticate identity: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,7 @@ package authn
|
|||||||
|
|
||||||
import "github.com/grafana/grafana/pkg/util/errutil"
|
import "github.com/grafana/grafana/pkg/util/errutil"
|
||||||
|
|
||||||
var ErrClientNotFound = errutil.NewBase(errutil.StatusNotFound, "auth.client.notConfigured")
|
var (
|
||||||
|
ErrClientNotConfigured = errutil.NewBase(errutil.StatusBadRequest, "auth.client.notConfigured")
|
||||||
|
ErrUnsupportedIdentity = errutil.NewBase(errutil.StatusNotImplemented, "auth.identity.unsupported")
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user