|
@@ -29,18 +29,17 @@ type IConnection interface {
|
|
|
// IServer is interface for LDAP authorization
|
|
// IServer is interface for LDAP authorization
|
|
|
type IServer interface {
|
|
type IServer interface {
|
|
|
Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
|
Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
|
|
- Add(string, map[string][]string) error
|
|
|
|
|
- Remove(string) error
|
|
|
|
|
Users([]string) ([]*models.ExternalUserInfo, error)
|
|
Users([]string) ([]*models.ExternalUserInfo, error)
|
|
|
ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error)
|
|
ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error)
|
|
|
|
|
+ InitialBind(string, string) error
|
|
|
Dial() error
|
|
Dial() error
|
|
|
Close()
|
|
Close()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Server is basic struct of LDAP authorization
|
|
// Server is basic struct of LDAP authorization
|
|
|
type Server struct {
|
|
type Server struct {
|
|
|
- config *ServerConfig
|
|
|
|
|
- connection IConnection
|
|
|
|
|
|
|
+ Config *ServerConfig
|
|
|
|
|
+ Connection IConnection
|
|
|
requireSecondBind bool
|
|
requireSecondBind bool
|
|
|
log log.Logger
|
|
log log.Logger
|
|
|
}
|
|
}
|
|
@@ -49,7 +48,6 @@ var (
|
|
|
|
|
|
|
|
// ErrInvalidCredentials is returned if username and password do not match
|
|
// ErrInvalidCredentials is returned if username and password do not match
|
|
|
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
|
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
|
|
- ErrLDAPUserNotFound = errors.New("LDAP user not found")
|
|
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
var dial = func(network, addr string) (IConnection, error) {
|
|
var dial = func(network, addr string) (IConnection, error) {
|
|
@@ -59,7 +57,7 @@ var dial = func(network, addr string) (IConnection, error) {
|
|
|
// New creates the new LDAP auth
|
|
// New creates the new LDAP auth
|
|
|
func New(config *ServerConfig) IServer {
|
|
func New(config *ServerConfig) IServer {
|
|
|
return &Server{
|
|
return &Server{
|
|
|
- config: config,
|
|
|
|
|
|
|
+ Config: config,
|
|
|
log: log.New("ldap"),
|
|
log: log.New("ldap"),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -68,9 +66,9 @@ func New(config *ServerConfig) IServer {
|
|
|
func (server *Server) Dial() error {
|
|
func (server *Server) Dial() error {
|
|
|
var err error
|
|
var err error
|
|
|
var certPool *x509.CertPool
|
|
var certPool *x509.CertPool
|
|
|
- if server.config.RootCACert != "" {
|
|
|
|
|
|
|
+ if server.Config.RootCACert != "" {
|
|
|
certPool = x509.NewCertPool()
|
|
certPool = x509.NewCertPool()
|
|
|
- for _, caCertFile := range strings.Split(server.config.RootCACert, " ") {
|
|
|
|
|
|
|
+ for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
|
|
|
pem, err := ioutil.ReadFile(caCertFile)
|
|
pem, err := ioutil.ReadFile(caCertFile)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
@@ -81,35 +79,35 @@ func (server *Server) Dial() error {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
var clientCert tls.Certificate
|
|
var clientCert tls.Certificate
|
|
|
- if server.config.ClientCert != "" && server.config.ClientKey != "" {
|
|
|
|
|
- clientCert, err = tls.LoadX509KeyPair(server.config.ClientCert, server.config.ClientKey)
|
|
|
|
|
|
|
+ if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
|
|
|
|
|
+ clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- for _, host := range strings.Split(server.config.Host, " ") {
|
|
|
|
|
- address := fmt.Sprintf("%s:%d", host, server.config.Port)
|
|
|
|
|
- if server.config.UseSSL {
|
|
|
|
|
|
|
+ for _, host := range strings.Split(server.Config.Host, " ") {
|
|
|
|
|
+ address := fmt.Sprintf("%s:%d", host, server.Config.Port)
|
|
|
|
|
+ if server.Config.UseSSL {
|
|
|
tlsCfg := &tls.Config{
|
|
tlsCfg := &tls.Config{
|
|
|
- InsecureSkipVerify: server.config.SkipVerifySSL,
|
|
|
|
|
|
|
+ InsecureSkipVerify: server.Config.SkipVerifySSL,
|
|
|
ServerName: host,
|
|
ServerName: host,
|
|
|
RootCAs: certPool,
|
|
RootCAs: certPool,
|
|
|
}
|
|
}
|
|
|
if len(clientCert.Certificate) > 0 {
|
|
if len(clientCert.Certificate) > 0 {
|
|
|
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
|
|
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
|
|
|
}
|
|
}
|
|
|
- if server.config.StartTLS {
|
|
|
|
|
- server.connection, err = dial("tcp", address)
|
|
|
|
|
|
|
+ if server.Config.StartTLS {
|
|
|
|
|
+ server.Connection, err = dial("tcp", address)
|
|
|
if err == nil {
|
|
if err == nil {
|
|
|
- if err = server.connection.StartTLS(tlsCfg); err == nil {
|
|
|
|
|
|
|
+ if err = server.Connection.StartTLS(tlsCfg); err == nil {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- server.connection, err = ldap.DialTLS("tcp", address, tlsCfg)
|
|
|
|
|
|
|
+ server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg)
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- server.connection, err = dial("tcp", address)
|
|
|
|
|
|
|
+ server.Connection, err = dial("tcp", address)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
if err == nil {
|
|
@@ -121,16 +119,16 @@ func (server *Server) Dial() error {
|
|
|
|
|
|
|
|
// Close closes the LDAP connection
|
|
// Close closes the LDAP connection
|
|
|
func (server *Server) Close() {
|
|
func (server *Server) Close() {
|
|
|
- server.connection.Close()
|
|
|
|
|
|
|
+ server.Connection.Close()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Log in user by searching and serializing it
|
|
|
|
|
|
|
+// Login user by searching and serializing it
|
|
|
func (server *Server) Login(query *models.LoginUserQuery) (
|
|
func (server *Server) Login(query *models.LoginUserQuery) (
|
|
|
*models.ExternalUserInfo, error,
|
|
*models.ExternalUserInfo, error,
|
|
|
) {
|
|
) {
|
|
|
|
|
|
|
|
// Perform initial authentication
|
|
// Perform initial authentication
|
|
|
- err := server.initialBind(query.Username, query.Password)
|
|
|
|
|
|
|
+ err := server.InitialBind(query.Username, query.Password)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
@@ -160,56 +158,6 @@ func (server *Server) Login(query *models.LoginUserQuery) (
|
|
|
return user, nil
|
|
return user, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Add adds stuff to LDAP
|
|
|
|
|
-func (server *Server) Add(dn string, values map[string][]string) error {
|
|
|
|
|
- err := server.initialBind(
|
|
|
|
|
- server.config.BindDN,
|
|
|
|
|
- server.config.BindPassword,
|
|
|
|
|
- )
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- attributes := make([]ldap.Attribute, 0)
|
|
|
|
|
- for key, value := range values {
|
|
|
|
|
- attributes = append(attributes, ldap.Attribute{
|
|
|
|
|
- Type: key,
|
|
|
|
|
- Vals: value,
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- request := &ldap.AddRequest{
|
|
|
|
|
- DN: dn,
|
|
|
|
|
- Attributes: attributes,
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- err = server.connection.Add(request)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// Remove removes stuff from LDAP
|
|
|
|
|
-func (server *Server) Remove(dn string) error {
|
|
|
|
|
- err := server.initialBind(
|
|
|
|
|
- server.config.BindDN,
|
|
|
|
|
- server.config.BindPassword,
|
|
|
|
|
- )
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- request := ldap.NewDelRequest(dn, nil)
|
|
|
|
|
- err = server.connection.Del(request)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// Users gets LDAP users
|
|
// Users gets LDAP users
|
|
|
func (server *Server) Users(logins []string) (
|
|
func (server *Server) Users(logins []string) (
|
|
|
[]*models.ExternalUserInfo,
|
|
[]*models.ExternalUserInfo,
|
|
@@ -217,10 +165,10 @@ func (server *Server) Users(logins []string) (
|
|
|
) {
|
|
) {
|
|
|
var result *ldap.SearchResult
|
|
var result *ldap.SearchResult
|
|
|
var err error
|
|
var err error
|
|
|
- var config = server.config
|
|
|
|
|
|
|
+ var Config = server.Config
|
|
|
|
|
|
|
|
- for _, base := range config.SearchBaseDNs {
|
|
|
|
|
- result, err = server.connection.Search(
|
|
|
|
|
|
|
+ for _, base := range Config.SearchBaseDNs {
|
|
|
|
|
+ result, err = server.Connection.Search(
|
|
|
server.getSearchRequest(base, logins),
|
|
server.getSearchRequest(base, logins),
|
|
|
)
|
|
)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -254,7 +202,7 @@ func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserIn
|
|
|
// If there are no ldap group mappings access is true
|
|
// If there are no ldap group mappings access is true
|
|
|
// otherwise a single group must match
|
|
// otherwise a single group must match
|
|
|
func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
|
|
func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
|
|
|
- if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 {
|
|
|
|
|
|
|
+ if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
|
|
|
server.log.Error(
|
|
server.log.Error(
|
|
|
"user does not belong in any of the specified LDAP groups",
|
|
"user does not belong in any of the specified LDAP groups",
|
|
|
"username", user.Login,
|
|
"username", user.Login,
|
|
@@ -301,7 +249,7 @@ func (server *Server) getSearchRequest(
|
|
|
) *ldap.SearchRequest {
|
|
) *ldap.SearchRequest {
|
|
|
attributes := []string{}
|
|
attributes := []string{}
|
|
|
|
|
|
|
|
- inputs := server.config.Attr
|
|
|
|
|
|
|
+ inputs := server.Config.Attr
|
|
|
attributes = appendIfNotEmpty(
|
|
attributes = appendIfNotEmpty(
|
|
|
attributes,
|
|
attributes,
|
|
|
inputs.Username,
|
|
inputs.Username,
|
|
@@ -314,7 +262,7 @@ func (server *Server) getSearchRequest(
|
|
|
search := ""
|
|
search := ""
|
|
|
for _, login := range logins {
|
|
for _, login := range logins {
|
|
|
query := strings.Replace(
|
|
query := strings.Replace(
|
|
|
- server.config.SearchFilter,
|
|
|
|
|
|
|
+ server.Config.SearchFilter,
|
|
|
"%s", ldap.EscapeFilter(login),
|
|
"%s", ldap.EscapeFilter(login),
|
|
|
-1,
|
|
-1,
|
|
|
)
|
|
)
|
|
@@ -347,7 +295,7 @@ func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo
|
|
|
OrgRoles: map[int64]models.RoleType{},
|
|
OrgRoles: map[int64]models.RoleType{},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for _, group := range server.config.Groups {
|
|
|
|
|
|
|
+ for _, group := range server.Config.Groups {
|
|
|
// only use the first match for each org
|
|
// only use the first match for each org
|
|
|
if extUser.OrgRoles[group.OrgId] != "" {
|
|
if extUser.OrgRoles[group.OrgId] != "" {
|
|
|
continue
|
|
continue
|
|
@@ -366,15 +314,15 @@ func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo
|
|
|
|
|
|
|
|
func (server *Server) serverBind() error {
|
|
func (server *Server) serverBind() error {
|
|
|
bindFn := func() error {
|
|
bindFn := func() error {
|
|
|
- return server.connection.Bind(
|
|
|
|
|
- server.config.BindDN,
|
|
|
|
|
- server.config.BindPassword,
|
|
|
|
|
|
|
+ return server.Connection.Bind(
|
|
|
|
|
+ server.Config.BindDN,
|
|
|
|
|
+ server.Config.BindPassword,
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if server.config.BindPassword == "" {
|
|
|
|
|
|
|
+ if server.Config.BindPassword == "" {
|
|
|
bindFn = func() error {
|
|
bindFn = func() error {
|
|
|
- return server.connection.UnauthenticatedBind(server.config.BindDN)
|
|
|
|
|
|
|
+ return server.Connection.UnauthenticatedBind(server.Config.BindDN)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -397,7 +345,7 @@ func (server *Server) secondBind(
|
|
|
user *models.ExternalUserInfo,
|
|
user *models.ExternalUserInfo,
|
|
|
userPassword string,
|
|
userPassword string,
|
|
|
) error {
|
|
) error {
|
|
|
- err := server.connection.Bind(user.AuthId, userPassword)
|
|
|
|
|
|
|
+ err := server.Connection.Bind(user.AuthId, userPassword)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
server.log.Info("Second bind failed", "error", err)
|
|
server.log.Info("Second bind failed", "error", err)
|
|
|
|
|
|
|
@@ -412,24 +360,25 @@ func (server *Server) secondBind(
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (server *Server) initialBind(username, userPassword string) error {
|
|
|
|
|
- if server.config.BindPassword != "" || server.config.BindDN == "" {
|
|
|
|
|
- userPassword = server.config.BindPassword
|
|
|
|
|
|
|
+// InitialBind intiates first bind to LDAP server
|
|
|
|
|
+func (server *Server) InitialBind(username, userPassword string) error {
|
|
|
|
|
+ if server.Config.BindPassword != "" || server.Config.BindDN == "" {
|
|
|
|
|
+ userPassword = server.Config.BindPassword
|
|
|
server.requireSecondBind = true
|
|
server.requireSecondBind = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- bindPath := server.config.BindDN
|
|
|
|
|
|
|
+ bindPath := server.Config.BindDN
|
|
|
if strings.Contains(bindPath, "%s") {
|
|
if strings.Contains(bindPath, "%s") {
|
|
|
- bindPath = fmt.Sprintf(server.config.BindDN, username)
|
|
|
|
|
|
|
+ bindPath = fmt.Sprintf(server.Config.BindDN, username)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bindFn := func() error {
|
|
bindFn := func() error {
|
|
|
- return server.connection.Bind(bindPath, userPassword)
|
|
|
|
|
|
|
+ return server.Connection.Bind(bindPath, userPassword)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if userPassword == "" {
|
|
if userPassword == "" {
|
|
|
bindFn = func() error {
|
|
bindFn = func() error {
|
|
|
- return server.connection.UnauthenticatedBind(bindPath)
|
|
|
|
|
|
|
+ return server.Connection.UnauthenticatedBind(bindPath)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -451,16 +400,16 @@ func (server *Server) initialBind(username, userPassword string) error {
|
|
|
func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
|
|
func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
|
|
|
var memberOf []string
|
|
var memberOf []string
|
|
|
|
|
|
|
|
- for _, groupSearchBase := range server.config.GroupSearchBaseDNs {
|
|
|
|
|
|
|
+ for _, groupSearchBase := range server.Config.GroupSearchBaseDNs {
|
|
|
var filterReplace string
|
|
var filterReplace string
|
|
|
- if server.config.GroupSearchFilterUserAttribute == "" {
|
|
|
|
|
- filterReplace = getLDAPAttr(server.config.Attr.Username, searchResult)
|
|
|
|
|
|
|
+ if server.Config.GroupSearchFilterUserAttribute == "" {
|
|
|
|
|
+ filterReplace = getLDAPAttr(server.Config.Attr.Username, searchResult)
|
|
|
} else {
|
|
} else {
|
|
|
- filterReplace = getLDAPAttr(server.config.GroupSearchFilterUserAttribute, searchResult)
|
|
|
|
|
|
|
+ filterReplace = getLDAPAttr(server.Config.GroupSearchFilterUserAttribute, searchResult)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
filter := strings.Replace(
|
|
filter := strings.Replace(
|
|
|
- server.config.GroupSearchFilter, "%s",
|
|
|
|
|
|
|
+ server.Config.GroupSearchFilter, "%s",
|
|
|
ldap.EscapeFilter(filterReplace),
|
|
ldap.EscapeFilter(filterReplace),
|
|
|
-1,
|
|
-1,
|
|
|
)
|
|
)
|
|
@@ -468,7 +417,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string
|
|
|
server.log.Info("Searching for user's groups", "filter", filter)
|
|
server.log.Info("Searching for user's groups", "filter", filter)
|
|
|
|
|
|
|
|
// support old way of reading settings
|
|
// support old way of reading settings
|
|
|
- groupIDAttribute := server.config.Attr.MemberOf
|
|
|
|
|
|
|
+ groupIDAttribute := server.Config.Attr.MemberOf
|
|
|
// but prefer dn attribute if default settings are used
|
|
// but prefer dn attribute if default settings are used
|
|
|
if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
|
|
if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
|
|
|
groupIDAttribute = "dn"
|
|
groupIDAttribute = "dn"
|
|
@@ -482,7 +431,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string
|
|
|
Filter: filter,
|
|
Filter: filter,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- groupSearchResult, err := server.connection.Search(&groupSearchReq)
|
|
|
|
|
|
|
+ groupSearchResult, err := server.Connection.Search(&groupSearchReq)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
@@ -518,22 +467,22 @@ func (server *Server) serializeUsers(
|
|
|
index,
|
|
index,
|
|
|
),
|
|
),
|
|
|
LastName: getLDAPAttrN(
|
|
LastName: getLDAPAttrN(
|
|
|
- server.config.Attr.Surname,
|
|
|
|
|
|
|
+ server.Config.Attr.Surname,
|
|
|
users,
|
|
users,
|
|
|
index,
|
|
index,
|
|
|
),
|
|
),
|
|
|
FirstName: getLDAPAttrN(
|
|
FirstName: getLDAPAttrN(
|
|
|
- server.config.Attr.Name,
|
|
|
|
|
|
|
+ server.Config.Attr.Name,
|
|
|
users,
|
|
users,
|
|
|
index,
|
|
index,
|
|
|
),
|
|
),
|
|
|
Username: getLDAPAttrN(
|
|
Username: getLDAPAttrN(
|
|
|
- server.config.Attr.Username,
|
|
|
|
|
|
|
+ server.Config.Attr.Username,
|
|
|
users,
|
|
users,
|
|
|
index,
|
|
index,
|
|
|
),
|
|
),
|
|
|
Email: getLDAPAttrN(
|
|
Email: getLDAPAttrN(
|
|
|
- server.config.Attr.Email,
|
|
|
|
|
|
|
+ server.Config.Attr.Email,
|
|
|
users,
|
|
users,
|
|
|
index,
|
|
index,
|
|
|
),
|
|
),
|
|
@@ -553,8 +502,8 @@ func (server *Server) serializeUsers(
|
|
|
func (server *Server) getMemberOf(search *ldap.SearchResult) (
|
|
func (server *Server) getMemberOf(search *ldap.SearchResult) (
|
|
|
[]string, error,
|
|
[]string, error,
|
|
|
) {
|
|
) {
|
|
|
- if server.config.GroupSearchFilter == "" {
|
|
|
|
|
- memberOf := getLDAPAttrArray(server.config.Attr.MemberOf, search)
|
|
|
|
|
|
|
+ if server.Config.GroupSearchFilter == "" {
|
|
|
|
|
+ memberOf := getLDAPAttrArray(server.Config.Attr.MemberOf, search)
|
|
|
|
|
|
|
|
return memberOf, nil
|
|
return memberOf, nil
|
|
|
}
|
|
}
|