diff --git a/Gopkg.lock b/Gopkg.lock index 04168e0..2ec10d7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index acd3a21..20df757 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -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" diff --git a/biz/docker/service.go b/biz/docker/service.go index 000efb1..a4d3524 100644 --- a/biz/docker/service.go +++ b/biz/docker/service.go @@ -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 diff --git a/biz/perm.go b/biz/perm.go index 8d411b0..d9df540 100644 --- a/biz/perm.go +++ b/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) - } -} diff --git a/biz/user.go b/biz/user.go index 2f35e41..dbd8918 100644 --- a/biz/user.go +++ b/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 } diff --git a/controller/perm.go b/controller/perm.go index f0392b8..80c38ad 100644 --- a/controller/perm.go +++ b/controller/perm.go @@ -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 diff --git a/controller/role.go b/controller/role.go index 9cd3f75..fa9e1c7 100644 --- a/controller/role.go +++ b/controller/role.go @@ -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) } diff --git a/controller/service.go b/controller/service.go index 034593a..c331cdd 100644 --- a/controller/service.go +++ b/controller/service.go @@ -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). diff --git a/controller/template.go b/controller/template.go index 5b9ab25..5d61920 100644 --- a/controller/template.go +++ b/controller/template.go @@ -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) diff --git a/controller/user.go b/controller/user.go index b6e4d97..a62c0bd 100644 --- a/controller/user.go +++ b/controller/user.go @@ -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) diff --git a/main.go b/main.go index 4c4516e..194a546 100644 --- a/main.go +++ b/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()) diff --git a/model/auth.go b/model/auth.go index ebabd9c..72e4a64 100644 --- a/model/auth.go +++ b/model/auth.go @@ -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 diff --git a/security/auth.go b/security/auth.go new file mode 100644 index 0000000..662206e --- /dev/null +++ b/security/auth.go @@ -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 +} diff --git a/misc/perm.go b/security/perm.go similarity index 86% rename from misc/perm.go rename to security/perm.go index 738ee90..0cecb66 100644 --- a/misc/perm.go +++ b/security/perm.go @@ -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",