mirror of
https://github.com/cuigh/swirl
synced 2025-06-04 03:36:27 +00:00
Support LDAP bind auth
This commit is contained in:
parent
32d66e3de6
commit
d5fb909ffc
60
biz/user.go
60
biz/user.go
@ -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) {
|
||||
|
53
dao/dao.go
53
dao/dao.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user