mirror of
https://github.com/cuigh/swirl
synced 2025-04-24 08:04:45 +00:00
Refactor authentication & authorization modules
This commit is contained in:
parent
81081c8164
commit
5b60a024c5
98
Gopkg.lock
generated
98
Gopkg.lock
generated
@ -22,25 +22,87 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/cuigh/auxo"
|
||||
packages = [".","app","app/flag","byte/size","cache","cache/memory","config","data","data/guid","data/valid","encoding/yaml","errors","ext/files","ext/reflects","ext/texts","ext/times","log","log/console","log/file","net/web","net/web/filter","net/web/filter/auth","net/web/renderer","net/web/renderer/jet","net/web/router","security","security/passwd","util/cast","util/debug","util/i18n","util/lazy"]
|
||||
revision = "f599f84ec7ef56f01457d0fb0d8eeb9d8c100b43"
|
||||
packages = [
|
||||
".",
|
||||
"app",
|
||||
"app/flag",
|
||||
"byte/size",
|
||||
"cache",
|
||||
"cache/memory",
|
||||
"config",
|
||||
"data",
|
||||
"data/guid",
|
||||
"data/set",
|
||||
"data/valid",
|
||||
"encoding/yaml",
|
||||
"errors",
|
||||
"ext/files",
|
||||
"ext/reflects",
|
||||
"ext/texts",
|
||||
"ext/times",
|
||||
"log",
|
||||
"log/console",
|
||||
"log/file",
|
||||
"net/web",
|
||||
"net/web/filter",
|
||||
"net/web/filter/auth",
|
||||
"net/web/renderer",
|
||||
"net/web/renderer/jet",
|
||||
"net/web/router",
|
||||
"security",
|
||||
"security/certify",
|
||||
"security/certify/ldap",
|
||||
"security/passwd",
|
||||
"util/cast",
|
||||
"util/debug",
|
||||
"util/i18n",
|
||||
"util/lazy"
|
||||
]
|
||||
revision = "ac7f3a5d4d43fd755008c87525a95698544843ec"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/distribution"
|
||||
packages = [".","digestset","reference"]
|
||||
packages = [
|
||||
".",
|
||||
"digestset",
|
||||
"reference"
|
||||
]
|
||||
revision = "e5b5e44386f7964a1f010a2b8b7687d073215bbb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/docker"
|
||||
packages = ["api","api/types","api/types/blkiodev","api/types/container","api/types/events","api/types/filters","api/types/image","api/types/mount","api/types/network","api/types/registry","api/types/strslice","api/types/swarm","api/types/swarm/runtime","api/types/time","api/types/versions","api/types/volume","client","pkg/stdcopy"]
|
||||
packages = [
|
||||
"api",
|
||||
"api/types",
|
||||
"api/types/blkiodev",
|
||||
"api/types/container",
|
||||
"api/types/events",
|
||||
"api/types/filters",
|
||||
"api/types/image",
|
||||
"api/types/mount",
|
||||
"api/types/network",
|
||||
"api/types/registry",
|
||||
"api/types/strslice",
|
||||
"api/types/swarm",
|
||||
"api/types/swarm/runtime",
|
||||
"api/types/time",
|
||||
"api/types/versions",
|
||||
"api/types/volume",
|
||||
"client",
|
||||
"pkg/stdcopy"
|
||||
]
|
||||
revision = "f62aeae97791b4c2318791a9b621d7da4c17ac32"
|
||||
source = "https://github.com/moby/moby.git"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = ["nat","sockets","tlsconfig"]
|
||||
packages = [
|
||||
"nat",
|
||||
"sockets",
|
||||
"tlsconfig"
|
||||
]
|
||||
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
|
||||
version = "v0.3.0"
|
||||
|
||||
@ -53,7 +115,13 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/globalsign/mgo"
|
||||
packages = [".","bson","internal/json","internal/sasl","internal/scram"]
|
||||
packages = [
|
||||
".",
|
||||
"bson",
|
||||
"internal/json",
|
||||
"internal/sasl",
|
||||
"internal/scram"
|
||||
]
|
||||
revision = "896bbb89d21253a28cd5a7f8b81fe091410cb94d"
|
||||
|
||||
[[projects]]
|
||||
@ -88,7 +156,10 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opencontainers/image-spec"
|
||||
packages = ["specs-go","specs-go/v1"]
|
||||
packages = [
|
||||
"specs-go",
|
||||
"specs-go/v1"
|
||||
]
|
||||
revision = "d60099175f88c47cd379c4738d158884749ed235"
|
||||
version = "v1.0.1"
|
||||
|
||||
@ -101,13 +172,20 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["acme","acme/autocert"]
|
||||
packages = [
|
||||
"acme",
|
||||
"acme/autocert"
|
||||
]
|
||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","context/ctxhttp","proxy"]
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"proxy"
|
||||
]
|
||||
revision = "a337091b0525af65de94df2eb7e98bd9962dcbe2"
|
||||
|
||||
[[projects]]
|
||||
@ -131,6 +209,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "2e2023629c134da8099f44e6d2719a5bc407d2b350e75f6f79a35ade95859ebd"
|
||||
inputs-digest = "aae5ea1cf0fb5fd50cb9659fb0abe58fc9546231ec91f9df9148c5df3e3cc194"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -17,10 +17,6 @@ required = ["github.com/docker/distribution"]
|
||||
name = "github.com/docker/go-units"
|
||||
version = "0.3.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-ldap/ldap"
|
||||
version = "2.5.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-shellwords"
|
||||
version = "1.0.3"
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// ServiceList return service list.
|
||||
func ServiceList(name string, pageIndex, pageSize int) (infos []*model.ServiceListInfo, totalCount int, err error) {
|
||||
func ServiceList(name string, pageIndex, pageSize int) (infos []*model.ServiceListInfo, totalCount int, err error) { // nolint: gocyclo
|
||||
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
||||
var (
|
||||
services []swarm.Service
|
||||
@ -118,7 +118,7 @@ func ServiceInspect(name string) (service swarm.Service, raw []byte, err error)
|
||||
}
|
||||
|
||||
// ServiceUpdate update a service.
|
||||
func ServiceUpdate(info *model.ServiceInfo) error {
|
||||
func ServiceUpdate(info *model.ServiceInfo) error { // nolint: gocyclo
|
||||
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
||||
service, _, err := cli.ServiceInspectWithRaw(ctx, info.Name, types.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
@ -343,7 +343,7 @@ func ServiceScale(name string, count uint64) error {
|
||||
}
|
||||
|
||||
// ServiceCreate create a service.
|
||||
func ServiceCreate(info *model.ServiceInfo) error {
|
||||
func ServiceCreate(info *model.ServiceInfo) error { // nolint: gocyclo
|
||||
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
||||
service := swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
@ -576,129 +576,129 @@ func ServiceCommand(name string) (cmd string, err error) { // nolint: gocyclo
|
||||
}
|
||||
|
||||
si := model.NewServiceInfo(service)
|
||||
b := texts.GetBuilder()
|
||||
b.Append("docker service create --name ", si.Name)
|
||||
b := texts.Builder{}
|
||||
b.WriteString("docker service create --name ", si.Name)
|
||||
if si.Mode == "global" {
|
||||
b.Append(" --mode global")
|
||||
b.WriteString(" --mode global")
|
||||
} else if si.Replicas > 1 {
|
||||
b.AppendFormat(" --replicas %d", si.Replicas)
|
||||
b.WriteFormat(" --replicas %d", si.Replicas)
|
||||
}
|
||||
for _, n := range service.Spec.TaskTemplate.Networks {
|
||||
var network types.NetworkResource
|
||||
network, err = cli.NetworkInspect(ctx, n.Target, types.NetworkInspectOptions{})
|
||||
b.Append(" --network ", network.Name)
|
||||
b.WriteString(" --network ", network.Name)
|
||||
}
|
||||
if len(si.Endpoint.Ports) > 0 {
|
||||
if si.Endpoint.Mode == swarm.ResolutionModeDNSRR {
|
||||
b.AppendFormat(" --endpoint-mode %s", si.Endpoint.Mode)
|
||||
b.WriteFormat(" --endpoint-mode %s", si.Endpoint.Mode)
|
||||
}
|
||||
for _, p := range si.Endpoint.Ports {
|
||||
b.AppendFormat(" --publish mode=%s,target=%d,published=%d,protocol=%s", p.PublishMode, p.TargetPort, p.PublishedPort, p.Protocol)
|
||||
b.WriteFormat(" --publish mode=%s,target=%d,published=%d,protocol=%s", p.PublishMode, p.TargetPort, p.PublishedPort, p.Protocol)
|
||||
}
|
||||
}
|
||||
for _, c := range si.Placement.Constraints {
|
||||
b.AppendFormat(" --constraint '%s'", c.ToConstraint())
|
||||
b.WriteFormat(" --constraint '%s'", c.ToConstraint())
|
||||
}
|
||||
for _, p := range si.Placement.Preferences {
|
||||
b.AppendFormat(" --placement-pref '%s'", p.Spread)
|
||||
b.WriteFormat(" --placement-pref '%s'", p.Spread)
|
||||
}
|
||||
for _, e := range si.Environments {
|
||||
b.AppendFormat(" --env '%s=%s'", e.Name, e.Value)
|
||||
b.WriteFormat(" --env '%s=%s'", e.Name, e.Value)
|
||||
}
|
||||
for _, l := range si.ServiceLabels {
|
||||
b.AppendFormat(" --label '%s=%s'", l.Name, l.Value)
|
||||
b.WriteFormat(" --label '%s=%s'", l.Name, l.Value)
|
||||
}
|
||||
for _, l := range si.ContainerLabels {
|
||||
b.AppendFormat(" --container-label '%s=%s'", l.Name, l.Value)
|
||||
b.WriteFormat(" --container-label '%s=%s'", l.Name, l.Value)
|
||||
}
|
||||
for _, mnt := range si.Mounts {
|
||||
// todo: add mnt.Propagation
|
||||
b.AppendFormat(" --mount type=%s,source=%s,destination=%s", mnt.Type, mnt.Source, mnt.Target)
|
||||
b.WriteFormat(" --mount type=%s,source=%s,destination=%s", mnt.Type, mnt.Source, mnt.Target)
|
||||
if mnt.ReadOnly {
|
||||
b.Append(",ro=1")
|
||||
b.WriteString(",ro=1")
|
||||
}
|
||||
}
|
||||
// todo: uid/gid
|
||||
for _, c := range si.Configs {
|
||||
b.AppendFormat(" --config source=%s,target=%s,mode=0%d", c.Name, c.FileName, c.Mode)
|
||||
b.WriteFormat(" --config source=%s,target=%s,mode=0%d", c.Name, c.FileName, c.Mode)
|
||||
}
|
||||
for _, c := range si.Secrets {
|
||||
b.AppendFormat(" --secret source=%s,target=%s,mode=0%d", c.Name, c.FileName, c.Mode)
|
||||
b.WriteFormat(" --secret source=%s,target=%s,mode=0%d", c.Name, c.FileName, c.Mode)
|
||||
}
|
||||
if si.Resource.Limit.IsSet() {
|
||||
if si.Resource.Limit.CPU > 0 {
|
||||
b.AppendFormat(" --limit-cpu %.2f", si.Resource.Limit.CPU)
|
||||
b.WriteFormat(" --limit-cpu %.2f", si.Resource.Limit.CPU)
|
||||
}
|
||||
if si.Resource.Limit.Memory != "" {
|
||||
b.Append(" --limit-memory ", si.Resource.Limit.Memory)
|
||||
b.WriteString(" --limit-memory ", si.Resource.Limit.Memory)
|
||||
}
|
||||
}
|
||||
if si.Resource.Reserve.IsSet() {
|
||||
if si.Resource.Reserve.CPU > 0 {
|
||||
b.AppendFormat(" --reserve-cpu %.2f", si.Resource.Reserve.CPU)
|
||||
b.WriteFormat(" --reserve-cpu %.2f", si.Resource.Reserve.CPU)
|
||||
}
|
||||
if si.Resource.Reserve.Memory != "" {
|
||||
b.Append(" --reserve-memory ", si.Resource.Reserve.Memory)
|
||||
b.WriteString(" --reserve-memory ", si.Resource.Reserve.Memory)
|
||||
}
|
||||
}
|
||||
if si.LogDriver.Name != "" {
|
||||
b.Append(" --log-driver ", si.LogDriver.Name)
|
||||
b.WriteString(" --log-driver ", si.LogDriver.Name)
|
||||
for _, opt := range si.LogDriver.Options {
|
||||
b.AppendFormat(" --log-opt '%s=%s'", opt.Name, opt.Value)
|
||||
b.WriteFormat(" --log-opt '%s=%s'", opt.Name, opt.Value)
|
||||
}
|
||||
}
|
||||
// UpdatePolicy
|
||||
if si.UpdatePolicy.Parallelism > 0 {
|
||||
b.AppendFormat(" --update-parallelism %d", si.UpdatePolicy.Parallelism)
|
||||
b.WriteFormat(" --update-parallelism %d", si.UpdatePolicy.Parallelism)
|
||||
}
|
||||
if si.UpdatePolicy.Delay != "" {
|
||||
b.Append(" --update-delay ", si.UpdatePolicy.Delay)
|
||||
b.WriteString(" --update-delay ", si.UpdatePolicy.Delay)
|
||||
}
|
||||
if si.UpdatePolicy.FailureAction != "" {
|
||||
b.Append(" --update-failure-action ", si.UpdatePolicy.FailureAction)
|
||||
b.WriteString(" --update-failure-action ", si.UpdatePolicy.FailureAction)
|
||||
}
|
||||
if si.UpdatePolicy.Order != "" {
|
||||
b.Append(" --update-order ", si.UpdatePolicy.Order)
|
||||
b.WriteString(" --update-order ", si.UpdatePolicy.Order)
|
||||
}
|
||||
// RollbackPolicy
|
||||
if si.RollbackPolicy.Parallelism > 0 {
|
||||
b.AppendFormat(" --rollback-parallelism %d", si.RollbackPolicy.Parallelism)
|
||||
b.WriteFormat(" --rollback-parallelism %d", si.RollbackPolicy.Parallelism)
|
||||
}
|
||||
if si.RollbackPolicy.Delay != "" {
|
||||
b.Append(" --rollback-delay ", si.RollbackPolicy.Delay)
|
||||
b.WriteString(" --rollback-delay ", si.RollbackPolicy.Delay)
|
||||
}
|
||||
if si.RollbackPolicy.FailureAction != "" {
|
||||
b.Append(" --rollback-failure-action ", si.RollbackPolicy.FailureAction)
|
||||
b.WriteString(" --rollback-failure-action ", si.RollbackPolicy.FailureAction)
|
||||
}
|
||||
if si.RollbackPolicy.Order != "" {
|
||||
b.Append(" --rollback-order ", si.RollbackPolicy.Order)
|
||||
b.WriteString(" --rollback-order ", si.RollbackPolicy.Order)
|
||||
}
|
||||
// RestartPolicy
|
||||
if si.RestartPolicy.Condition != "" {
|
||||
b.AppendFormat(" --restart-condition %s", si.RestartPolicy.Condition)
|
||||
b.WriteFormat(" --restart-condition %s", si.RestartPolicy.Condition)
|
||||
}
|
||||
if si.RestartPolicy.MaxAttempts > 0 {
|
||||
b.AppendFormat(" --restart-max-attempts %d", si.RestartPolicy.MaxAttempts)
|
||||
b.WriteFormat(" --restart-max-attempts %d", si.RestartPolicy.MaxAttempts)
|
||||
}
|
||||
if si.RestartPolicy.Delay != "" {
|
||||
b.Append(" --restart-delay ", si.RestartPolicy.Delay)
|
||||
b.WriteString(" --restart-delay ", si.RestartPolicy.Delay)
|
||||
}
|
||||
if si.RestartPolicy.Window != "" {
|
||||
b.Append(" --restart-window ", si.RestartPolicy.Window)
|
||||
b.WriteString(" --restart-window ", si.RestartPolicy.Window)
|
||||
}
|
||||
//b.Append(" --with-registry-auth")
|
||||
//b.WriteString(" --with-registry-auth")
|
||||
if si.Dir != "" {
|
||||
b.Append(" --workdir ", si.Dir)
|
||||
b.WriteString(" --workdir ", si.Dir)
|
||||
}
|
||||
if si.User != "" {
|
||||
b.Append(" --user ", si.User)
|
||||
b.WriteString(" --user ", si.User)
|
||||
}
|
||||
b.Append(" ", si.Image)
|
||||
b.WriteString(" ", si.Image)
|
||||
if si.Command != "" {
|
||||
b.Append(" ", si.Command)
|
||||
b.WriteString(" ", si.Command)
|
||||
}
|
||||
if si.Args != "" {
|
||||
b.Append(" ", si.Args)
|
||||
b.WriteString(" ", si.Args)
|
||||
}
|
||||
cmd = b.String()
|
||||
return
|
||||
|
16
biz/perm.go
16
biz/perm.go
@ -2,7 +2,6 @@ package biz
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/dao"
|
||||
@ -39,6 +38,7 @@ func (b *permBiz) Update(perm *model.Perm, user web.User) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func (b *permBiz) Check(user web.User, scope string, resType, resID string) (err error) {
|
||||
au := user.(*model.AuthUser)
|
||||
if au.Admin() {
|
||||
@ -71,17 +71,3 @@ func (b *permBiz) Check(user web.User, scope string, resType, resID string) (err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (b *permBiz) Apply(next web.HandlerFunc) web.HandlerFunc {
|
||||
return func(ctx web.Context) error {
|
||||
opt := ctx.Handler().Option("perm")
|
||||
if opt != "" {
|
||||
array := strings.Split(opt, ",")
|
||||
err := b.Check(ctx.User(), array[0], array[1], ctx.P(array[2]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
|
243
biz/user.go
243
biz/user.go
@ -1,25 +1,17 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/cache"
|
||||
"github.com/cuigh/auxo/errors"
|
||||
"github.com/cuigh/auxo/log"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/auxo/security/passwd"
|
||||
"github.com/cuigh/swirl/dao"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/model"
|
||||
"github.com/go-ldap/ldap"
|
||||
)
|
||||
|
||||
var ErrIncorrectAuth = errors.New("login name or password is incorrect")
|
||||
|
||||
var User = &userBiz{}
|
||||
var User = &userBiz{} // nolint: golint
|
||||
|
||||
type userBiz struct {
|
||||
}
|
||||
@ -137,232 +129,25 @@ func (b *userBiz) Count() (count int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (b *userBiz) Login(name, pwd string) (token string, err error) {
|
||||
func (b *userBiz) UpdateSession(id string) (token string, err error) {
|
||||
session := &model.Session{
|
||||
UserID: id,
|
||||
Token: misc.NewID(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
session.Expires = session.UpdatedAt.Add(time.Hour * 24)
|
||||
do(func(d dao.Interface) {
|
||||
var (
|
||||
user *model.User
|
||||
)
|
||||
|
||||
user, err = d.UserGetByName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
user = &model.User{
|
||||
Type: model.UserTypeLDAP,
|
||||
LoginName: name,
|
||||
}
|
||||
err = b.loginLDAP(d, user, pwd)
|
||||
} else {
|
||||
if user.Status == model.UserStatusBlocked {
|
||||
err = fmt.Errorf("user %s is blocked", name)
|
||||
return
|
||||
}
|
||||
|
||||
if user.Type == model.UserTypeInternal {
|
||||
err = b.loginInternal(user, pwd)
|
||||
} else {
|
||||
err = b.loginLDAP(d, user, pwd)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
session := &model.Session{
|
||||
UserID: user.ID,
|
||||
Token: misc.NewID(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
session.Expires = session.UpdatedAt.Add(time.Hour * 24)
|
||||
err = d.SessionUpdate(session)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
token = session.Token
|
||||
|
||||
// create event
|
||||
Event.CreateAuthentication(model.EventActionLogin, user.ID, user.LoginName, user.Name)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (b *userBiz) loginInternal(user *model.User, pwd string) error {
|
||||
if !passwd.Validate(pwd, user.Password, user.Salt) {
|
||||
return ErrIncorrectAuth
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *userBiz) loginLDAP(d dao.Interface, user *model.User, pwd string) error {
|
||||
setting, err := Setting.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !setting.LDAP.Enabled {
|
||||
return ErrIncorrectAuth
|
||||
}
|
||||
|
||||
l, err := b.ldapDial(setting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
// bind
|
||||
err = b.ldapBind(setting, l, user, pwd)
|
||||
if err != nil {
|
||||
log.Get("user").Error("LDAP > Failed to bind: ", err)
|
||||
return ErrIncorrectAuth
|
||||
}
|
||||
|
||||
// Stop here for an exist user because we only need validate password.
|
||||
if user.ID != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If user wasn't exist, we need create it
|
||||
entry, err := b.ldapSearchOne(setting, l, user.LoginName, setting.LDAP.NameAttr, setting.LDAP.EmailAttr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Email = entry.GetAttributeValue(setting.LDAP.EmailAttr)
|
||||
user.Name = entry.GetAttributeValue(setting.LDAP.NameAttr)
|
||||
return b.Create(user, nil)
|
||||
}
|
||||
|
||||
func (b *userBiz) ldapDial(setting *model.Setting) (*ldap.Conn, error) {
|
||||
host, _, err := net.SplitHostPort(setting.LDAP.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: support tls cert and verification
|
||||
tc := &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: nil,
|
||||
}
|
||||
|
||||
if setting.LDAP.Security == model.LDAPSecurityTLS {
|
||||
return ldap.DialTLS("tcp", setting.LDAP.Address, tc)
|
||||
}
|
||||
|
||||
conn, err := ldap.Dial("tcp", setting.LDAP.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if setting.LDAP.Security == model.LDAPSecurityStartTLS {
|
||||
if err = conn.StartTLS(tc); err != nil {
|
||||
conn.Close()
|
||||
log.Get("user").Error("LDAP > Failed to switch to TLS: ", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, 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)
|
||||
}
|
||||
token = session.Token
|
||||
}
|
||||
}
|
||||
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) {
|
||||
const cacheKey = "auth_user"
|
||||
|
||||
do(func(d dao.Interface) {
|
||||
var (
|
||||
roles []*model.Role
|
||||
role *model.Role
|
||||
)
|
||||
|
||||
session, err := d.SessionGet(token)
|
||||
if err != nil {
|
||||
log.Get("user").Errorf("Load session failed: %v", err)
|
||||
return
|
||||
}
|
||||
if session == nil || session.Expires.Before(time.Now()) {
|
||||
return
|
||||
}
|
||||
|
||||
value := cache.Get(cacheKey, session.UserID)
|
||||
if !value.IsNil() {
|
||||
user = &model.AuthUser{}
|
||||
if err = value.Scan(user); err == nil {
|
||||
return
|
||||
}
|
||||
log.Get("user").Warnf("Load auth user from cache failed: %v", err)
|
||||
}
|
||||
|
||||
u, err := d.UserGetByID(session.UserID)
|
||||
if err != nil {
|
||||
log.Get("user").Errorf("Load user failed: %v", err)
|
||||
return
|
||||
}
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(u.Roles) > 0 {
|
||||
roles = make([]*model.Role, len(u.Roles))
|
||||
for i, id := range u.Roles {
|
||||
role, err = d.RoleGet(id)
|
||||
if err != nil {
|
||||
return
|
||||
} else if role != nil {
|
||||
roles[i] = role
|
||||
}
|
||||
}
|
||||
}
|
||||
user = model.NewAuthUser(u, roles)
|
||||
cache.Set(user, cacheKey, session.UserID)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Authorize check permission of user
|
||||
func (b *userBiz) Authorize(user web.User, h web.HandlerInfo) bool {
|
||||
if au, ok := user.(*model.AuthUser); ok {
|
||||
return au.IsAllowed(h.Name())
|
||||
}
|
||||
return false
|
||||
func (b *userBiz) GetSession(token string) (session *model.Session, err error) {
|
||||
do(func(d dao.Interface) {
|
||||
session, err = d.SessionGet(token)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/data/set"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/model"
|
||||
@ -21,11 +22,7 @@ func permEdit(ctx web.Context, resType, resID, tpl string, m data.Map) error {
|
||||
return err
|
||||
}
|
||||
|
||||
checkedRoles := data.Set{}
|
||||
checkedRoles.AddSlice(perm.Roles, func(i int) interface{} {
|
||||
return perm.Roles[i]
|
||||
})
|
||||
|
||||
checkedRoles := set.NewStringSet(perm.Roles...)
|
||||
var users []*model.User
|
||||
for _, id := range perm.Users {
|
||||
var user *model.User
|
||||
|
@ -1,10 +1,11 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/model"
|
||||
"github.com/cuigh/swirl/security"
|
||||
)
|
||||
|
||||
// RoleController is a controller of user role
|
||||
@ -42,7 +43,7 @@ func roleIndex(ctx web.Context) error {
|
||||
}
|
||||
|
||||
func roleNew(ctx web.Context) error {
|
||||
m := newModel(ctx).Set("Perms", misc.Perms)
|
||||
m := newModel(ctx).Set("Perms", security.Perms)
|
||||
return ctx.Render("system/role/new", m)
|
||||
}
|
||||
|
||||
@ -73,9 +74,9 @@ func roleDetail(ctx web.Context) error {
|
||||
|
||||
perms := make(map[string]struct{})
|
||||
for _, p := range role.Perms {
|
||||
perms[p] = model.Placeholder
|
||||
perms[p] = data.Empty
|
||||
}
|
||||
m := newModel(ctx).Set("Role", role).Set("Perms", misc.Perms).Set("CheckedPerms", perms)
|
||||
m := newModel(ctx).Set("Role", role).Set("Perms", security.Perms).Set("CheckedPerms", perms)
|
||||
return ctx.Render("system/role/detail", m)
|
||||
}
|
||||
|
||||
@ -91,9 +92,9 @@ func roleEdit(ctx web.Context) error {
|
||||
|
||||
perms := make(map[string]struct{})
|
||||
for _, p := range role.Perms {
|
||||
perms[p] = model.Placeholder
|
||||
perms[p] = data.Empty
|
||||
}
|
||||
m := newModel(ctx).Set("Role", role).Set("Perms", misc.Perms).Set("CheckedPerms", perms)
|
||||
m := newModel(ctx).Set("Role", role).Set("Perms", security.Perms).Set("CheckedPerms", perms)
|
||||
return ctx.Render("system/role/edit", m)
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/data/set"
|
||||
"github.com/cuigh/auxo/errors"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/auxo/util/cast"
|
||||
@ -162,11 +162,7 @@ func serviceNew(ctx web.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
checkedNetworks := data.NewSet()
|
||||
checkedNetworks.AddSlice(info.Networks, func(i int) interface{} {
|
||||
return info.Networks[i]
|
||||
})
|
||||
|
||||
checkedNetworks := set.NewStringSet(info.Networks...)
|
||||
m := newModel(ctx).Set("Service", info).Set("Registries", registries).
|
||||
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
|
||||
Set("Secrets", secrets).Set("Configs", configs)
|
||||
@ -221,8 +217,8 @@ func serviceEdit(ctx web.Context) error {
|
||||
}
|
||||
|
||||
stack := service.Spec.Labels["com.docker.stack.namespace"]
|
||||
checkedNetworks := data.NewSet()
|
||||
checkedNetworks.AddSlice(service.Endpoint.VirtualIPs, func(i int) interface{} { return service.Endpoint.VirtualIPs[i].NetworkID })
|
||||
checkedNetworks := set.StringSet{}
|
||||
checkedNetworks.AddSlice(service.Endpoint.VirtualIPs, func(i int) string { return service.Endpoint.VirtualIPs[i].NetworkID })
|
||||
|
||||
m := newModel(ctx).Set("Service", model.NewServiceInfo(service)).Set("Stack", stack).
|
||||
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
|
||||
|
@ -3,7 +3,7 @@ package controller
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/data/set"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/biz/docker"
|
||||
@ -73,7 +73,7 @@ func templateNew(ctx web.Context) error {
|
||||
return err
|
||||
}
|
||||
m := newModel(ctx).Set("Action", "New").Set("Service", service).Set("Registries", registries).
|
||||
Set("Networks", networks).Set("CheckedNetworks", data.Set{}).
|
||||
Set("Networks", networks).Set("CheckedNetworks", set.StringSet{}).
|
||||
Set("Secrets", secrets).Set("Configs", configs)
|
||||
return ctx.Render("service/template/edit", m)
|
||||
}
|
||||
@ -143,9 +143,7 @@ func templateEdit(ctx web.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
checkedNetworks := data.NewSet()
|
||||
checkedNetworks.AddSlice(service.Networks, func(i int) interface{} { return service.Networks[i] })
|
||||
|
||||
checkedNetworks := set.NewStringSet(service.Networks...)
|
||||
m := newModel(ctx).Set("Action", "Edit").Set("Service", service).Set("Registries", registries).
|
||||
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
|
||||
Set("Secrets", secrets).Set("Configs", configs)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/log"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
@ -129,7 +130,7 @@ func userEdit(ctx web.Context) error {
|
||||
|
||||
userRoles := make(map[string]struct{})
|
||||
for _, id := range user.Roles {
|
||||
userRoles[id] = model.Placeholder
|
||||
userRoles[id] = data.Empty
|
||||
}
|
||||
m := newModel(ctx).Set("User", user).Set("Roles", roles).Set("UserRoles", userRoles)
|
||||
return ctx.Render("system/user/edit", m)
|
||||
|
11
main.go
11
main.go
@ -19,13 +19,14 @@ import (
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/controller"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/security"
|
||||
)
|
||||
|
||||
func main() {
|
||||
misc.BindOptions()
|
||||
|
||||
app.Name = "Swirl"
|
||||
app.Version = "0.6.7"
|
||||
app.Version = "0.6.8"
|
||||
app.Desc = "A web management UI for Docker, focused on swarm cluster"
|
||||
app.Action = func(ctx *app.Context) {
|
||||
misc.LoadOptions()
|
||||
@ -77,14 +78,14 @@ func server() *web.Server {
|
||||
|
||||
// create biz group
|
||||
form := &auth.Form{
|
||||
Identifier: biz.User.Identify,
|
||||
Identifier: security.Identifier,
|
||||
Timeout: time.Minute * 30,
|
||||
SlidingExpiration: true,
|
||||
}
|
||||
g := ws.Group("", form, filter.NewAuthorizer(biz.User.Authorize))
|
||||
g := ws.Group("", form, filter.NewAuthorizer(security.Checker))
|
||||
|
||||
// register auth handlers
|
||||
g.Post("/login", form.LoginJSON(biz.User.Login), web.WithName("login"), web.WithAuthorize(web.AuthAnonymous))
|
||||
g.Post("/login", form.LoginJSON(security.Validator(setting)), web.WithName("login"), web.WithAuthorize(web.AuthAnonymous))
|
||||
g.Get("/logout", form.Logout, web.WithName("logout"), web.WithAuthorize(web.AuthAuthenticated))
|
||||
|
||||
// register controllers
|
||||
@ -92,7 +93,7 @@ func server() *web.Server {
|
||||
g.Handle("/profile", controller.Profile())
|
||||
g.Handle("/registry", controller.Registry())
|
||||
g.Handle("/node", controller.Node())
|
||||
g.Handle("/service", controller.Service(), biz.Perm)
|
||||
g.Handle("/service", controller.Service(), web.FilterFunc(security.Permiter))
|
||||
g.Handle("/service/template", controller.Template())
|
||||
g.Handle("/stack", controller.Stack())
|
||||
g.Handle("/network", controller.Network())
|
||||
|
@ -2,6 +2,8 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/data"
|
||||
)
|
||||
|
||||
type UserType string
|
||||
@ -13,8 +15,6 @@ const (
|
||||
UserTypeLDAP UserType = "ldap"
|
||||
)
|
||||
|
||||
var Placeholder = struct{}{}
|
||||
|
||||
type UserStatus int32
|
||||
|
||||
const (
|
||||
@ -80,7 +80,7 @@ func NewAuthUser(user *User, roles []*Role) *AuthUser {
|
||||
}
|
||||
for _, role := range roles {
|
||||
for _, perm := range role.Perms {
|
||||
u.perms[perm] = Placeholder
|
||||
u.perms[perm] = data.Empty
|
||||
}
|
||||
}
|
||||
return u
|
||||
|
132
security/auth.go
Normal file
132
security/auth.go
Normal file
@ -0,0 +1,132 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/cache"
|
||||
"github.com/cuigh/auxo/log"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/auxo/security"
|
||||
"github.com/cuigh/auxo/security/certify"
|
||||
"github.com/cuigh/auxo/security/certify/ldap"
|
||||
"github.com/cuigh/auxo/security/passwd"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/model"
|
||||
)
|
||||
|
||||
const pkgName = "swirl.security"
|
||||
|
||||
// Validator is the authenticator function of swirl.
|
||||
func Validator(setting *model.Setting) func(name, pwd string) (ticket string, err error) {
|
||||
ldapRealm := createLDAPRealm(setting)
|
||||
return func(name, pwd string) (ticket string, err error) {
|
||||
var (
|
||||
su security.User
|
||||
mu *model.User
|
||||
)
|
||||
|
||||
// try find user first
|
||||
mu, err = biz.User.GetByName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if mu != nil && mu.Type == model.UserTypeInternal { // internal user
|
||||
if !passwd.Validate(pwd, mu.Password, mu.Salt) {
|
||||
err = certify.ErrInvalidToken
|
||||
}
|
||||
} else if ldapRealm != nil { // user not exist or ldap user
|
||||
su, err = ldapRealm.Login(certify.NewSimpleToken(name, pwd))
|
||||
if err == nil && mu == nil { // create user if not exists
|
||||
lu := su.(*ldap.User)
|
||||
mu = &model.User{
|
||||
Type: model.UserTypeLDAP,
|
||||
LoginName: lu.LoginName(),
|
||||
Name: lu.Name(),
|
||||
Email: lu.Email(),
|
||||
}
|
||||
err = biz.User.Create(mu, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// replace user session, one session is allowed per user.
|
||||
if err == nil {
|
||||
ticket, err = biz.User.UpdateSession(mu.ID)
|
||||
}
|
||||
|
||||
// create event
|
||||
if err == nil {
|
||||
biz.Event.CreateAuthentication(model.EventActionLogin, mu.ID, mu.LoginName, mu.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func createLDAPRealm(setting *model.Setting) certify.Realm {
|
||||
if setting.LDAP.Enabled {
|
||||
opts := []ldap.Option{
|
||||
ldap.NameAttr(setting.LDAP.NameAttr),
|
||||
ldap.EmailAttr(setting.LDAP.EmailAttr),
|
||||
ldap.UserFilter(setting.LDAP.UserFilter),
|
||||
ldap.Security(ldap.SecurityPolicy(setting.LDAP.Security)),
|
||||
}
|
||||
if setting.LDAP.Authentication == 1 {
|
||||
opts = append(opts, ldap.Binding(setting.LDAP.BindDN, setting.LDAP.BindPassword))
|
||||
}
|
||||
return ldap.New(setting.LDAP.Address, setting.LDAP.BaseDN, setting.LDAP.UserDN, opts...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Identifier is used to identity user.
|
||||
func Identifier(token string) (user web.User) {
|
||||
const cacheKey = "auth_user"
|
||||
|
||||
session, err := biz.User.GetSession(token)
|
||||
if err != nil {
|
||||
log.Get(pkgName).Errorf("Load session failed: %v", err)
|
||||
return
|
||||
}
|
||||
if session == nil || session.Expires.Before(time.Now()) {
|
||||
return
|
||||
}
|
||||
|
||||
// try find from cache first
|
||||
value := cache.Get(cacheKey, session.UserID)
|
||||
if !value.IsNil() {
|
||||
user = &model.AuthUser{}
|
||||
if err = value.Scan(user); err == nil {
|
||||
return
|
||||
}
|
||||
log.Get(pkgName).Warnf("Load auth user from cache failed: %v", err)
|
||||
}
|
||||
|
||||
u, err := biz.User.GetByID(session.UserID)
|
||||
if err != nil {
|
||||
log.Get(pkgName).Errorf("Load user failed: %v", err)
|
||||
return
|
||||
}
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var roles []*model.Role
|
||||
if roles, err = getRoles(u); err == nil {
|
||||
user = model.NewAuthUser(u, roles)
|
||||
cache.Set(user, cacheKey, session.UserID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getRoles(u *model.User) (roles []*model.Role, err error) {
|
||||
if len(u.Roles) > 0 {
|
||||
roles = make([]*model.Role, len(u.Roles))
|
||||
for i, id := range u.Roles {
|
||||
roles[i], err = biz.Role.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,15 +1,49 @@
|
||||
package misc
|
||||
package security
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/model"
|
||||
)
|
||||
|
||||
// Checker check permission of user
|
||||
func Checker(user web.User, h web.HandlerInfo) bool {
|
||||
if au, ok := user.(*model.AuthUser); ok {
|
||||
return au.IsAllowed(h.Name())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Permiter is a middleware for validate data permission.
|
||||
func Permiter(next web.HandlerFunc) web.HandlerFunc {
|
||||
return func(ctx web.Context) error {
|
||||
opt := ctx.Handler().Option("perm")
|
||||
if opt != "" {
|
||||
array := strings.Split(opt, ",")
|
||||
err := biz.Perm.Check(ctx.User(), array[0], array[1], ctx.P(array[2]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Perm holds permission key and description.
|
||||
type Perm struct {
|
||||
Key string
|
||||
Text string
|
||||
}
|
||||
|
||||
// PermGroup holds information of a perm group.
|
||||
type PermGroup struct {
|
||||
Name string
|
||||
Perms []Perm
|
||||
}
|
||||
|
||||
// Perms holds all valid perm groups.
|
||||
var Perms = []PermGroup{
|
||||
{
|
||||
Name: "Registry",
|
Loading…
Reference in New Issue
Block a user