Add more LDAP options

This commit is contained in:
cuigh 2017-11-09 17:16:18 +08:00
parent 902c2bca7c
commit 0ff76725bb
12 changed files with 140 additions and 27 deletions

View File

@ -1849,6 +1849,16 @@ var Swirl;
let enabled = $(e.target).prop("checked"); let enabled = $(e.target).prop("checked");
$("#fs-ldap").find("input:not(:checkbox)").prop("readonly", !enabled); $("#fs-ldap").find("input:not(:checkbox)").prop("readonly", !enabled);
}); });
$("#ldap-auth-simple,#ldap-auth-bind").click(e => {
if ($(e.target).val() == "0") {
$("#div-auth-simple").show();
$("#div-auth-bind").hide();
}
else {
$("#div-auth-simple").hide();
$("#div-auth-bind").show();
}
});
} }
} }
Setting.IndexPage = IndexPage; Setting.IndexPage = IndexPage;

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ namespace Swirl.Core {
} }
/** /**
* Dispatcher * Create a Dispatcher instance
* *
* @param elem * @param elem
* @param event * @param event
@ -23,11 +23,11 @@ namespace Swirl.Core {
} }
/** /**
* * Register event
* *
* @param action * @param action
* @param handler * @param handler
* @returns {Mtime.Util.Dispatcher} * @returns {Swirl.Core.Dispatcher}
*/ */
on(action: string, handler: (e: JQueryEventObject) => any): Dispatcher { on(action: string, handler: (e: JQueryEventObject) => any): Dispatcher {
this.events[action] = handler; this.events[action] = handler;
@ -35,10 +35,10 @@ namespace Swirl.Core {
} }
/** /**
* * Unregister event
* *
* @param action * @param action
* @returns {Mtime.Util.Dispatcher} * @returns {Swirl.Core.Dispatcher}
*/ */
off(action: string): Dispatcher { off(action: string): Dispatcher {
delete this.events[action]; delete this.events[action];
@ -46,11 +46,11 @@ namespace Swirl.Core {
} }
/** /**
* * Bind events to element
* *
* @param elem * @param elem
* @param event * @param event
* @returns {Mtime.Util.Dispatcher} * @returns {Swirl.Core.Dispatcher}
*/ */
bind(elem: string | JQuery | Element | Document, event: string = "click"): Dispatcher { bind(elem: string | JQuery | Element | Document, event: string = "click"): Dispatcher {
$(elem).on(event, this.handle.bind(this)); $(elem).on(event, this.handle.bind(this));

View File

@ -77,7 +77,7 @@ namespace Swirl.Core {
* Submit form by AJAX * Submit form by AJAX
* *
* @param {string} url submit url * @param {string} url submit url
* @returns {Mtime.Net.AjaxPostRequest} * @returns {Swirl.Core.AjaxPostRequest}
* *
* @memberOf Form * @memberOf Form
*/ */

View File

@ -5,6 +5,15 @@ namespace Swirl.Setting {
let enabled = $(e.target).prop("checked"); let enabled = $(e.target).prop("checked");
$("#fs-ldap").find("input:not(:checkbox)").prop("readonly", !enabled); $("#fs-ldap").find("input:not(:checkbox)").prop("readonly", !enabled);
}); });
$("#ldap-auth-simple,#ldap-auth-bind").click(e => {
if ($(e.target).val() == "0") {
$("#div-auth-simple").show();
$("#div-auth-bind").hide();
} else {
$("#div-auth-simple").hide();
$("#div-auth-bind").show();
}
});
} }
} }
} }

View File

@ -14,6 +14,7 @@ var Setting = &settingBiz{}
type settingBiz struct { type settingBiz struct {
} }
// Get returns settings of swirl. If not found, default settings will be returned.
func (b *settingBiz) Get() (setting *model.Setting, err error) { func (b *settingBiz) Get() (setting *model.Setting, err error) {
do(func(d dao.Interface) { do(func(d dao.Interface) {
setting, err = d.SettingGet() setting, err = d.SettingGet()

View File

@ -193,6 +193,7 @@ func (b *userBiz) loginInternal(user *model.User, pwd string) error {
return nil return nil
} }
// TODO: support tls and bind auth
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 {
@ -210,7 +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 = 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
if err != nil { if err != nil {
log.Get("user").Error("Login by LDAP failed: ", err) log.Get("user").Error("Login by LDAP failed: ", err)
return ErrIncorrectAuth return ErrIncorrectAuth
@ -224,7 +227,7 @@ 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( req := ldap.NewSearchRequest(
setting.LDAP.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, setting.LDAP.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=organizationalPerson)(userPrincipalName=%s))", user.LoginName), fmt.Sprintf(setting.LDAP.UserFilter, user.LoginName),
[]string{setting.LDAP.NameAttr, setting.LDAP.EmailAttr}, []string{setting.LDAP.NameAttr, setting.LDAP.EmailAttr},
nil, nil,
) )
@ -232,8 +235,10 @@ func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error
if err != nil { if err != nil {
return err return err
} }
if len(sr.Entries) == 0 { if length := len(sr.Entries); length == 0 {
return ErrIncorrectAuth return ErrIncorrectAuth
} else if length > 1 {
return errors.New("Found more than one account when using LDAP authentication")
} }
entry := sr.Entries[0] entry := sr.Entries[0]

View File

@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/errors" "github.com/cuigh/auxo/errors"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/auxo/util/cast" "github.com/cuigh/auxo/util/cast"
@ -12,7 +13,6 @@ import (
"github.com/cuigh/swirl/biz/docker" "github.com/cuigh/swirl/biz/docker"
"github.com/cuigh/swirl/misc" "github.com/cuigh/swirl/misc"
"github.com/cuigh/swirl/model" "github.com/cuigh/swirl/model"
"mtime.com/auxo/data/set"
) )
// ServiceController is a controller of docker service // ServiceController is a controller of docker service
@ -175,7 +175,11 @@ func serviceNew(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
checkedNetworks := set.FromSlice(service.Networks, func(i int) interface{} { return service.Networks[i] })
checkedNetworks := data.NewSet()
checkedNetworks.AddSlice(service.Networks, func(i int) interface{} {
return service.Networks[i]
})
m := newModel(ctx).Set("Service", service).Set("Registries", registries). m := newModel(ctx).Set("Service", service).Set("Registries", registries).
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks). Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
@ -230,7 +234,8 @@ func serviceEdit(ctx web.Context) error {
} }
stack := service.Spec.Labels["com.docker.stack.namespace"] stack := service.Spec.Labels["com.docker.stack.namespace"]
checkedNetworks := set.FromSlice(service.Endpoint.VirtualIPs, func(i int) interface{} { return service.Endpoint.VirtualIPs[i].NetworkID }) checkedNetworks := data.NewSet()
checkedNetworks.AddSlice(service.Endpoint.VirtualIPs, func(i int) interface{} { return service.Endpoint.VirtualIPs[i].NetworkID })
m := newModel(ctx).Set("Service", model.NewServiceInfo(service)).Set("Stack", stack). m := newModel(ctx).Set("Service", model.NewServiceInfo(service)).Set("Stack", stack).
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks). Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).

View File

@ -29,7 +29,7 @@ func main() {
misc.LoadOptions() misc.LoadOptions()
app.Run(server()) app.Run(server())
} }
app.Register(flag.All) app.Flags.Register(flag.All)
app.Start() app.Start()
} }

View File

@ -2,13 +2,22 @@ package model
import "time" import "time"
// Setting represents the options of swirl.
type Setting struct { type Setting struct {
LDAP struct { LDAP struct {
Enabled bool `bson:"enabled" json:"enabled,omitempty"` Enabled bool `bson:"enabled" json:"enabled,omitempty"`
Address string `bson:"address" json:"address,omitempty"` Address string `bson:"address" json:"address,omitempty"`
BaseDN string `bson:"base_dn" json:"base_dn,omitempty"` Security int32 `bson:"security" json:"security,omitempty"` // 0-None/1-TLS/2-StartTLS
NameAttr string `bson:"name_attr" json:"name_attr,omitempty"` TLSCert string `bson:"tls_cert" json:"tls_cert,omitempty"` // TLS cert
EmailAttr string `bson:"email_attr" json:"email_attr,omitempty"` TLSVerify bool `bson:"tls_verify" json:"tls_verify,omitempty"` // Verify cert
Authentication int32 `bson:"auth" json:"auth,omitempty"` // 0-Bind/1-Simple
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
UserDN string `bson:"user_dn" json:"user_dn,omitempty"` // Template for the DN of the user for simple auth
UserFilter string `bson:"user_filter" json:"user_filter,omitempty"` // Search filter for user
NameAttr string `bson:"name_attr" json:"name_attr,omitempty"`
EmailAttr string `bson:"email_attr" json:"email_attr,omitempty"`
} `bson:"ldap" json:"ldap,omitempty"` } `bson:"ldap" json:"ldap,omitempty"`
TimeZone struct { TimeZone struct {
Name string `bson:"name" json:"name,omitempty"` // Asia/Shanghai Name string `bson:"name" json:"name,omitempty"` // Asia/Shanghai

View File

@ -9,7 +9,7 @@
{{ end }} {{ end }}
{{ block radio(id, name, value, label, checked, disabled=false) }} {{ block radio(id, name, value, label, checked, disabled=false) }}
<input id="{{id ? id : (name + "-" + value)}}" name="{{name}}" value="{{value}}" type="radio" class="is-radio"{{if checked == value}} checked{{end}}{{if disabled}} disabled{{end}}{{yield content}}> <input id="{{id ? id : (name + "-" + value)}}" name="{{name}}" value="{{value}}" type="radio" class="is-radio"{{if eq(checked, value)}} checked{{end}}{{if disabled}} disabled{{end}}{{yield content}}>
<label for="{{id ? id : (name + "-" + value)}}">{{label}}</label> <label for="{{id ? id : (name + "-" + value)}}">{{label}}</label>
{{ end }} {{ end }}

View File

@ -65,6 +65,68 @@
</div> </div>
</div> </div>
</div> </div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Security</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
{{ yield radio(id="ldap.security-none", name="ldap.security", value="0", label="None", checked=.Setting.LDAP.Security) content}} data-type="integer"{{end}}
{{ yield radio(id="ldap.security-tls", name="ldap.security", value="1", label="TLS", disabled=true) content}} data-type="integer"{{end}}
{{ yield radio(id="ldap.security-starttls", name="ldap.security", value="2", label="StartTLS", disabled=true) content}} data-type="integer"{{end}}
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Authentication</label>
</div>
<div class="field-body">
<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}}
</div>
</div>
</div>
</div>
<div id="div-auth-simple" class="field is-horizontal"{{ if .Setting.LDAP.Authentication == 1 }} style="display: none" {{ end }}>
<div class="field-label is-normal">
<label class="label">User DN</label>
</div>
<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">
</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>
</div>
<div class="field-body">
<div class="field has-addons">
<p class="control">
<a class="button is-static">DN</a>
</p>
<div class="control is-expanded">
<input name="ldap.bind_dn" value="{{ .Setting.LDAP.BindDN }}" class="input" type="text" placeholder="e.g. cn=search,dc=xxx,dc=com">
</div>
</div>
<div class="field has-addons">
<p class="control">
<a class="button is-static">Password</a>
</p>
<div class="control is-expanded">
<input name="ldap.bind_pwd" value="{{ .Setting.LDAP.BindPassword }}" class="input" type="password" autocomplete="new-password" placeholder="Bind DN password">
</div>
</div>
</div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label">Base DN</label> <label class="label">Base DN</label>
@ -79,23 +141,35 @@
</div> </div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label">Username attribute</label> <label class="label">User filter</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input name="ldap.name_attr" value="{{ .Setting.LDAP.NameAttr }}" class="input" type="text" placeholder="e.g. displayName"> <input name="ldap.user_filter" value="{{ .Setting.LDAP.UserFilter }}" class="input" type="text" placeholder="e.g. (&(objectClass=organizationalPerson)(sAMAccountName=%s))">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label">Email attribute</label> <label class="label">Attributes</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field has-addons">
<div class="control"> <p class="control">
<a class="button is-static">Username</a>
</p>
<div class="control is-expanded">
<input name="ldap.name_attr" value="{{ .Setting.LDAP.NameAttr }}" class="input" type="text" placeholder="e.g. displayName">
</div>
</div>
<div class="field has-addons">
<p class="control">
<a class="button is-static">Email</a>
</p>
<div class="control is-expanded">
<input name="ldap.email_attr" value="{{ .Setting.LDAP.EmailAttr }}" class="input" type="text" placeholder="e.g. mail"> <input name="ldap.email_attr" value="{{ .Setting.LDAP.EmailAttr }}" class="input" type="text" placeholder="e.g. mail">
</div> </div>
</div> </div>