mirror of
https://github.com/grafana/grafana.git
synced 2025-09-27 05:14:12 +08:00
Initial work on ldap support, #1450
This commit is contained in:
@ -174,6 +174,18 @@ header_name = X-WEBAUTH-USER
|
|||||||
header_property = username
|
header_property = username
|
||||||
auto_sign_up = true
|
auto_sign_up = true
|
||||||
|
|
||||||
|
#################################### Auth LDAP ##########################
|
||||||
|
[auth.ldap]
|
||||||
|
enabled = true
|
||||||
|
hosts = ldap://localhost.com:389
|
||||||
|
use_ssl = false
|
||||||
|
base_dn = dc=grafana,dc=org
|
||||||
|
bind_path = cn=%username%,dc=grafana,dc=org
|
||||||
|
attr_username = cn
|
||||||
|
attr_name = cn
|
||||||
|
attr_surname = sn
|
||||||
|
attr_email = email
|
||||||
|
|
||||||
#################################### Logging ##########################
|
#################################### Logging ##########################
|
||||||
[log]
|
[log]
|
||||||
# Either "console", "file", default is "console"
|
# Either "console", "file", default is "console"
|
||||||
|
@ -19,7 +19,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
// not logged in views
|
// not logged in views
|
||||||
r.Get("/", reqSignedIn, Index)
|
r.Get("/", reqSignedIn, Index)
|
||||||
r.Get("/logout", Logout)
|
r.Get("/logout", Logout)
|
||||||
r.Post("/login", bind(dtos.LoginCommand{}), LoginPost)
|
r.Post("/login", bind(dtos.LoginCommand{}), wrap(LoginPost))
|
||||||
r.Get("/login/:name", OAuthLogin)
|
r.Get("/login/:name", OAuthLogin)
|
||||||
r.Get("/login", LoginView)
|
r.Get("/login", LoginView)
|
||||||
|
|
||||||
|
56
pkg/api/ldapauth/ldapauth.go
Normal file
56
pkg/api/ldapauth/ldapauth.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package ldapauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gogits/gogs/modules/ldap"
|
||||||
|
"github.com/grafana/grafana/pkg/log"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Login(username, password string) error {
|
||||||
|
url, err := url.Parse(setting.LdapUrls[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Host: %v", url.Host)
|
||||||
|
conn, err := ldap.Dial("tcp", url.Host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
bindFormat := "cn=%s,dc=grafana,dc=org"
|
||||||
|
|
||||||
|
nx := fmt.Sprintf(bindFormat, username)
|
||||||
|
err = conn.Bind(nx, password)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if ldapErr, ok := err.(*ldap.Error); ok {
|
||||||
|
if ldapErr.ResultCode == 49 {
|
||||||
|
return ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// search := ldap.NewSearchRequest(url.Path,
|
||||||
|
// ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
// fmt.Sprintf(ls.Filter, name),
|
||||||
|
// []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
|
||||||
|
// nil)
|
||||||
|
// sr, err := l.Search(search)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Debug("LDAP Authen OK but not in filter %s", name)
|
||||||
|
// return "", "", "", "", false
|
||||||
|
// }
|
||||||
|
}
|
@ -4,6 +4,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/api/ldapauth"
|
||||||
|
"github.com/grafana/grafana/pkg/auth"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
@ -86,21 +88,28 @@ func LoginApiPing(c *middleware.Context) {
|
|||||||
c.JsonOK("Logged in")
|
c.JsonOK("Logged in")
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
|
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
|
||||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.User}
|
sourcesQuery := auth.GetAuthSourcesQuery{}
|
||||||
err := bus.Dispatch(&userQuery)
|
if err := bus.Dispatch(&sourcesQuery); err != nil {
|
||||||
|
return ApiError(500, "Could not get login sources", err)
|
||||||
if err != nil {
|
|
||||||
c.JsonApiErr(401, "Invalid username or password", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user := userQuery.Result
|
var err error
|
||||||
|
var user *m.User
|
||||||
|
|
||||||
passwordHashed := util.EncodePassword(cmd.Password, user.Salt)
|
for _, authSource := range sourcesQuery.Sources {
|
||||||
if passwordHashed != user.Password {
|
user, err = authSource.AuthenticateUser(cmd.User, cmd.Password)
|
||||||
c.JsonApiErr(401, "Invalid username or password", err)
|
if err == nil {
|
||||||
return
|
break
|
||||||
|
}
|
||||||
|
// handle non invalid credentials error, otherwise try next auth source
|
||||||
|
if err != auth.ErrInvalidCredentials {
|
||||||
|
return ApiError(500, "Error while trying to authenticate user", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ApiError(401, "Invalid username or password", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
loginUserWithUser(user, c)
|
loginUserWithUser(user, c)
|
||||||
@ -116,7 +125,20 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
|
|||||||
|
|
||||||
metrics.M_Api_Login_Post.Inc(1)
|
metrics.M_Api_Login_Post.Inc(1)
|
||||||
|
|
||||||
c.JSON(200, result)
|
return Json(200, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoginUsingLdap(c *middleware.Context, cmd dtos.LoginCommand) Response {
|
||||||
|
err := ldapauth.Login(cmd.User, cmd.Password)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == ldapauth.ErrInvalidCredentials {
|
||||||
|
return ApiError(401, "Invalid username or password", err)
|
||||||
|
}
|
||||||
|
return ApiError(500, "Ldap login failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Empty(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginUserWithUser(user *m.User, c *middleware.Context) {
|
func loginUserWithUser(user *m.User, c *middleware.Context) {
|
||||||
|
73
pkg/auth/auth.go
Normal file
73
pkg/auth/auth.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginSettings struct {
|
||||||
|
LdapEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type LdapFilterToOrg struct {
|
||||||
|
Filter string
|
||||||
|
OrgId int
|
||||||
|
OrgRole string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LdapSettings struct {
|
||||||
|
Enabled bool
|
||||||
|
Hosts []string
|
||||||
|
UseSSL bool
|
||||||
|
BindDN string
|
||||||
|
AttrUsername string
|
||||||
|
AttrName string
|
||||||
|
AttrSurname string
|
||||||
|
AttrMail string
|
||||||
|
Filters []LdapFilterToOrg
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthSource interface {
|
||||||
|
AuthenticateUser(username, password string) (*m.User, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAuthSourcesQuery struct {
|
||||||
|
Sources []AuthSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bus.AddHandler("auth", GetAuthSources)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAuthSources(query *GetAuthSourcesQuery) error {
|
||||||
|
query.Sources = []AuthSource{&GrafanaDBAuthSource{}}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrafanaDBAuthSource struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GrafanaDBAuthSource) AuthenticateUser(username, password string) (*m.User, error) {
|
||||||
|
userQuery := m.GetUserByLoginQuery{LoginOrEmail: username}
|
||||||
|
err := bus.Dispatch(&userQuery)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
user := userQuery.Result
|
||||||
|
|
||||||
|
passwordHashed := util.EncodePassword(password, user.Salt)
|
||||||
|
if passwordHashed != user.Password {
|
||||||
|
return nil, ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
@ -114,6 +114,10 @@ var (
|
|||||||
|
|
||||||
ReportingEnabled bool
|
ReportingEnabled bool
|
||||||
GoogleAnalyticsId string
|
GoogleAnalyticsId string
|
||||||
|
|
||||||
|
// LDAP
|
||||||
|
LdapEnabled bool
|
||||||
|
LdapUrls []string
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandLineArgs struct {
|
type CommandLineArgs struct {
|
||||||
@ -406,6 +410,10 @@ func NewConfigContext(args *CommandLineArgs) {
|
|||||||
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
|
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
|
||||||
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
|
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
|
||||||
|
|
||||||
|
ldapSec := Cfg.Section("auth.ldap")
|
||||||
|
LdapEnabled = ldapSec.Key("enabled").MustBool(false)
|
||||||
|
LdapUrls = ldapSec.Key("urls").Strings(" ")
|
||||||
|
|
||||||
readSessionConfig()
|
readSessionConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
pkg/setting/setting_ldap.go
Normal file
19
pkg/setting/setting_ldap.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package setting
|
||||||
|
|
||||||
|
type LdapFilterToOrg struct {
|
||||||
|
Filter string
|
||||||
|
OrgId int
|
||||||
|
OrgRole string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LdapSettings struct {
|
||||||
|
Enabled bool
|
||||||
|
Hosts []string
|
||||||
|
UseSSL bool
|
||||||
|
BindDN string
|
||||||
|
AttrUsername string
|
||||||
|
AttrName string
|
||||||
|
AttrSurname string
|
||||||
|
AttrMail string
|
||||||
|
Filters []LdapFilterToOrg
|
||||||
|
}
|
Reference in New Issue
Block a user