Selaa lähdekoodia

Initial work on ldap support, #1450

Torkel Ödegaard 10 vuotta sitten
vanhempi
commit
eb793f7feb

+ 12 - 0
conf/defaults.ini

@@ -174,6 +174,18 @@ header_name = X-WEBAUTH-USER
 header_property = username
 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 ##########################
 [log]
 # Either "console", "file", default is "console"

+ 1 - 1
pkg/api/api.go

@@ -19,7 +19,7 @@ func Register(r *macaron.Macaron) {
 	// not logged in views
 	r.Get("/", reqSignedIn, Index)
 	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", LoginView)
 

+ 0 - 0
pkg/api/ldap/ldap.go


+ 56 - 0
pkg/api/ldapauth/ldapauth.go

@@ -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
+	// }
+}

+ 35 - 13
pkg/api/login.go

@@ -4,6 +4,8 @@ import (
 	"net/url"
 
 	"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/log"
 	"github.com/grafana/grafana/pkg/metrics"
@@ -86,21 +88,28 @@ func LoginApiPing(c *middleware.Context) {
 	c.JsonOK("Logged in")
 }
 
-func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
-	userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.User}
-	err := bus.Dispatch(&userQuery)
-
-	if err != nil {
-		c.JsonApiErr(401, "Invalid username or password", err)
-		return
+func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
+	sourcesQuery := auth.GetAuthSourcesQuery{}
+	if err := bus.Dispatch(&sourcesQuery); err != nil {
+		return ApiError(500, "Could not get login sources", err)
 	}
 
-	user := userQuery.Result
+	var err error
+	var user *m.User
 
-	passwordHashed := util.EncodePassword(cmd.Password, user.Salt)
-	if passwordHashed != user.Password {
-		c.JsonApiErr(401, "Invalid username or password", err)
-		return
+	for _, authSource := range sourcesQuery.Sources {
+		user, err = authSource.AuthenticateUser(cmd.User, cmd.Password)
+		if err == nil {
+			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)
@@ -116,7 +125,20 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
 
 	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) {

+ 73 - 0
pkg/auth/auth.go

@@ -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
+}

+ 8 - 0
pkg/setting/setting.go

@@ -114,6 +114,10 @@ var (
 
 	ReportingEnabled  bool
 	GoogleAnalyticsId string
+
+	// LDAP
+	LdapEnabled bool
+	LdapUrls    []string
 )
 
 type CommandLineArgs struct {
@@ -406,6 +410,10 @@ func NewConfigContext(args *CommandLineArgs) {
 	ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
 	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()
 }
 

+ 19 - 0
pkg/setting/setting_ldap.go

@@ -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
+}