2022-11-29 10:57:47 +01:00
|
|
|
package authnimpl
|
|
|
|
|
|
2022-12-02 15:10:03 +01:00
|
|
|
import (
|
|
|
|
|
"context"
|
2022-12-20 21:18:48 +01:00
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
2022-12-02 15:10:03 +01:00
|
|
|
|
2023-01-10 14:55:27 +01:00
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
|
|
|
|
2022-12-02 15:10:03 +01:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2023-01-10 14:55:27 +01:00
|
|
|
"github.com/grafana/grafana/pkg/infra/network"
|
2022-12-02 15:10:03 +01:00
|
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
2023-01-03 10:23:38 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
2022-12-19 09:22:11 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/apikey"
|
2023-01-04 15:10:43 +00:00
|
|
|
"github.com/grafana/grafana/pkg/services/auth"
|
2022-12-02 15:10:03 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/authn"
|
2022-12-20 13:59:05 +00:00
|
|
|
sync "github.com/grafana/grafana/pkg/services/authn/authnimpl/usersync"
|
2022-12-02 15:10:03 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/authn/clients"
|
2023-01-03 10:23:38 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/login"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/loginattempt"
|
2022-12-02 15:10:03 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2023-01-03 10:23:38 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/quota"
|
2023-01-04 13:48:00 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/rendering"
|
2022-12-19 09:22:11 +01:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2022-12-02 15:10:03 +01:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2023-01-10 14:55:27 +01:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2022-12-02 15:10:03 +01:00
|
|
|
)
|
2022-11-29 10:57:47 +01:00
|
|
|
|
2022-12-20 21:18:48 +01:00
|
|
|
// make sure service implements authn.Service interface
|
2022-11-29 10:57:47 +01:00
|
|
|
var _ authn.Service = new(Service)
|
|
|
|
|
|
2023-01-03 10:23:38 +01:00
|
|
|
func ProvideService(
|
2023-01-04 15:10:43 +00:00
|
|
|
cfg *setting.Cfg, tracer tracing.Tracer,
|
|
|
|
|
orgService org.Service, sessionService auth.UserTokenService,
|
|
|
|
|
accessControlService accesscontrol.Service,
|
|
|
|
|
apikeyService apikey.Service, userService user.Service,
|
2023-01-10 14:08:52 +00:00
|
|
|
jwtService auth.JWTVerifierService,
|
2023-01-04 15:10:43 +00:00
|
|
|
loginAttempts loginattempt.Service, quotaService quota.Service,
|
2023-01-04 13:48:00 +01:00
|
|
|
authInfoService login.AuthInfoService, renderService rendering.Service,
|
2023-01-03 10:23:38 +01:00
|
|
|
) *Service {
|
2022-12-02 15:10:03 +01:00
|
|
|
s := &Service{
|
2023-01-10 14:55:27 +01:00
|
|
|
log: log.New("authn.service"),
|
|
|
|
|
cfg: cfg,
|
|
|
|
|
clients: make(map[string]authn.Client),
|
|
|
|
|
tracer: tracer,
|
|
|
|
|
sessionService: sessionService,
|
|
|
|
|
postAuthHooks: []authn.PostAuthHookFn{},
|
2022-12-02 15:10:03 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-04 13:48:00 +01:00
|
|
|
s.clients[authn.ClientRender] = clients.ProvideRender(userService, renderService)
|
2022-12-19 09:22:11 +01:00
|
|
|
s.clients[authn.ClientAPIKey] = clients.ProvideAPIKey(apikeyService, userService)
|
|
|
|
|
|
2023-01-04 15:10:43 +00:00
|
|
|
sessionClient := clients.ProvideSession(sessionService, userService, cfg.LoginCookieName, cfg.LoginMaxLifetime)
|
|
|
|
|
s.clients[authn.ClientSession] = sessionClient
|
|
|
|
|
s.RegisterPostAuthHook(sessionClient.RefreshTokenHook)
|
|
|
|
|
|
2022-12-02 15:10:03 +01:00
|
|
|
if s.cfg.AnonymousEnabled {
|
|
|
|
|
s.clients[authn.ClientAnonymous] = clients.ProvideAnonymous(cfg, orgService)
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-09 16:40:29 +01:00
|
|
|
var passwordClients []authn.PasswordClient
|
|
|
|
|
|
|
|
|
|
if !s.cfg.DisableLogin {
|
|
|
|
|
passwordClients = append(passwordClients, clients.ProvideGrafana(userService))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if s.cfg.LDAPEnabled {
|
|
|
|
|
passwordClients = append(passwordClients, clients.ProvideLDAP(cfg))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// only configure basic auth client if it is enabled, and we have at least one password client enabled
|
|
|
|
|
if s.cfg.BasicAuthEnabled && len(passwordClients) > 0 {
|
|
|
|
|
s.clients[authn.ClientBasic] = clients.ProvideBasic(loginAttempts, passwordClients...)
|
2023-01-03 10:23:38 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-10 14:08:52 +00:00
|
|
|
if s.cfg.JWTAuthEnabled {
|
|
|
|
|
s.clients[authn.ClientJWT] = clients.ProvideJWT(jwtService, cfg)
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 13:59:05 +00:00
|
|
|
// FIXME (jguer): move to User package
|
2023-01-03 10:23:38 +01:00
|
|
|
userSyncService := sync.ProvideUserSync(userService, authInfoService, quotaService)
|
|
|
|
|
orgUserSyncService := sync.ProvideOrgSync(userService, orgService, accessControlService)
|
2022-12-20 13:59:05 +00:00
|
|
|
s.RegisterPostAuthHook(userSyncService.SyncUser)
|
|
|
|
|
s.RegisterPostAuthHook(orgUserSyncService.SyncOrgUser)
|
|
|
|
|
|
2022-12-02 15:10:03 +01:00
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 10:57:47 +01:00
|
|
|
type Service struct {
|
2022-12-02 15:10:03 +01:00
|
|
|
log log.Logger
|
|
|
|
|
cfg *setting.Cfg
|
|
|
|
|
clients map[string]authn.Client
|
2023-01-10 14:55:27 +01:00
|
|
|
|
|
|
|
|
tracer tracing.Tracer
|
|
|
|
|
sessionService auth.UserTokenService
|
|
|
|
|
|
2022-12-20 13:59:05 +00:00
|
|
|
// postAuthHooks are called after a successful authentication. They can modify the identity.
|
|
|
|
|
postAuthHooks []authn.PostAuthHookFn
|
2023-01-12 15:02:04 +01:00
|
|
|
// postLoginHooks are called after a login request is performed, both for failing and successful requests.
|
|
|
|
|
postLoginHooks []authn.PostLoginHookFn
|
2022-12-02 15:10:03 +01:00
|
|
|
}
|
|
|
|
|
|
2022-12-19 09:22:11 +01:00
|
|
|
func (s *Service) Authenticate(ctx context.Context, client string, r *authn.Request) (*authn.Identity, bool, error) {
|
|
|
|
|
c, ok := s.clients[client]
|
2022-12-02 15:10:03 +01:00
|
|
|
if !ok {
|
2022-12-19 09:22:11 +01:00
|
|
|
return nil, false, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !c.Test(ctx, r) {
|
|
|
|
|
return nil, false, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-04 16:25:42 +01:00
|
|
|
ctx, span := s.tracer.Start(ctx, "authn.Authenticate")
|
|
|
|
|
defer span.End()
|
|
|
|
|
span.SetAttributes("authn.client", client, attribute.Key("authn.client").String(client))
|
|
|
|
|
|
2022-12-20 21:18:48 +01:00
|
|
|
r.OrgID = orgIDFromRequest(r)
|
2022-12-19 09:22:11 +01:00
|
|
|
identity, err := c.Authenticate(ctx, r)
|
|
|
|
|
if err != nil {
|
2023-01-04 16:25:42 +01:00
|
|
|
s.log.FromContext(ctx).Warn("auth client could not authenticate request", "client", client, "error", err)
|
2022-12-19 09:22:11 +01:00
|
|
|
span.AddEvents([]string{"message"}, []tracing.EventValue{{Str: "auth client could not authenticate request"}})
|
|
|
|
|
return nil, true, err
|
2022-12-02 15:10:03 +01:00
|
|
|
}
|
|
|
|
|
|
2022-12-20 13:59:05 +00:00
|
|
|
for _, hook := range s.postAuthHooks {
|
2023-01-05 20:17:41 +01:00
|
|
|
if err := hook(ctx, identity, r); err != nil {
|
2023-01-10 14:08:52 +00:00
|
|
|
s.log.FromContext(ctx).Warn("post auth hook failed", "error", err, "id", identity)
|
2022-12-20 13:59:05 +00:00
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-19 09:22:11 +01:00
|
|
|
return identity, true, nil
|
2022-11-29 10:57:47 +01:00
|
|
|
}
|
2022-12-20 13:59:05 +00:00
|
|
|
|
2023-01-12 15:02:04 +01:00
|
|
|
func (s *Service) RegisterPostAuthHook(hook authn.PostAuthHookFn) {
|
|
|
|
|
s.postAuthHooks = append(s.postAuthHooks, hook)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (identity *authn.Identity, err error) {
|
|
|
|
|
var ok bool
|
|
|
|
|
identity, ok, err = s.Authenticate(ctx, client, r)
|
2023-01-10 14:55:27 +01:00
|
|
|
if !ok {
|
|
|
|
|
return nil, authn.ErrClientNotConfigured.Errorf("client not configured: %s", client)
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 15:02:04 +01:00
|
|
|
defer func() {
|
|
|
|
|
for _, hook := range s.postLoginHooks {
|
|
|
|
|
hook(ctx, identity, r, err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2023-01-10 14:55:27 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace, id := identity.NamespacedID()
|
|
|
|
|
|
|
|
|
|
// Login is only supported for users
|
2023-01-12 15:02:04 +01:00
|
|
|
if namespace != authn.NamespaceUser || id <= 0 {
|
2023-01-10 14:55:27 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
identity.SessionToken = sessionToken
|
|
|
|
|
return identity, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 15:02:04 +01:00
|
|
|
func (s *Service) RegisterPostLoginHook(hook authn.PostLoginHookFn) {
|
|
|
|
|
s.postLoginHooks = append(s.postLoginHooks, hook)
|
2022-12-20 13:59:05 +00:00
|
|
|
}
|
2022-12-20 21:18:48 +01:00
|
|
|
|
|
|
|
|
func orgIDFromRequest(r *authn.Request) int64 {
|
|
|
|
|
if r.HTTPRequest == nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
orgID := orgIDFromQuery(r.HTTPRequest)
|
|
|
|
|
if orgID > 0 {
|
|
|
|
|
return orgID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return orgIDFromHeader(r.HTTPRequest)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// name of query string used to target specific org for request
|
|
|
|
|
const orgIDTargetQuery = "targetOrgId"
|
|
|
|
|
|
|
|
|
|
func orgIDFromQuery(req *http.Request) int64 {
|
|
|
|
|
params := req.URL.Query()
|
|
|
|
|
if !params.Has(orgIDTargetQuery) {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
id, err := strconv.ParseInt(params.Get(orgIDTargetQuery), 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
return id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// name of header containing org id for request
|
|
|
|
|
const orgIDHeaderName = "X-Grafana-Org-Id"
|
|
|
|
|
|
|
|
|
|
func orgIDFromHeader(req *http.Request) int64 {
|
|
|
|
|
header := req.Header.Get(orgIDHeaderName)
|
|
|
|
|
if header == "" {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
id, err := strconv.ParseInt(header, 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
return id
|
|
|
|
|
}
|