mirror of
https://github.com/cuigh/swirl
synced 2024-12-30 15:53:24 +00:00
307 lines
6.5 KiB
Go
307 lines
6.5 KiB
Go
|
package security
|
||
|
|
||
|
import (
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/cuigh/auxo/errors"
|
||
|
"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/misc"
|
||
|
"github.com/cuigh/swirl/model"
|
||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
|
)
|
||
|
|
||
|
// Identifier identifies the user.
|
||
|
type Identifier struct {
|
||
|
ub biz.UserBiz
|
||
|
rb biz.RoleBiz
|
||
|
sb biz.SessionBiz
|
||
|
realms []RealmFunc
|
||
|
extractors []TokenExtractor
|
||
|
logger log.Logger
|
||
|
}
|
||
|
|
||
|
func NewIdentifier(s *misc.Setting, ub biz.UserBiz, rb biz.RoleBiz, sb biz.SessionBiz) *Identifier {
|
||
|
return &Identifier{
|
||
|
ub: ub,
|
||
|
rb: rb,
|
||
|
sb: sb,
|
||
|
realms: []RealmFunc{internalRealm(), ldapRealm(s, ub)},
|
||
|
extractors: []TokenExtractor{headerExtractor, queryExtractor},
|
||
|
logger: log.Get(PkgName),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) Apply(next web.HandlerFunc) web.HandlerFunc {
|
||
|
return func(ctx web.Context) error {
|
||
|
token := c.extractToken(ctx)
|
||
|
if token != "" {
|
||
|
user := c.identifyUser(token)
|
||
|
ctx.SetUser(user)
|
||
|
}
|
||
|
return next(ctx)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) Identify(loginName, password string) (identify Identity, err error) {
|
||
|
var (
|
||
|
u security.User
|
||
|
s *model.Session
|
||
|
)
|
||
|
|
||
|
u, err = c.signIn(loginName, password)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
s, err = c.createSession(u)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &UserInfo{
|
||
|
id: u.ID(),
|
||
|
name: u.Name(),
|
||
|
token: s.ID,
|
||
|
perms: s.Perms,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) signIn(loginName, password string) (user security.User, err error) {
|
||
|
privacy, err := c.ub.FindPrivacy(loginName)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if privacy != nil && privacy.Status == biz.UserStatusBlocked {
|
||
|
return nil, misc.Error(misc.ErrAccountDisabled, certify.ErrAccountDisabled)
|
||
|
}
|
||
|
|
||
|
for _, login := range c.realms {
|
||
|
user, err = login(privacy, loginName, password)
|
||
|
if user != nil && err == nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err == nil {
|
||
|
err = misc.Error(misc.ErrInvalidToken, certify.ErrInvalidToken)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) extractToken(ctx web.Context) (token string) {
|
||
|
for _, e := range c.extractors {
|
||
|
if token = e(ctx); token != "" {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) identifyUser(token string) web.User {
|
||
|
session, err := c.sb.Find(token)
|
||
|
if err != nil {
|
||
|
c.logger.Error("failed to find session: ", err)
|
||
|
return nil
|
||
|
} else if session == nil || session.Expiry.Before(time.Now()) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if session.Dirty {
|
||
|
if err = c.updateSession(session); err != nil {
|
||
|
c.logger.Error("failed to refresh session: ", err)
|
||
|
return nil
|
||
|
}
|
||
|
} else if time.Now().Add(time.Minute * 5).After(session.Expiry) {
|
||
|
c.renewSession(session)
|
||
|
}
|
||
|
|
||
|
return c.createUser(session)
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) createUser(s *model.Session) web.User {
|
||
|
return &User{
|
||
|
token: s.ID,
|
||
|
id: s.UserID,
|
||
|
name: s.Username,
|
||
|
admin: s.Admin,
|
||
|
perm: PermMap(s.Perm),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) createSession(user security.User) (s *model.Session, err error) {
|
||
|
s = &model.Session{
|
||
|
ID: primitive.NewObjectID().Hex(),
|
||
|
UserID: user.ID(),
|
||
|
Username: user.Name(),
|
||
|
Expiry: time.Now().Add(misc.Options.TokenExpiry),
|
||
|
}
|
||
|
s.MaxExpiry = s.Expiry.Add(24 * time.Hour)
|
||
|
if err = c.fillSession(s); err == nil {
|
||
|
err = c.sb.Create(s)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) updateSession(s *model.Session) (err error) {
|
||
|
if err = c.fillSession(s); err == nil {
|
||
|
err = c.sb.Update(s)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) fillSession(s *model.Session) (err error) {
|
||
|
u, err := c.ub.FindByID(s.UserID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
} else if u == nil {
|
||
|
return errors.New("user not found")
|
||
|
}
|
||
|
|
||
|
if u.Admin {
|
||
|
s.Perms = []string{"*"}
|
||
|
} else {
|
||
|
s.Perms, err = c.rb.GetPerms(u.Roles)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
s.Perm = uint64(NewPermMap(s.Perms))
|
||
|
}
|
||
|
|
||
|
s.Username = u.Name
|
||
|
s.Admin = u.Admin
|
||
|
s.Roles = u.Roles
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Identifier) renewSession(s *model.Session) {
|
||
|
expiry := time.Now().Add(misc.Options.TokenExpiry)
|
||
|
if expiry.After(s.MaxExpiry) {
|
||
|
expiry = s.MaxExpiry
|
||
|
}
|
||
|
err := c.sb.UpdateExpiry(s.ID, expiry)
|
||
|
if err != nil {
|
||
|
c.logger.Errorf("failed to renew token '%s': %s", s.ID, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type TokenExtractor func(ctx web.Context) string
|
||
|
|
||
|
func headerExtractor(ctx web.Context) (token string) {
|
||
|
const prefix = "Bearer "
|
||
|
if value := ctx.Header(web.HeaderAuthorization); strings.HasPrefix(value, prefix) {
|
||
|
token = value[len(prefix):]
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func queryExtractor(ctx web.Context) (token string) {
|
||
|
return ctx.Query("token")
|
||
|
}
|
||
|
|
||
|
type RealmFunc func(u *biz.UserPrivacy, loginName, password string) (security.User, error)
|
||
|
|
||
|
func internalRealm() RealmFunc {
|
||
|
return func(u *biz.UserPrivacy, loginName, password string) (security.User, error) {
|
||
|
if u == nil || u.Type != biz.UserTypeInternal {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if passwd.Validate(password, u.Password, u.Salt) {
|
||
|
return security.NewUser(u.ID, u.Name), nil
|
||
|
}
|
||
|
return nil, misc.Error(misc.ErrInvalidToken, certify.ErrInvalidToken)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ldapRealm(s *misc.Setting, ub biz.UserBiz) RealmFunc {
|
||
|
const authBind = "bind"
|
||
|
var r certify.Realm
|
||
|
|
||
|
if s.LDAP.Enabled {
|
||
|
opts := []ldap.Option{
|
||
|
ldap.NameAttr(s.LDAP.NameAttr),
|
||
|
ldap.EmailAttr(s.LDAP.EmailAttr),
|
||
|
ldap.UserFilter(s.LDAP.UserFilter),
|
||
|
ldap.Security(ldap.SecurityPolicy(s.LDAP.Security)),
|
||
|
}
|
||
|
if s.LDAP.Authentication == authBind {
|
||
|
opts = append(opts, ldap.Binding(s.LDAP.BindDN, s.LDAP.BindPassword))
|
||
|
}
|
||
|
r = ldap.New(s.LDAP.Address, s.LDAP.BaseDN, s.LDAP.UserDN, opts...)
|
||
|
}
|
||
|
|
||
|
return func(u *biz.UserPrivacy, loginName, password string) (security.User, error) {
|
||
|
if r == nil || (u != nil && u.Type != biz.UserTypeLDAP) {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
user, err := r.Login(certify.NewSimpleToken(loginName, password))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
id string
|
||
|
lu = user.(*ldap.User)
|
||
|
)
|
||
|
if u == nil {
|
||
|
id, err = ub.Create(&model.User{
|
||
|
Type: biz.UserTypeLDAP,
|
||
|
LoginName: loginName,
|
||
|
Name: lu.Name(),
|
||
|
Email: lu.Email(),
|
||
|
}, nil)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
lu.SetID(id)
|
||
|
} else {
|
||
|
lu.SetID(u.ID)
|
||
|
}
|
||
|
return user, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type Identity interface {
|
||
|
ID() string
|
||
|
Name() string
|
||
|
Anonymous() bool
|
||
|
Token() string
|
||
|
Perms() []string
|
||
|
}
|
||
|
|
||
|
type UserInfo struct {
|
||
|
id string
|
||
|
name string
|
||
|
token string
|
||
|
perms []string
|
||
|
}
|
||
|
|
||
|
func (u *UserInfo) ID() string {
|
||
|
return u.id
|
||
|
}
|
||
|
|
||
|
func (u *UserInfo) Name() string {
|
||
|
return u.name
|
||
|
}
|
||
|
|
||
|
func (u *UserInfo) Anonymous() bool {
|
||
|
return u.id == ""
|
||
|
}
|
||
|
|
||
|
func (u *UserInfo) Token() string {
|
||
|
return u.token
|
||
|
}
|
||
|
|
||
|
func (u *UserInfo) Perms() []string {
|
||
|
return u.perms
|
||
|
}
|