mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AuthN: Add auth proxy client (#61555)
* AuthN: set up boilerplate for proxy client * AuthN: Implement Test for proxy client * AuthN: parse accept list in constructor * AuthN: add proxy client interface * AuthN: handle error * AuthN: Implement the proxy client interface for ldap * AuthN: change reciever name * AuthN: add grafana as a proxy client * AuthN: for error returned * AuthN: add tests for grafana proxy auth * AuthN: swap order of grafan and ldap auth * AuthN: Parse additional proxy headers in proxy client and pass down
This commit is contained in:
parent
c1d3b59643
commit
b44b6fc5c6
@ -25,6 +25,7 @@ const (
|
|||||||
ClientRender = "auth.client.render"
|
ClientRender = "auth.client.render"
|
||||||
ClientSession = "auth.client.session"
|
ClientSession = "auth.client.session"
|
||||||
ClientForm = "auth.client.form"
|
ClientForm = "auth.client.form"
|
||||||
|
ClientProxy = "auth.client.proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -72,6 +73,10 @@ type PasswordClient interface {
|
|||||||
AuthenticatePassword(ctx context.Context, r *Request, username, password string) (*Identity, error)
|
AuthenticatePassword(ctx context.Context, r *Request, username, password string) (*Identity, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxyClient interface {
|
||||||
|
AuthenticateProxy(ctx context.Context, r *Request, username string, additional map[string]string) (*Identity, error)
|
||||||
|
}
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
// OrgID will be populated by authn.Service
|
// OrgID will be populated by authn.Service
|
||||||
OrgID int64
|
OrgID int64
|
||||||
|
@ -64,12 +64,18 @@ func ProvideService(
|
|||||||
s.clients[authn.ClientAnonymous] = clients.ProvideAnonymous(cfg, orgService)
|
s.clients[authn.ClientAnonymous] = clients.ProvideAnonymous(cfg, orgService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var proxyClients []authn.ProxyClient
|
||||||
var passwordClients []authn.PasswordClient
|
var passwordClients []authn.PasswordClient
|
||||||
if !s.cfg.DisableLogin {
|
|
||||||
passwordClients = append(passwordClients, clients.ProvideGrafana(userService))
|
|
||||||
}
|
|
||||||
if s.cfg.LDAPEnabled {
|
if s.cfg.LDAPEnabled {
|
||||||
passwordClients = append(passwordClients, clients.ProvideLDAP(cfg))
|
ldap := clients.ProvideLDAP(cfg)
|
||||||
|
proxyClients = append(proxyClients, ldap)
|
||||||
|
passwordClients = append(passwordClients, ldap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.cfg.DisableLogin {
|
||||||
|
grafana := clients.ProvideGrafana(cfg, userService)
|
||||||
|
proxyClients = append(proxyClients, grafana)
|
||||||
|
passwordClients = append(passwordClients, grafana)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have password clients configure check if basic auth or form auth is enabled
|
// if we have password clients configure check if basic auth or form auth is enabled
|
||||||
@ -84,6 +90,15 @@ func ProvideService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.cfg.AuthProxyEnabled && len(proxyClients) > 0 {
|
||||||
|
proxy, err := clients.ProvideProxy(cfg, proxyClients...)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("failed to configure auth proxy", "err", err)
|
||||||
|
} else {
|
||||||
|
s.clients[authn.ClientProxy] = proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.cfg.JWTAuthEnabled {
|
if s.cfg.JWTAuthEnabled {
|
||||||
s.clients[authn.ClientJWT] = clients.ProvideJWT(jwtService, cfg)
|
s.clients[authn.ClientJWT] = clients.ProvideJWT(jwtService, cfg)
|
||||||
}
|
}
|
||||||
|
@ -26,3 +26,16 @@ func (m MockClient) Test(ctx context.Context, r *authn.Request) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ authn.ProxyClient = new(MockProxyClient)
|
||||||
|
|
||||||
|
type MockProxyClient struct {
|
||||||
|
AuthenticateProxyFunc func(ctx context.Context, r *authn.Request, username string, additional map[string]string) (*authn.Identity, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MockProxyClient) AuthenticateProxy(ctx context.Context, r *authn.Request, username string, additional map[string]string) (*authn.Identity, error) {
|
||||||
|
if m.AuthenticateProxyFunc != nil {
|
||||||
|
return m.AuthenticateProxyFunc(ctx, r, username, additional)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
@ -4,23 +4,88 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/mail"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"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/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ authn.ProxyClient = new(Grafana)
|
||||||
var _ authn.PasswordClient = new(Grafana)
|
var _ authn.PasswordClient = new(Grafana)
|
||||||
|
|
||||||
func ProvideGrafana(userService user.Service) *Grafana {
|
func ProvideGrafana(cfg *setting.Cfg, userService user.Service) *Grafana {
|
||||||
return &Grafana{userService}
|
return &Grafana{cfg, userService}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Grafana struct {
|
type Grafana struct {
|
||||||
|
cfg *setting.Cfg
|
||||||
userService user.Service
|
userService user.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, username, password string) (*authn.Identity, error) {
|
func (c *Grafana) AuthenticateProxy(ctx context.Context, r *authn.Request, username string, additional map[string]string) (*authn.Identity, error) {
|
||||||
|
identity := &authn.Identity{
|
||||||
|
AuthModule: login.AuthProxyAuthModule,
|
||||||
|
AuthID: username,
|
||||||
|
ClientParams: authn.ClientParams{
|
||||||
|
SyncUser: true,
|
||||||
|
SyncTeamMembers: true,
|
||||||
|
AllowSignUp: c.cfg.AuthProxyAutoSignUp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.cfg.AuthProxyHeaderProperty {
|
||||||
|
case "username":
|
||||||
|
identity.Login = username
|
||||||
|
addr, err := mail.ParseAddress(username)
|
||||||
|
if err == nil {
|
||||||
|
identity.Email = addr.Address
|
||||||
|
}
|
||||||
|
case "email":
|
||||||
|
identity.Login = username
|
||||||
|
identity.Email = username
|
||||||
|
default:
|
||||||
|
return nil, errInvalidProxyHeader.Errorf("invalid auth proxy header property, expected username or email but got: %s", c.cfg.AuthProxyHeaderProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := additional[proxyFieldName]; ok {
|
||||||
|
identity.Name = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := additional[proxyFieldEmail]; ok {
|
||||||
|
identity.Email = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := additional[proxyFieldLogin]; ok {
|
||||||
|
identity.Login = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := additional[proxyFieldRole]; ok {
|
||||||
|
role := org.RoleType(v)
|
||||||
|
if role.IsValid() {
|
||||||
|
orgID := int64(1)
|
||||||
|
if c.cfg.AutoAssignOrg && c.cfg.AutoAssignOrgId > 0 {
|
||||||
|
orgID = int64(c.cfg.AutoAssignOrgId)
|
||||||
|
}
|
||||||
|
identity.OrgID = orgID
|
||||||
|
identity.OrgRoles = map[int64]org.RoleType{orgID: role}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := additional[proxyFieldGroups]; ok {
|
||||||
|
identity.Groups = util.SplitString(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
identity.ClientParams.LookUpParams.Email = &identity.Email
|
||||||
|
identity.ClientParams.LookUpParams.Login = &identity.Login
|
||||||
|
|
||||||
|
return identity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, username, password string) (*authn.Identity, error) {
|
||||||
usr, err := c.userService.GetByLogin(ctx, &user.GetUserByLoginQuery{LoginOrEmail: username})
|
usr, err := c.userService.GetByLogin(ctx, &user.GetUserByLoginQuery{LoginOrEmail: username})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, user.ErrUserNotFound) {
|
if errors.Is(err, user.ErrUserNotFound) {
|
||||||
|
@ -2,16 +2,125 @@ package clients
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestGrafana_AuthenticateProxy(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
desc string
|
||||||
|
req *authn.Request
|
||||||
|
username string
|
||||||
|
proxyProperty string
|
||||||
|
additional map[string]string
|
||||||
|
expectedErr error
|
||||||
|
expectedIdentity *authn.Identity
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
desc: "expect valid identity",
|
||||||
|
username: "test",
|
||||||
|
req: &authn.Request{HTTPRequest: &http.Request{}},
|
||||||
|
proxyProperty: "username",
|
||||||
|
additional: map[string]string{
|
||||||
|
proxyFieldName: "name",
|
||||||
|
proxyFieldRole: "Viewer",
|
||||||
|
proxyFieldGroups: "grp1,grp2",
|
||||||
|
proxyFieldEmail: "email@email.com",
|
||||||
|
},
|
||||||
|
expectedIdentity: &authn.Identity{
|
||||||
|
OrgID: 1,
|
||||||
|
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
|
||||||
|
Login: "test",
|
||||||
|
Name: "name",
|
||||||
|
Email: "email@email.com",
|
||||||
|
AuthModule: "authproxy",
|
||||||
|
AuthID: "test",
|
||||||
|
Groups: []string{"grp1", "grp2"},
|
||||||
|
ClientParams: authn.ClientParams{
|
||||||
|
SyncUser: true,
|
||||||
|
SyncTeamMembers: true,
|
||||||
|
AllowSignUp: true,
|
||||||
|
LookUpParams: models.UserLookupParams{
|
||||||
|
Email: strPtr("email@email.com"),
|
||||||
|
Login: strPtr("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should set email as both email and login when configured proxy auth header property is email",
|
||||||
|
username: "test@test.com",
|
||||||
|
req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{}}},
|
||||||
|
additional: map[string]string{},
|
||||||
|
expectedIdentity: &authn.Identity{
|
||||||
|
Login: "test@test.com",
|
||||||
|
Email: "test@test.com",
|
||||||
|
AuthModule: "authproxy",
|
||||||
|
AuthID: "test@test.com",
|
||||||
|
ClientParams: authn.ClientParams{
|
||||||
|
SyncUser: true,
|
||||||
|
SyncTeamMembers: true,
|
||||||
|
AllowSignUp: true,
|
||||||
|
LookUpParams: models.UserLookupParams{
|
||||||
|
Email: strPtr("test@test.com"),
|
||||||
|
Login: strPtr("test@test.com"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
proxyProperty: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return error on invalid auth proxy header property",
|
||||||
|
req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{}}},
|
||||||
|
proxyProperty: "other",
|
||||||
|
expectedErr: errInvalidProxyHeader,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.AuthProxyAutoSignUp = true
|
||||||
|
cfg.AuthProxyHeaderProperty = tt.proxyProperty
|
||||||
|
c := ProvideGrafana(cfg, usertest.NewUserServiceFake())
|
||||||
|
|
||||||
|
identity, err := c.AuthenticateProxy(context.Background(), tt.req, tt.username, tt.additional)
|
||||||
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
if tt.expectedIdentity != nil {
|
||||||
|
assert.Equal(t, tt.expectedIdentity.OrgID, identity.OrgID)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.Login, identity.Login)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.Name, identity.Name)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.Email, identity.Email)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.AuthID, identity.AuthID)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.AuthModule, identity.AuthModule)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.Groups, identity.Groups)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedIdentity.ClientParams.SyncUser, identity.ClientParams.SyncUser)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.ClientParams.AllowSignUp, identity.ClientParams.AllowSignUp)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.ClientParams.SyncTeamMembers, identity.ClientParams.SyncTeamMembers)
|
||||||
|
assert.Equal(t, tt.expectedIdentity.ClientParams.EnableDisabledUsers, identity.ClientParams.EnableDisabledUsers)
|
||||||
|
|
||||||
|
assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.Email, identity.ClientParams.LookUpParams.Email)
|
||||||
|
assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.Login, identity.ClientParams.LookUpParams.Login)
|
||||||
|
assert.EqualValues(t, tt.expectedIdentity.ClientParams.LookUpParams.UserID, identity.ClientParams.LookUpParams.UserID)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, tt.expectedIdentity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGrafana_AuthenticatePassword(t *testing.T) {
|
func TestGrafana_AuthenticatePassword(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
desc string
|
desc string
|
||||||
@ -60,7 +169,7 @@ func TestGrafana_AuthenticatePassword(t *testing.T) {
|
|||||||
userService.ExpectedError = user.ErrUserNotFound
|
userService.ExpectedError = user.ErrUserNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
c := ProvideGrafana(userService)
|
c := ProvideGrafana(setting.NewCfg(), userService)
|
||||||
identity, err := c.AuthenticatePassword(context.Background(), &authn.Request{OrgID: 1}, tt.username, tt.password)
|
identity, err := c.AuthenticatePassword(context.Background(), &authn.Request{OrgID: 1}, tt.username, tt.password)
|
||||||
assert.ErrorIs(t, err, tt.expectedErr)
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
assert.EqualValues(t, tt.expectedIdentity, identity)
|
assert.EqualValues(t, tt.expectedIdentity, identity)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ authn.ProxyClient = new(LDAP)
|
||||||
var _ authn.PasswordClient = new(LDAP)
|
var _ authn.PasswordClient = new(LDAP)
|
||||||
|
|
||||||
func ProvideLDAP(cfg *setting.Cfg) *LDAP {
|
func ProvideLDAP(cfg *setting.Cfg) *LDAP {
|
||||||
@ -21,6 +22,19 @@ type LDAP struct {
|
|||||||
service ldapService
|
service ldapService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *LDAP) AuthenticateProxy(ctx context.Context, r *authn.Request, username string, _ map[string]string) (*authn.Identity, error) {
|
||||||
|
info, err := c.service.User(username)
|
||||||
|
if errors.Is(err, multildap.ErrDidNotFindUser) {
|
||||||
|
return nil, errIdentityNotFound.Errorf("no user found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return identityFromLDAPInfo(r.OrgID, info, c.cfg.LDAPAllowSignup), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *LDAP) AuthenticatePassword(ctx context.Context, r *authn.Request, username, password string) (*authn.Identity, error) {
|
func (c *LDAP) AuthenticatePassword(ctx context.Context, r *authn.Request, username, password string) (*authn.Identity, error) {
|
||||||
info, err := c.service.Login(&models.LoginUserQuery{
|
info, err := c.service.Login(&models.LoginUserQuery{
|
||||||
Username: username,
|
Username: username,
|
||||||
@ -43,31 +57,12 @@ func (c *LDAP) AuthenticatePassword(ctx context.Context, r *authn.Request, usern
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &authn.Identity{
|
return identityFromLDAPInfo(r.OrgID, info, c.cfg.LDAPAllowSignup), nil
|
||||||
OrgID: r.OrgID,
|
|
||||||
OrgRoles: info.OrgRoles,
|
|
||||||
Login: info.Login,
|
|
||||||
Name: info.Name,
|
|
||||||
Email: info.Email,
|
|
||||||
IsGrafanaAdmin: info.IsGrafanaAdmin,
|
|
||||||
AuthModule: info.AuthModule,
|
|
||||||
AuthID: info.AuthId,
|
|
||||||
Groups: info.Groups,
|
|
||||||
ClientParams: authn.ClientParams{
|
|
||||||
SyncUser: true,
|
|
||||||
SyncTeamMembers: true,
|
|
||||||
AllowSignUp: c.cfg.LDAPAllowSignup,
|
|
||||||
EnableDisabledUsers: true,
|
|
||||||
LookUpParams: models.UserLookupParams{
|
|
||||||
Login: &info.Login,
|
|
||||||
Email: &info.Email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ldapService interface {
|
type ldapService interface {
|
||||||
Login(query *models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
Login(query *models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
||||||
|
User(username string) (*models.ExternalUserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: remove the implementation if we convert ldap to an actual service
|
// FIXME: remove the implementation if we convert ldap to an actual service
|
||||||
@ -83,3 +78,37 @@ func (s *ldapServiceImpl) Login(query *models.LoginUserQuery) (*models.ExternalU
|
|||||||
|
|
||||||
return multildap.New(cfg.Servers).Login(query)
|
return multildap.New(cfg.Servers).Login(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ldapServiceImpl) User(username string) (*models.ExternalUserInfo, error) {
|
||||||
|
cfg, err := multildap.GetConfig(s.cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, _, err := multildap.New(cfg.Servers).User(username)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func identityFromLDAPInfo(orgID int64, info *models.ExternalUserInfo, allowSignup bool) *authn.Identity {
|
||||||
|
return &authn.Identity{
|
||||||
|
OrgID: orgID,
|
||||||
|
OrgRoles: info.OrgRoles,
|
||||||
|
Login: info.Login,
|
||||||
|
Name: info.Name,
|
||||||
|
Email: info.Email,
|
||||||
|
IsGrafanaAdmin: info.IsGrafanaAdmin,
|
||||||
|
AuthModule: info.AuthModule,
|
||||||
|
AuthID: info.AuthId,
|
||||||
|
Groups: info.Groups,
|
||||||
|
ClientParams: authn.ClientParams{
|
||||||
|
SyncUser: true,
|
||||||
|
SyncTeamMembers: true,
|
||||||
|
AllowSignUp: allowSignup,
|
||||||
|
EnableDisabledUsers: true,
|
||||||
|
LookUpParams: models.UserLookupParams{
|
||||||
|
Login: &info.Login,
|
||||||
|
Email: &info.Email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,11 +8,74 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
|
"github.com/grafana/grafana/pkg/services/multildap"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestLDAP_AuthenticateProxy(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
desc string
|
||||||
|
username string
|
||||||
|
expectedLDAPErr error
|
||||||
|
expectedLDAPInfo *models.ExternalUserInfo
|
||||||
|
expectedErr error
|
||||||
|
expectedIdentity *authn.Identity
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
desc: "should return valid identity when found by ldap service",
|
||||||
|
username: "test",
|
||||||
|
expectedLDAPInfo: &models.ExternalUserInfo{
|
||||||
|
AuthModule: login.LDAPAuthModule,
|
||||||
|
AuthId: "123",
|
||||||
|
Email: "test@test.com",
|
||||||
|
Login: "test",
|
||||||
|
Name: "test test",
|
||||||
|
Groups: []string{"1", "2"},
|
||||||
|
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
|
||||||
|
},
|
||||||
|
expectedIdentity: &authn.Identity{
|
||||||
|
OrgID: 1,
|
||||||
|
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
|
||||||
|
Login: "test",
|
||||||
|
Name: "test test",
|
||||||
|
Email: "test@test.com",
|
||||||
|
AuthModule: login.LDAPAuthModule,
|
||||||
|
AuthID: "123",
|
||||||
|
Groups: []string{"1", "2"},
|
||||||
|
ClientParams: authn.ClientParams{
|
||||||
|
SyncUser: true,
|
||||||
|
SyncTeamMembers: true,
|
||||||
|
AllowSignUp: false,
|
||||||
|
EnableDisabledUsers: true,
|
||||||
|
LookUpParams: models.UserLookupParams{
|
||||||
|
Email: strPtr("test@test.com"),
|
||||||
|
Login: strPtr("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return error when user is not found",
|
||||||
|
username: "test",
|
||||||
|
expectedLDAPErr: multildap.ErrDidNotFindUser,
|
||||||
|
expectedErr: errIdentityNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
c := &LDAP{cfg: setting.NewCfg(), service: fakeLDAPService{ExpectedInfo: tt.expectedLDAPInfo, ExpectedErr: tt.expectedLDAPErr}}
|
||||||
|
identity, err := c.AuthenticateProxy(context.Background(), &authn.Request{OrgID: 1}, tt.username, nil)
|
||||||
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
assert.EqualValues(t, tt.expectedIdentity, identity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLDAP_AuthenticatePassword(t *testing.T) {
|
func TestLDAP_AuthenticatePassword(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
desc string
|
desc string
|
||||||
@ -20,7 +83,7 @@ func TestLDAP_AuthenticatePassword(t *testing.T) {
|
|||||||
password string
|
password string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
expectedLDAPErr error
|
expectedLDAPErr error
|
||||||
expectedInfo *models.ExternalUserInfo
|
expectedLDAPInfo *models.ExternalUserInfo
|
||||||
expectedIdentity *authn.Identity
|
expectedIdentity *authn.Identity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +92,7 @@ func TestLDAP_AuthenticatePassword(t *testing.T) {
|
|||||||
desc: "should successfully authenticate with correct username and password",
|
desc: "should successfully authenticate with correct username and password",
|
||||||
username: "test",
|
username: "test",
|
||||||
password: "test123",
|
password: "test123",
|
||||||
expectedInfo: &models.ExternalUserInfo{
|
expectedLDAPInfo: &models.ExternalUserInfo{
|
||||||
AuthModule: login.LDAPAuthModule,
|
AuthModule: login.LDAPAuthModule,
|
||||||
AuthId: "123",
|
AuthId: "123",
|
||||||
Email: "test@test.com",
|
Email: "test@test.com",
|
||||||
@ -77,7 +140,7 @@ func TestLDAP_AuthenticatePassword(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
c := &LDAP{cfg: setting.NewCfg(), service: fakeLDAPService{ExpectedInfo: tt.expectedInfo, ExpectedErr: tt.expectedLDAPErr}}
|
c := &LDAP{cfg: setting.NewCfg(), service: fakeLDAPService{ExpectedInfo: tt.expectedLDAPInfo, ExpectedErr: tt.expectedLDAPErr}}
|
||||||
|
|
||||||
identity, err := c.AuthenticatePassword(context.Background(), &authn.Request{OrgID: 1}, tt.username, tt.password)
|
identity, err := c.AuthenticatePassword(context.Background(), &authn.Request{OrgID: 1}, tt.username, tt.password)
|
||||||
assert.ErrorIs(t, err, tt.expectedErr)
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
@ -100,3 +163,7 @@ type fakeLDAPService struct {
|
|||||||
func (f fakeLDAPService) Login(query *models.LoginUserQuery) (*models.ExternalUserInfo, error) {
|
func (f fakeLDAPService) Login(query *models.LoginUserQuery) (*models.ExternalUserInfo, error) {
|
||||||
return f.ExpectedInfo, f.ExpectedErr
|
return f.ExpectedInfo, f.ExpectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f fakeLDAPService) User(username string) (*models.ExternalUserInfo, error) {
|
||||||
|
return f.ExpectedInfo, f.ExpectedErr
|
||||||
|
}
|
||||||
|
147
pkg/services/authn/clients/proxy.go
Normal file
147
pkg/services/authn/clients/proxy.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package clients
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
proxyFieldName = "Name"
|
||||||
|
proxyFieldEmail = "Email"
|
||||||
|
proxyFieldLogin = "Login"
|
||||||
|
proxyFieldRole = "Role"
|
||||||
|
proxyFieldGroups = "Groups"
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxyFields = [...]string{proxyFieldName, proxyFieldEmail, proxyFieldLogin, proxyFieldRole, proxyFieldGroups}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotAcceptedIP = errutil.NewBase(errutil.StatusUnauthorized, "auth-proxy.invalid-ip")
|
||||||
|
errEmptyProxyHeader = errutil.NewBase(errutil.StatusUnauthorized, "auth-proxy.empty-header")
|
||||||
|
errInvalidProxyHeader = errutil.NewBase(errutil.StatusInternal, "auth-proxy.invalid-proxy-header")
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ authn.Client = new(Proxy)
|
||||||
|
|
||||||
|
func ProvideProxy(cfg *setting.Cfg, clients ...authn.ProxyClient) (*Proxy, error) {
|
||||||
|
list, err := parseAcceptList(cfg.AuthProxyWhitelist)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Proxy{cfg, clients, list}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
cfg *setting.Cfg
|
||||||
|
clients []authn.ProxyClient
|
||||||
|
acceptedIPs []*net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||||
|
if !c.isAllowedIP(r) {
|
||||||
|
return nil, errNotAcceptedIP.Errorf("request ip is not in the configured accept list")
|
||||||
|
}
|
||||||
|
|
||||||
|
username := getProxyHeader(r, c.cfg.AuthProxyHeaderName, c.cfg.AuthProxyHeadersEncoded)
|
||||||
|
if len(username) == 0 {
|
||||||
|
return nil, errEmptyProxyHeader.Errorf("no username provided in auth proxy header")
|
||||||
|
}
|
||||||
|
|
||||||
|
additional := getAdditionalProxyHeaders(r, c.cfg)
|
||||||
|
|
||||||
|
// FIXME: add cache to prevent sync on every request
|
||||||
|
|
||||||
|
var clientErr error
|
||||||
|
for _, proxyClient := range c.clients {
|
||||||
|
var identity *authn.Identity
|
||||||
|
identity, clientErr = proxyClient.AuthenticateProxy(ctx, r, username, additional)
|
||||||
|
if identity != nil {
|
||||||
|
return identity, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, clientErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) Test(ctx context.Context, r *authn.Request) bool {
|
||||||
|
return len(getProxyHeader(r, c.cfg.AuthProxyHeaderName, c.cfg.AuthProxyHeadersEncoded)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) isAllowedIP(r *authn.Request) bool {
|
||||||
|
if len(c.acceptedIPs) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(r.HTTPRequest.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
for _, v := range c.acceptedIPs {
|
||||||
|
if v.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAcceptList(s string) ([]*net.IPNet, error) {
|
||||||
|
if len(strings.TrimSpace(s)) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
addresses := strings.Split(s, ",")
|
||||||
|
list := make([]*net.IPNet, 0, len(addresses))
|
||||||
|
for _, addr := range addresses {
|
||||||
|
result, err := coerceProxyAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list = append(list, result)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// coerceProxyAddress gets network of the presented CIDR notation
|
||||||
|
func coerceProxyAddress(proxyAddr string) (*net.IPNet, error) {
|
||||||
|
proxyAddr = strings.TrimSpace(proxyAddr)
|
||||||
|
if !strings.Contains(proxyAddr, "/") {
|
||||||
|
proxyAddr = path.Join(proxyAddr, "32")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, network, err := net.ParseCIDR(proxyAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse the network: %w", err)
|
||||||
|
}
|
||||||
|
return network, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProxyHeader(r *authn.Request, headerName string, encoded bool) string {
|
||||||
|
if r.HTTPRequest == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v := r.HTTPRequest.Header.Get(headerName)
|
||||||
|
if encoded {
|
||||||
|
v = util.DecodeQuotedPrintable(v)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAdditionalProxyHeaders(r *authn.Request, cfg *setting.Cfg) map[string]string {
|
||||||
|
additional := make(map[string]string, len(proxyFields))
|
||||||
|
for _, k := range proxyFields {
|
||||||
|
if v := getProxyHeader(r, cfg.AuthProxyHeaders[k], cfg.AuthProxyHeadersEncoded); v != "" {
|
||||||
|
additional[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return additional
|
||||||
|
}
|
172
pkg/services/authn/clients/proxy_test.go
Normal file
172
pkg/services/authn/clients/proxy_test.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package clients
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProxy_Authenticate(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
desc string
|
||||||
|
req *authn.Request
|
||||||
|
ips string
|
||||||
|
proxyHeader string
|
||||||
|
proxyHeaders map[string]string
|
||||||
|
expectedErr error
|
||||||
|
expectedUsername string
|
||||||
|
expectedAdditional map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
desc: "should authenticate using passed in proxy client",
|
||||||
|
ips: "127.0.0.1",
|
||||||
|
req: &authn.Request{
|
||||||
|
HTTPRequest: &http.Request{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"X-Username": {"username"},
|
||||||
|
"X-Name": {"name"},
|
||||||
|
"X-Email": {"email"},
|
||||||
|
"X-Login": {"login"},
|
||||||
|
"X-Role": {"Viewer"},
|
||||||
|
"X-Group": {"grp1,grp2"},
|
||||||
|
},
|
||||||
|
RemoteAddr: "127.0.0.1:333",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
proxyHeader: "X-Username",
|
||||||
|
proxyHeaders: map[string]string{
|
||||||
|
proxyFieldName: "X-Name",
|
||||||
|
proxyFieldEmail: "X-Email",
|
||||||
|
proxyFieldLogin: "X-Login",
|
||||||
|
proxyFieldRole: "X-Role",
|
||||||
|
proxyFieldGroups: "X-Group",
|
||||||
|
},
|
||||||
|
expectedUsername: "username",
|
||||||
|
expectedAdditional: map[string]string{
|
||||||
|
proxyFieldName: "name",
|
||||||
|
proxyFieldEmail: "email",
|
||||||
|
proxyFieldLogin: "login",
|
||||||
|
proxyFieldRole: "Viewer",
|
||||||
|
proxyFieldGroups: "grp1,grp2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should fail when proxy header is empty",
|
||||||
|
req: &authn.Request{
|
||||||
|
HTTPRequest: &http.Request{Header: map[string][]string{
|
||||||
|
"X-Username": {""},
|
||||||
|
"X-Name": {"name"},
|
||||||
|
"X-Email": {"email"},
|
||||||
|
"X-Login": {"login"},
|
||||||
|
"X-Role": {"Viewer"},
|
||||||
|
"X-Group": {"grp1,grp2"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
proxyHeader: "X-Username",
|
||||||
|
proxyHeaders: map[string]string{
|
||||||
|
proxyFieldName: "X-Name",
|
||||||
|
proxyFieldEmail: "X-Email",
|
||||||
|
proxyFieldLogin: "X-Login",
|
||||||
|
proxyFieldRole: "X-Role",
|
||||||
|
proxyFieldGroups: "X-Group",
|
||||||
|
},
|
||||||
|
expectedErr: errEmptyProxyHeader,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should fail when caller ip is not in accept list",
|
||||||
|
req: &authn.Request{
|
||||||
|
HTTPRequest: &http.Request{
|
||||||
|
Header: map[string][]string{},
|
||||||
|
RemoteAddr: "127.0.0.2:333",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ips: "127.0.0.1",
|
||||||
|
expectedErr: errNotAcceptedIP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.AuthProxyHeaderName = "X-Username"
|
||||||
|
cfg.AuthProxyHeaders = tt.proxyHeaders
|
||||||
|
cfg.AuthProxyWhitelist = tt.ips
|
||||||
|
|
||||||
|
calledUsername := ""
|
||||||
|
var calledAdditional map[string]string
|
||||||
|
|
||||||
|
proxyClient := authntest.MockProxyClient{AuthenticateProxyFunc: func(ctx context.Context, r *authn.Request, username string, additional map[string]string) (*authn.Identity, error) {
|
||||||
|
calledUsername = username
|
||||||
|
calledAdditional = additional
|
||||||
|
return nil, nil
|
||||||
|
}}
|
||||||
|
c, err := ProvideProxy(cfg, proxyClient)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = c.Authenticate(context.Background(), tt.req)
|
||||||
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
assert.Equal(t, tt.expectedUsername, calledUsername)
|
||||||
|
assert.EqualValues(t, tt.expectedAdditional, calledAdditional)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxy_Test(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
desc string
|
||||||
|
req *authn.Request
|
||||||
|
expectedOK bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
desc: "should return true when proxy header exists",
|
||||||
|
req: &authn.Request{
|
||||||
|
HTTPRequest: &http.Request{
|
||||||
|
Header: map[string][]string{"Proxy-Header": {"some value"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return false when proxy header exists but has no value",
|
||||||
|
req: &authn.Request{
|
||||||
|
HTTPRequest: &http.Request{
|
||||||
|
Header: map[string][]string{"Proxy-Header": {""}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return false when no proxy header is set on request",
|
||||||
|
req: &authn.Request{
|
||||||
|
HTTPRequest: &http.Request{Header: map[string][]string{}},
|
||||||
|
},
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should return false when no http request is present",
|
||||||
|
req: &authn.Request{},
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.AuthProxyHeaderName = "Proxy-Header"
|
||||||
|
|
||||||
|
c, _ := ProvideProxy(cfg, nil)
|
||||||
|
assert.Equal(t, tt.expectedOK, c.Test(context.Background(), tt.req))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
||||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||||
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
"github.com/grafana/grafana/pkg/services/rendering"
|
||||||
@ -106,7 +107,7 @@ func getContextHandler(t *testing.T) *ContextHandler {
|
|||||||
|
|
||||||
return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc,
|
return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc,
|
||||||
renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator,
|
renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator,
|
||||||
&userService, orgService, nil, nil, &authntest.FakeService{})
|
&userService, orgService, nil, featuremgmt.WithFeatures(), &authntest.FakeService{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeAuthenticator struct{}
|
type fakeAuthenticator struct{}
|
||||||
|
@ -714,6 +714,28 @@ func (h *ContextHandler) handleError(ctx *models.ReqContext, err error, statusCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext, orgID int64) bool {
|
func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext, orgID int64) bool {
|
||||||
|
if h.features.IsEnabled(featuremgmt.FlagAuthnService) {
|
||||||
|
identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), authn.ClientProxy, &authn.Request{HTTPRequest: reqContext.Req, Resp: reqContext.Resp})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeErr(reqContext, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), h.Cfg.AuthProxyHeaderName)
|
||||||
|
for _, header := range h.Cfg.AuthProxyHeaders {
|
||||||
|
if header != "" {
|
||||||
|
ctx = WithAuthHTTPHeader(ctx, header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*reqContext.Req = *reqContext.Req.WithContext(ctx)
|
||||||
|
reqContext.IsSignedIn = true
|
||||||
|
reqContext.SignedInUser = identity.SignedInUser()
|
||||||
|
return true
|
||||||
|
}
|
||||||
username := reqContext.Req.Header.Get(h.Cfg.AuthProxyHeaderName)
|
username := reqContext.Req.Header.Get(h.Cfg.AuthProxyHeaderName)
|
||||||
|
|
||||||
logger := log.New("auth.proxy")
|
logger := log.New("auth.proxy")
|
||||||
|
Loading…
Reference in New Issue
Block a user