add simple LDAP authentification to the config and login page

This commit is contained in:
Anton Markelov 2017-03-27 14:53:00 +10:00
parent 8e8e7b58d9
commit 308c1e64ef
4 changed files with 141 additions and 17 deletions

View File

@ -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{

View File

@ -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;

View File

@ -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"`
}

View File

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