mirror of
https://github.com/semaphoreui/semaphore.git
synced 2024-11-23 20:35:24 +01:00
add simple LDAP authentification to the config and login page
This commit is contained in:
parent
8e8e7b58d9
commit
308c1e64ef
144
api/login.go
144
api/login.go
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strings"
|
||||
@ -13,8 +15,91 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
sq "github.com/masterminds/squirrel"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/ldap.v2"
|
||||
)
|
||||
|
||||
func ldapAuthentication(auth, password string) (error, models.User) {
|
||||
|
||||
if util.Config.LdapEnable != true {
|
||||
return fmt.Errorf("LDAP not configured"), models.User{}
|
||||
}
|
||||
|
||||
bindusername := util.Config.LdapBindDN
|
||||
bindpassword := util.Config.LdapBindPassword
|
||||
|
||||
l, err := ldap.Dial("tcp", util.Config.LdapServer)
|
||||
if err != nil {
|
||||
return err, models.User{}
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// Reconnect with TLS if needed
|
||||
if util.Config.LdapNeedTLS == true {
|
||||
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
return err, models.User{}
|
||||
}
|
||||
}
|
||||
|
||||
// First bind with a read only user
|
||||
err = l.Bind(bindusername, bindpassword)
|
||||
if err != nil {
|
||||
return err, models.User{}
|
||||
}
|
||||
|
||||
// Search for the given username
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
util.Config.LdapSearchDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf(util.Config.LdapSearchFilter, auth),
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
sr, err := l.Search(searchRequest)
|
||||
if err != nil {
|
||||
return err, models.User{}
|
||||
}
|
||||
|
||||
if len(sr.Entries) != 1 {
|
||||
return fmt.Errorf("User does not exist or too many entries returned"), models.User{}
|
||||
}
|
||||
|
||||
// Bind as the user to verify their password
|
||||
userdn := sr.Entries[0].DN
|
||||
err = l.Bind(userdn, password)
|
||||
if err != nil {
|
||||
return err, models.User{}
|
||||
}
|
||||
|
||||
// Get user info and ensure authentication in case LDAP supports unauthenticated bind
|
||||
searchRequest = ldap.NewSearchRequest(
|
||||
util.Config.LdapSearchDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf(util.Config.LdapSearchFilter, auth),
|
||||
[]string{"dn", "mail", "uid", "cn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
sr, err = l.Search(searchRequest)
|
||||
if err != nil {
|
||||
return err, models.User{}
|
||||
}
|
||||
|
||||
ldapUser := models.User{
|
||||
Username: sr.Entries[0].GetAttributeValue("uid"),
|
||||
Created: time.Now(),
|
||||
Name: sr.Entries[0].GetAttributeValue("cn"),
|
||||
Email: sr.Entries[0].GetAttributeValue("mail"),
|
||||
External: true,
|
||||
Alert: false,
|
||||
}
|
||||
|
||||
println("User " + ldapUser.Name + " with email " + ldapUser.Email + " authorized via LDAP correctly")
|
||||
return nil, ldapUser
|
||||
|
||||
}
|
||||
|
||||
func login(c *gin.Context) {
|
||||
var login struct {
|
||||
Auth string `json:"auth" binding:"required"`
|
||||
@ -27,31 +112,58 @@ func login(c *gin.Context) {
|
||||
|
||||
login.Auth = strings.ToLower(login.Auth)
|
||||
|
||||
ldapErr, ldapUser := ldapAuthentication(login.Auth, login.Password)
|
||||
|
||||
if util.Config.LdapEnable == true && ldapErr != nil {
|
||||
println(ldapErr.Error())
|
||||
}
|
||||
|
||||
q := sq.Select("*").
|
||||
From("user")
|
||||
|
||||
_, err := mail.ParseAddress(login.Auth)
|
||||
if err == nil {
|
||||
q = q.Where("email=?", login.Auth)
|
||||
} else {
|
||||
q = q.Where("username=?", login.Auth)
|
||||
}
|
||||
|
||||
query, args, _ := q.ToSql()
|
||||
|
||||
var user models.User
|
||||
if err := database.Mysql.SelectOne(&user, query, args...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if ldapErr != nil {
|
||||
// Perform normal authorization
|
||||
_, err := mail.ParseAddress(login.Auth)
|
||||
if err == nil {
|
||||
q = q.Where("email=?", login.Auth)
|
||||
} else {
|
||||
q = q.Where("username=?", login.Auth)
|
||||
}
|
||||
|
||||
query, args, _ := q.ToSql()
|
||||
|
||||
if err := database.Mysql.SelectOne(&user, query, args...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
}
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(login.Password)); err != nil {
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Check if that user already exist in database
|
||||
q = q.Where("username=? and external=true", ldapUser.Username)
|
||||
|
||||
panic(err)
|
||||
}
|
||||
query, args, _ := q.ToSql()
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(login.Password)); err != nil {
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
if err := database.Mysql.SelectOne(&user, query, args...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
//Create new user
|
||||
user = ldapUser
|
||||
if err := database.Mysql.Insert(&user); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
session := models.Session{
|
||||
|
@ -1,2 +1,4 @@
|
||||
ALTER TABLE user ADD alert BOOLEAN NOT NULL AFTER password;
|
||||
ALTER TABLE project ADD alert BOOLEAN NOT NULL AFTER name;
|
||||
|
||||
ALTER TABLE user ADD external BOOLEAN NOT NULL AFTER password;
|
@ -13,6 +13,7 @@ type User struct {
|
||||
Name string `db:"name" json:"name" binding:"required"`
|
||||
Email string `db:"email" json:"email" binding:"required"`
|
||||
Password string `db:"password" json:"-"`
|
||||
External bool `db:"external" json:"external"`
|
||||
Alert bool `db:"alert" json:"alert"`
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,15 @@ type configType struct {
|
||||
|
||||
//web host
|
||||
WebHost string `json:"web_host"`
|
||||
|
||||
//ldap settings
|
||||
LdapEnable bool `json:"ldap_enable"`
|
||||
LdapBindDN string `json:"ldap_binddn"`
|
||||
LdapBindPassword string `json:"ldap_bindpassword"`
|
||||
LdapServer string `json:"ldap_server"`
|
||||
LdapNeedTLS bool `json:"ldap_needtls"`
|
||||
LdapSearchDN string `json:"ldap_searchdn"`
|
||||
LdapSearchFilter string `json:"ldap_searchfilter"`
|
||||
}
|
||||
|
||||
var Config *configType
|
||||
|
Loading…
Reference in New Issue
Block a user