Support LDAP bind auth

This commit is contained in:
cuigh 2017-11-13 18:16:16 +08:00
parent 32d66e3de6
commit d5fb909ffc
4 changed files with 108 additions and 93 deletions

View File

@ -193,7 +193,7 @@ func (b *userBiz) loginInternal(user *model.User, pwd string) error {
return nil
}
// TODO: support tls and bind auth
// TODO: support tls
func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error {
setting, err := Setting.Get()
if err != nil {
@ -211,11 +211,9 @@ func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error
defer l.Close()
// bind
//err = l.Bind(user.LoginName, pwd)
//err = l.Bind(setting.LDAP.BindDN, setting.LDAP.BindPassword) // bind auth
err = l.Bind(fmt.Sprintf(setting.LDAP.UserDN, user.LoginName), pwd) // simple auth
err = b.ldapBind(setting, l, user, pwd)
if err != nil {
log.Get("user").Error("Login by LDAP failed: ", err)
log.Get("user").Error("LDAP > Failed to bind: ", err)
return ErrIncorrectAuth
}
@ -225,28 +223,54 @@ func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error
}
// If user wasn't exist, we need create it
req := ldap.NewSearchRequest(
setting.LDAP.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(setting.LDAP.UserFilter, user.LoginName),
[]string{setting.LDAP.NameAttr, setting.LDAP.EmailAttr},
nil,
)
sr, err := l.Search(req)
entry, err := b.ldapSearchOne(setting, l, user.LoginName, setting.LDAP.NameAttr, setting.LDAP.EmailAttr)
if err != nil {
return err
}
if length := len(sr.Entries); length == 0 {
return ErrIncorrectAuth
} else if length > 1 {
return errors.New("Found more than one account when using LDAP authentication")
}
entry := sr.Entries[0]
user.Email = entry.GetAttributeValue(setting.LDAP.EmailAttr)
user.Name = entry.GetAttributeValue(setting.LDAP.NameAttr)
return b.Create(user, nil)
}
func (b *userBiz) ldapBind(setting *model.Setting, l *ldap.Conn, user *model.User, pwd string) (err error) {
if setting.LDAP.Authentication == 0 {
// simple auth
err = l.Bind(fmt.Sprintf(setting.LDAP.UserDN, user.LoginName), pwd)
} else {
// bind auth
err = l.Bind(setting.LDAP.BindDN, setting.LDAP.BindPassword)
if err == nil {
var entry *ldap.Entry
entry, err = b.ldapSearchOne(setting, l, user.LoginName, "cn")
if err == nil {
err = l.Bind(entry.DN, pwd)
}
}
}
return
}
func (b *userBiz) ldapSearchOne(setting *model.Setting, l *ldap.Conn, name string, attrs ...string) (entry *ldap.Entry, err error) {
filter := fmt.Sprintf(setting.LDAP.UserFilter, name)
req := ldap.NewSearchRequest(
setting.LDAP.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter, attrs, nil,
)
sr, err := l.Search(req)
if err != nil {
return nil, err
}
if length := len(sr.Entries); length == 0 {
return nil, errors.New("User not found with filter: " + filter)
} else if length > 1 {
return nil, errors.New("Found more than one account when searching user with filter: " + filter)
}
return sr.Entries[0], nil
}
// Identify authenticate user
func (b *userBiz) Identify(token string) (user web.User) {
do(func(d dao.Interface) {

View File

@ -1,46 +1,18 @@
package dao
import (
"sync"
"github.com/cuigh/auxo/errors"
"github.com/cuigh/auxo/util/lazy"
"github.com/cuigh/swirl/dao/mongo"
"github.com/cuigh/swirl/misc"
"github.com/cuigh/swirl/model"
)
var (
iface Interface
locker sync.Mutex
value = lazy.Value{New: create}
)
// Get return a dao instance according to DB_TYPE.
func Get() (Interface, error) {
if iface == nil {
locker.Lock()
defer locker.Unlock()
if iface == nil {
d, err := create()
if err != nil {
return nil, err
}
iface = d
}
}
return iface, nil
}
func create() (d Interface, err error) {
switch misc.Options.DBType {
case "", "mongo":
return mongo.New(misc.Options.DBAddress)
default:
err = errors.New("Unknown database type: " + misc.Options.DBType)
}
return
}
// Interface is the interface that wraps all dao methods.
type Interface interface {
RoleGet(id string) (*model.Role, error)
RoleList() (roles []*model.Role, err error)
@ -87,3 +59,22 @@ type Interface interface {
SettingGet() (setting *model.Setting, err error)
SettingUpdate(setting *model.Setting) error
}
// Get return a dao instance according to DB_TYPE.
func Get() (Interface, error) {
v, err := value.Get()
if err != nil {
return nil, err
}
return v.(Interface), nil
}
func create() (d interface{}, err error) {
switch misc.Options.DBType {
case "", "mongo":
return mongo.New(misc.Options.DBAddress)
default:
err = errors.New("Unknown database type: " + misc.Options.DBType)
}
return
}

View File

@ -10,7 +10,7 @@ type Setting struct {
Security int32 `bson:"security" json:"security,omitempty"` // 0-None/1-TLS/2-StartTLS
TLSCert string `bson:"tls_cert" json:"tls_cert,omitempty"` // TLS cert
TLSVerify bool `bson:"tls_verify" json:"tls_verify,omitempty"` // Verify cert
Authentication int32 `bson:"auth" json:"auth,omitempty"` // 0-Bind/1-Simple
Authentication int32 `bson:"auth" json:"auth,omitempty"` // 0-Simple/1-Bind
BindDN string `bson:"bind_dn" json:"bind_dn,omitempty"` // DN to bind with
BindPassword string `bson:"bind_pwd" json:"bind_pwd,omitempty"` // Bind DN password
BaseDN string `bson:"base_dn" json:"base_dn,omitempty"` // Base search path for users

View File

@ -39,6 +39,45 @@
<section class="section">
<div class="container">
<form method="post" data-form="ajax-json" data-message="The settings have been successfully modified">
<fieldset id="fs-zone">
<legend class="lead is-5 is-bordered">Time zone and language</legend>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Time zone</label>
</div>
<div class="field-body">
<div class="field has-addons">
<div class="control has-icons-left">
<input name="tz.name" value="{{ .Setting.TimeZone.Name }}" class="input" type="text" placeholder="Location name">
<div class="icon is-left">
<i class="fa fa-globe"></i>
</div>
</div>
<div class="control">
<input name="tz.offset" value="{{ .Setting.TimeZone.Offset }}" class="input" type="text" placeholder="Offset in seconds" data-type="integer">
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Language</label>
</div>
<div class="field-body">
<div class="control has-icons-left">
<div class="select">
<select name="lang">
{{ yield option(value="en", label="English", selected=.Setting.Language) }}
{{ yield option(value="zh", label="中文", selected=.Setting.Language) }}
</select>
</div>
<div class="icon is-left">
<i class="fa fa-language"></i>
</div>
</div>
</div>
</div>
</fieldset>
<fieldset id="fs-ldap">
<legend class="lead is-5 is-bordered">LDAP</legend>
<div class="field is-horizontal">
@ -87,7 +126,7 @@
<div class="field">
<div class="control">
{{ yield radio(id="ldap-auth-simple", name="ldap.auth", value="0", label="Simple", checked=.Setting.LDAP.Authentication) content}} data-type="integer"{{end}}
{{ yield radio(id="ldap-auth-bind", name="ldap.auth", value="1", label="Bind", checked=.Setting.LDAP.Authentication, disabled=true) content}} data-type="integer"{{end}}
{{ yield radio(id="ldap-auth-bind", name="ldap.auth", value="1", label="Bind", checked=.Setting.LDAP.Authentication) content}} data-type="integer"{{end}}
</div>
</div>
</div>
@ -99,14 +138,14 @@
<div class="field-body">
<div class="field">
<div class="control">
<input name="ldap.user_dn" value="{{ .Setting.LDAP.UserDN }}" class="input" type="text" placeholder="e.g. cn=%s,dc=xxx,dc=com">
<input name="ldap.user_dn" value="{{ .Setting.LDAP.UserDN }}" class="input" type="text" placeholder="User DN template for direct binding, e.g. cn=%s,dc=xxx,dc=com">
</div>
</div>
</div>
</div>
<div id="div-auth-bind" class="field is-horizontal"{{ if .Setting.LDAP.Authentication == 0 }} style="display: none" {{ end }}>
<div class="field-label is-normal">
<label class="label">Bind</label>
<label class="label">Bind account</label>
</div>
<div class="field-body">
<div class="field has-addons">
@ -134,7 +173,7 @@
<div class="field-body">
<div class="field">
<div class="control">
<input name="ldap.base_dn" value="{{ .Setting.LDAP.BaseDN }}" class="input" type="text" placeholder="e.g. dc=xxx,dc=com">
<input name="ldap.base_dn" value="{{ .Setting.LDAP.BaseDN }}" class="input" type="text" placeholder="Base DN for searching user, e.g. dc=xxx,dc=com">
</div>
</div>
</div>
@ -176,45 +215,6 @@
</div>
</div>
</fieldset>
<fieldset id="fs-ldap">
<legend class="lead is-5 is-bordered">Time zone and language</legend>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Time zone</label>
</div>
<div class="field-body">
<div class="field has-addons">
<div class="control has-icons-left">
<input name="tz.name" value="{{ .Setting.TimeZone.Name }}" class="input" type="text" placeholder="Location name">
<div class="icon is-left">
<i class="fa fa-globe"></i>
</div>
</div>
<div class="control">
<input name="tz.offset" value="{{ .Setting.TimeZone.Offset }}" class="input" type="text" placeholder="Offset in seconds" data-type="integer">
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Language</label>
</div>
<div class="field-body">
<div class="control has-icons-left">
<div class="select">
<select name="lang">
{{ yield option(value="en", label="English", selected=.Setting.Language) }}
{{ yield option(value="zh", label="中文", selected=.Setting.Language) }}
</select>
</div>
<div class="icon is-left">
<i class="fa fa-language"></i>
</div>
</div>
</div>
</div>
</fieldset>
<hr>
<div class="field">
<p class="control">