mirror of
https://github.com/cuigh/swirl
synced 2025-06-26 18:16:50 +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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: support tls and bind auth
|
// TODO: support tls
|
||||||
func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error {
|
func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error {
|
||||||
setting, err := Setting.Get()
|
setting, err := Setting.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -211,11 +211,9 @@ func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error
|
|||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
// bind
|
// bind
|
||||||
//err = l.Bind(user.LoginName, pwd)
|
err = b.ldapBind(setting, l, user, 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
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Get("user").Error("Login by LDAP failed: ", err)
|
log.Get("user").Error("LDAP > Failed to bind: ", err)
|
||||||
return ErrIncorrectAuth
|
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
|
// If user wasn't exist, we need create it
|
||||||
req := ldap.NewSearchRequest(
|
entry, err := b.ldapSearchOne(setting, l, user.LoginName, setting.LDAP.NameAttr, setting.LDAP.EmailAttr)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.Email = entry.GetAttributeValue(setting.LDAP.EmailAttr)
|
||||||
user.Name = entry.GetAttributeValue(setting.LDAP.NameAttr)
|
user.Name = entry.GetAttributeValue(setting.LDAP.NameAttr)
|
||||||
return b.Create(user, nil)
|
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
|
// Identify authenticate user
|
||||||
func (b *userBiz) Identify(token string) (user web.User) {
|
func (b *userBiz) Identify(token string) (user web.User) {
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
|
|||||||
53
dao/dao.go
53
dao/dao.go
@ -1,46 +1,18 @@
|
|||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/cuigh/auxo/errors"
|
"github.com/cuigh/auxo/errors"
|
||||||
|
"github.com/cuigh/auxo/util/lazy"
|
||||||
"github.com/cuigh/swirl/dao/mongo"
|
"github.com/cuigh/swirl/dao/mongo"
|
||||||
"github.com/cuigh/swirl/misc"
|
"github.com/cuigh/swirl/misc"
|
||||||
"github.com/cuigh/swirl/model"
|
"github.com/cuigh/swirl/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
iface Interface
|
value = lazy.Value{New: create}
|
||||||
locker sync.Mutex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get return a dao instance according to DB_TYPE.
|
// Interface is the interface that wraps all dao methods.
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
RoleGet(id string) (*model.Role, error)
|
RoleGet(id string) (*model.Role, error)
|
||||||
RoleList() (roles []*model.Role, err error)
|
RoleList() (roles []*model.Role, err error)
|
||||||
@ -87,3 +59,22 @@ type Interface interface {
|
|||||||
SettingGet() (setting *model.Setting, err error)
|
SettingGet() (setting *model.Setting, err error)
|
||||||
SettingUpdate(setting *model.Setting) 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
|
Security int32 `bson:"security" json:"security,omitempty"` // 0-None/1-TLS/2-StartTLS
|
||||||
TLSCert string `bson:"tls_cert" json:"tls_cert,omitempty"` // TLS cert
|
TLSCert string `bson:"tls_cert" json:"tls_cert,omitempty"` // TLS cert
|
||||||
TLSVerify bool `bson:"tls_verify" json:"tls_verify,omitempty"` // Verify 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
|
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
|
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
|
BaseDN string `bson:"base_dn" json:"base_dn,omitempty"` // Base search path for users
|
||||||
|
|||||||
@ -39,6 +39,45 @@
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form method="post" data-form="ajax-json" data-message="The settings have been successfully modified">
|
<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">
|
<fieldset id="fs-ldap">
|
||||||
<legend class="lead is-5 is-bordered">LDAP</legend>
|
<legend class="lead is-5 is-bordered">LDAP</legend>
|
||||||
<div class="field is-horizontal">
|
<div class="field is-horizontal">
|
||||||
@ -87,7 +126,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<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-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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -99,14 +138,14 @@
|
|||||||
<div class="field-body">
|
<div class="field-body">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="div-auth-bind" class="field is-horizontal"{{ if .Setting.LDAP.Authentication == 0 }} style="display: none" {{ end }}>
|
<div id="div-auth-bind" class="field is-horizontal"{{ if .Setting.LDAP.Authentication == 0 }} style="display: none" {{ end }}>
|
||||||
<div class="field-label is-normal">
|
<div class="field-label is-normal">
|
||||||
<label class="label">Bind</label>
|
<label class="label">Bind account</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-body">
|
<div class="field-body">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
@ -134,7 +173,7 @@
|
|||||||
<div class="field-body">
|
<div class="field-body">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -176,45 +215,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</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>
|
<hr>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user