The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/authn/clients/ldap.go

142 lines
4.4 KiB

package clients
import (
"context"
"errors"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/ldap/multildap"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
var _ authn.ProxyClient = new(LDAP)
var _ authn.PasswordClient = new(LDAP)
type ldapService interface {
Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error)
User(username string) (*login.ExternalUserInfo, error)
}
func ProvideLDAP(cfg *setting.Cfg, ldapService ldapService, userService user.Service, authInfoService login.AuthInfoService) *LDAP {
return &LDAP{cfg, log.New("authn.ldap"), ldapService, userService, authInfoService}
}
type LDAP struct {
cfg *setting.Cfg
logger log.Logger
service ldapService
userService user.Service
authInfoService login.AuthInfoService
}
func (c *LDAP) String() string {
return "ldap"
}
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 c.disableUser(ctx, username)
}
if err != nil {
return nil, err
}
return c.identityFromLDAPInfo(r.OrgID, info), nil
}
func (c *LDAP) AuthenticatePassword(ctx context.Context, r *authn.Request, username, password string) (*authn.Identity, error) {
info, err := c.service.Login(&login.LoginUserQuery{
Username: username,
Password: password,
})
if errors.Is(err, multildap.ErrCouldNotFindUser) {
return c.disableUser(ctx, username)
}
// user was found so set auth module in req metadata
r.SetMeta(authn.MetaKeyAuthModule, "ldap")
if errors.Is(err, multildap.ErrInvalidCredentials) {
return nil, errInvalidPassword.Errorf("invalid password: %w", err)
}
if err != nil {
return nil, err
}
return c.identityFromLDAPInfo(r.OrgID, info), nil
}
// disableUser will disable users if they logged in via LDAP previously
func (c *LDAP) disableUser(ctx context.Context, username string) (*authn.Identity, error) {
c.logger.Debug("User was not found in the LDAP directory tree", "username", username)
retErr := errIdentityNotFound.Errorf("no user found: %w", multildap.ErrDidNotFindUser)
// Retrieve the user from store based on the login
dbUser, errGet := c.userService.GetByLogin(ctx, &user.GetUserByLoginQuery{
LoginOrEmail: username,
})
if errors.Is(errGet, user.ErrUserNotFound) {
return nil, retErr
} else if errGet != nil {
return nil, errGet
}
// Check if the user logged in via LDAP
query := &login.GetAuthInfoQuery{UserId: dbUser.ID, AuthModule: login.LDAPAuthModule}
authinfo, errGetAuthInfo := c.authInfoService.GetAuthInfo(ctx, query)
if errors.Is(errGetAuthInfo, user.ErrUserNotFound) {
return nil, retErr
} else if errGetAuthInfo != nil {
return nil, errGetAuthInfo
}
// Disable the user
c.logger.Debug("User was removed from the LDAP directory tree, disabling it", "username", username, "authID", authinfo.AuthId)
if errDisable := c.userService.Disable(ctx, &user.DisableUserCommand{UserID: dbUser.ID, IsDisabled: true}); errDisable != nil {
return nil, errDisable
}
return nil, retErr
}
func (c *LDAP) identityFromLDAPInfo(orgID int64, info *login.ExternalUserInfo) *authn.Identity {
id := &authn.Identity{
OrgID: orgID,
OrgRoles: info.OrgRoles,
Login: info.Login,
Name: info.Name,
Email: info.Email,
IsGrafanaAdmin: info.IsGrafanaAdmin,
AuthenticatedBy: info.AuthModule,
AuthID: info.AuthId,
Groups: info.Groups,
ClientParams: authn.ClientParams{
SyncUser: true,
SyncTeams: true,
EnableDisabledUsers: true,
FetchSyncedUser: true,
SyncPermissions: true,
SyncOrgRoles: !c.cfg.LDAPSkipOrgRoleSync,
AllowSignUp: c.cfg.LDAPAllowSignup,
LookUpParams: login.UserLookupParams{
Login: &info.Login,
Email: &info.Email,
},
},
}
// The ldap service is not aware of the internal state of the user. Fetching the user
// from the store to know if that user is disabled or not, is almost as costly as
// running an update systematically. We are setting IsDisabled to true so that the
// EnableDisabledUserHook force-enable that user.
id.IsDisabled = true
return id
}