diff --git a/api/api.go b/api/api.go index 9a25ec4..5acc86f 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "OneAuth/api/app" + "OneAuth/api/role" "OneAuth/api/user" "OneAuth/api/wx" "github.com/veypi/OneBD" @@ -18,5 +19,7 @@ func Router(r OneBD.Router) { user.Router(r.SubRouter("/user")) wx.Router(r.SubRouter("wx")) app.Router(r.SubRouter("app")) + role.Router(r) + //message.Router(r.SubRouter("/message")) } diff --git a/api/app/app.go b/api/app/app.go index 4e950be..0faf2c7 100644 --- a/api/app/app.go +++ b/api/app/app.go @@ -2,6 +2,7 @@ package app import ( "OneAuth/cfg" + "OneAuth/libs/auth" "OneAuth/libs/base" "OneAuth/libs/oerr" "OneAuth/models" @@ -26,14 +27,40 @@ type appHandler struct { func (h *appHandler) Get() (interface{}, error) { id := h.Meta().Params("id") - if id == "" { - return nil, oerr.ApiArgsMissing - } h.query = &models.App{} - h.query.UUID = id - err := cfg.DB().Where(h.query).First(h.query).Error + isSelf := h.Meta().Query("is_self") + if isSelf != "" { + // 无权限可以获取本系统基本信息 + h.query.ID = cfg.CFG.APPID + err := cfg.DB().Where(h.query).First(h.query).Error + return h.query, err + } + err := h.ParsePayload(h.Meta()) if err != nil { return nil, err } - return h.query, nil + if !h.GetAuth(auth.APP, id).CanRead() { + return nil, oerr.NoAuth + } + if id != "" { + h.query.UUID = id + err := cfg.DB().Where(h.query).First(h.query).Error + return h.query, err + } + // 注释代码为获取已经绑定的应用 + //user := &models.User{} + //user.ID = h.Payload.ID + //err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(user).First(user).Error + //if err != nil { + // return nil, oerr.DBErr.Attach(err) + //} + //ids := make([]string, 0, 10) + //for _, a := range user.GetAuths() { + // if a.RID == auth.Login && a.Level.CanDo() { + // ids = append(ids, a.RUID) + // } + //} + list := make([]*models.App, 0, 10) + err = cfg.DB().Find(&list).Error + return list, err } diff --git a/api/role/auth.go b/api/role/auth.go index 7d3996f..4c2a8a3 100644 --- a/api/role/auth.go +++ b/api/role/auth.go @@ -2,7 +2,9 @@ package role import ( "OneAuth/cfg" + "OneAuth/libs/auth" "OneAuth/libs/base" + "OneAuth/libs/oerr" "OneAuth/models" "github.com/veypi/OneBD" "github.com/veypi/OneBD/core" @@ -17,6 +19,9 @@ type authHandler struct { } func (h *authHandler) Get() (interface{}, error) { + if !h.GetAuth(auth.Auth).CanRead() { + return nil, oerr.NoAuth + } l := make([]*models.Auth, 0, 10) return &l, cfg.DB().Find(&l).Error } diff --git a/api/role/role.go b/api/role/role.go index 1bbefd7..d4bb1b3 100644 --- a/api/role/role.go +++ b/api/role/role.go @@ -2,6 +2,7 @@ package role import ( "OneAuth/cfg" + "OneAuth/libs/auth" "OneAuth/libs/base" "OneAuth/libs/oerr" "OneAuth/models" @@ -20,8 +21,9 @@ type roleHandler struct { func (h *roleHandler) Get() (interface{}, error) { id := h.Meta().ParamsInt("id") - aid := h.Meta().ParamsInt("aid") - act := h.Meta().Params("action") + if !h.GetAuth(auth.Role, h.Meta().Params("id")).CanRead() { + return nil, oerr.NoAuth + } if id > 0 { role := &models.Role{} role.ID = uint(id) @@ -29,21 +31,6 @@ func (h *roleHandler) Get() (interface{}, error) { if err != nil { return nil, err } - if aid <= 0 { - return role, nil - } - if !h.CheckAuth("admin", "").CanDoAny() { - return nil, oerr.NoAuth - } - at := &models.RoleAuth{} - at.RoleID = role.ID - at.AuthID = uint(aid) - defer models.SyncGlobalRoles() - if act == "bind" { - err = cfg.DB().Where(at).FirstOrCreate(at).Error - } else if act == "unbind" { - err = cfg.DB().Where(at).Delete(at).Error - } return role, nil } roles := make([]*models.Role, 0, 10) @@ -52,7 +39,7 @@ func (h *roleHandler) Get() (interface{}, error) { } func (h *roleHandler) Post() (interface{}, error) { - if !h.CheckAuth("role", "").CanCreate() { + if !h.GetAuth(auth.Role).CanCreate() { return nil, oerr.NoAuth } role := &models.Role{} @@ -61,15 +48,14 @@ func (h *roleHandler) Post() (interface{}, error) { return nil, err } role.ID = 0 - if role.Category == 0 || role.Name == "" { + if role.Name == "" { return nil, oerr.ApiArgsMissing } - defer models.SyncGlobalRoles() return role, cfg.DB().Where(role).FirstOrCreate(role).Error } func (h *roleHandler) Patch() (interface{}, error) { - if !h.CheckAuth("role", "").CanUpdate() { + if !h.GetAuth(auth.Role).CanUpdate() { return nil, oerr.NoAuth } query := &struct { @@ -120,7 +106,7 @@ func (h *roleHandler) Patch() (interface{}, error) { } func (h *roleHandler) Delete() (interface{}, error) { - if !h.CheckAuth("role").CanDelete() { + if !h.GetAuth(auth.Role).CanDelete() { return nil, oerr.NoAuth } rid := h.Meta().ParamsInt("id") @@ -133,6 +119,5 @@ func (h *roleHandler) Delete() (interface{}, error) { if err != nil { return nil, err } - defer models.SyncGlobalRoles() return nil, cfg.DB().Delete(role).Error } diff --git a/api/role/router.go b/api/role/router.go index 712bf03..bf17337 100644 --- a/api/role/router.go +++ b/api/role/router.go @@ -8,6 +8,6 @@ import ( func Router(r OneBD.Router) { r.Set("/role/", roleP, rfc.MethodGet, rfc.MethodPost) r.Set("/role/:id", roleP, rfc.MethodGet, rfc.MethodDelete, rfc.MethodPatch) - r.Set("/role/:id/:action/:aid", roleP, rfc.MethodGet) + r.Set("/role/:id/:action/:rid", roleP, rfc.MethodGet) r.Set("/auth/", authP, rfc.MethodGet) } diff --git a/api/user/user.go b/api/user/user.go index 4115131..3faf9f9 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -2,9 +2,13 @@ package user import ( "OneAuth/cfg" + "OneAuth/libs/app" "OneAuth/libs/base" "OneAuth/libs/oerr" + "OneAuth/libs/token" "OneAuth/models" + "errors" + //"OneAuth/ws" "encoding/base64" "fmt" @@ -68,10 +72,17 @@ func (h *handler) Get() (interface{}, error) { // Post register user func (h *handler) Post() (interface{}, error) { - if !cfg.CFG.EnableRegister { - return nil, oerr.NoAuth.AttachStr("register disabled.") + self := &models.App{} + self.ID = cfg.CFG.APPID + err := cfg.DB().Where(self).First(self).Error + if err != nil { + return nil, oerr.DBErr.Attach(err) + } + if !self.EnableRegister { + return nil, oerr.NoAuth.AttachStr("register disabled") } var userdata = struct { + UUID string `json:"uuid"` Username string `json:"username"` Password string `json:"password"` Nickname string `json:"nickname"` @@ -101,16 +112,40 @@ func (h *handler) Post() (interface{}, error) { h.User.Username = userdata.Username h.User.Email = userdata.Email h.User.Position = userdata.Position - if err := h.User.UpdateAuth(string(pass)); err != nil { + if err := h.User.UpdatePass(string(pass)); err != nil { log.HandlerErrs(err) return nil, oerr.ResourceCreatedFailed } - tx := cfg.DB().Begin() - if err := tx.Create(&h.User).Error; err != nil { - tx.Rollback() - return nil, oerr.ResourceDuplicated + err = cfg.DB().Transaction(func(tx *gorm.DB) error { + if err := tx.Create(&h.User).Error; err != nil { + return oerr.ResourceDuplicated + } + err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID) + if err != nil { + return err + } + if userdata.UUID != self.UUID && userdata.UUID != "" { + target := &models.App{} + target.UUID = userdata.UUID + err = tx.Where(target).First(target).Error + if err != nil { + return err + } + if target.EnableRegister { + err := app.AddUser(tx, target.ID, h.User.ID, target.InitRoleID) + if err != nil { + return err + } + } else { + // TODO + } + return err + } + return nil + }) + if err != nil { + return nil, err } - tx.Commit() return h.User, nil } @@ -142,7 +177,7 @@ func (h *handler) Patch() (interface{}, error) { return nil, oerr.NoAuth } if len(opts.Password) >= 6 { - if err := target.UpdateAuth(opts.Password); err != nil { + if err := target.UpdatePass(opts.Password); err != nil { log.HandlerErrs(err) return nil, oerr.ApiArgsError.AttachStr(err.Error()) } @@ -187,9 +222,9 @@ func (h *handler) Head() (interface{}, error) { if len(uid) == 0 || len(password) == 0 { return nil, oerr.ApiArgsError } - appID, err := strconv.Atoi(h.Meta().Query("app_id")) - if err != nil || appID <= 0 { - return nil, oerr.ApiArgsMissing + appUUID := h.Meta().Query("uuid") + if appUUID == "" { + return nil, oerr.ApiArgsMissing.AttachStr("uuid") } h.User = new(models.User) uidType := h.Meta().Query("uid_type") @@ -203,32 +238,15 @@ func (h *handler) Head() (interface{}, error) { default: h.User.Username = uid } - app := &models.App{} - app.ID = uint(appID) - err = cfg.DB().Where(app).Find(app).Error + target := &models.App{} + target.UUID = appUUID + err = cfg.DB().Where(target).Find(target).Error if err != nil { return nil, oerr.DBErr.Attach(err) } - if err := cfg.DB().Preload("Roles").Where(h.User).First(h.User).Error; err != nil { + if err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(h.User).First(h.User).Error; err != nil { if err.Error() == gorm.ErrRecordNotFound.Error() { - // admin 登录自动注册 - if h.User.Username == "admin" { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - h.User.Icon = fmt.Sprintf("/media/icon/default/%04d.jpg", r.Intn(230)) - err = h.User.UpdateAuth(password) - if err != nil { - return nil, err - } - role := &models.Role{} - role.ID = 1 - h.User.Roles = []*models.Role{role} - err = cfg.DB().Create(h.User).Error - if err != nil { - return nil, err - } - } else { - return nil, oerr.AccountNotExist - } + return nil, oerr.AccountNotExist } else { log.HandlerErrs(err) return nil, oerr.DBErr.Attach(err) @@ -238,15 +256,27 @@ func (h *handler) Head() (interface{}, error) { if err != nil || !isAuth { return nil, oerr.PassError.Attach(err) } - if h.User.Status == "disabled" { + au := &models.AppUser{} + au.UserID = h.User.ID + au.AppID = target.ID + err = cfg.DB().Where(au).First(au).Error + appID := target.ID + h.Meta().SetHeader("content", target.UserRefreshUrl) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + appID = cfg.CFG.APPID + h.Meta().SetHeader("content", "/app/"+target.UUID) + } else if au.Disabled { return nil, oerr.DisableLogin } - token, err := h.User.GetToken(app.Key, app.ID) + tokenStr, err := token.GetToken(h.User, appID) if err != nil { log.HandlerErrs(err) return nil, oerr.Unknown.Attach(err) } - h.Meta().SetHeader("auth_token", token) + h.Meta().SetHeader("auth_token", tokenStr) log.Info().Msg(h.User.Username + " login") return nil, nil } diff --git a/api/user/user_role.go b/api/user/user_role.go index be0673c..861958c 100644 --- a/api/user/user_role.go +++ b/api/user/user_role.go @@ -35,9 +35,8 @@ func (h *userRoleHandler) Post() (interface{}, error) { err = cfg.DB().First(query, query.ID).Error } else if query.Name != "" { err = cfg.DB().Where(map[string]interface{}{ - "name": query.Name, - "category": query.Category, - "tag": query.Tag, + "name": query.Name, + "tag": query.Tag, }).First(query).Error if errors.Is(err, gorm.ErrRecordNotFound) { err = cfg.DB().Create(query).Error diff --git a/cfg/cfg.go b/cfg/cfg.go index ba2d52a..05719cb 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -8,19 +8,19 @@ import ( "gorm.io/gorm" ) -var Path = cmd.GetCfgPath("OneAuth", "oa") +var Path = cmd.GetCfgPath("oa", "settings") var CFG = &struct { - AdminUser string - Host string - LoggerPath string - LoggerLevel string - Key string - TimeFormat string - Debug bool - EXEDir string - EnableRegister bool - DB struct { + AdminUser string + Host string + LoggerPath string + LoggerLevel string + APPID uint + APPKey string + TimeFormat string + Debug bool + EXEDir string + DB struct { Type string Addr string User string @@ -28,14 +28,14 @@ var CFG = &struct { DB string } }{ - AdminUser: "admin", - Host: "0.0.0.0:4001", - LoggerPath: "", - LoggerLevel: "debug", - TimeFormat: "2006/01/02 15:04:05", - Debug: true, - EXEDir: "./", - EnableRegister: true, + APPID: 1, + AdminUser: "admin", + Host: "0.0.0.0:4001", + LoggerPath: "", + LoggerLevel: "debug", + TimeFormat: "2006/01/02 15:04:05", + Debug: true, + EXEDir: "./", DB: struct { Type string Addr string diff --git a/libs/app/user.go b/libs/app/user.go new file mode 100644 index 0000000..65424f8 --- /dev/null +++ b/libs/app/user.go @@ -0,0 +1,48 @@ +package app + +import ( + "OneAuth/libs/auth" + "OneAuth/libs/oerr" + "OneAuth/models" + "errors" + "gorm.io/gorm" +) + +func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error { + au := &models.AppUser{} + au.AppID = appID + au.UserID = userID + err := tx.Where(au).First(au).Error + if err == nil { + return oerr.ResourceDuplicated + } + if errors.Is(err, gorm.ErrRecordNotFound) { + err = tx.Create(au).Error + if err != nil { + return err + } + err = auth.BindUserRole(tx, userID, roleID) + if err != nil { + return err + } + return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error + } + return err +} +func EnableUser(tx *gorm.DB, appID uint, userID uint) error { + au := &models.AppUser{} + au.AppID = appID + au.UserID = userID + err := tx.Where(au).First(au).Error + if err != nil { + return err + } + return tx.Where(au).Update("disabled", false).Error +} + +func DisableUser(tx *gorm.DB, appID uint, userID uint) error { + au := &models.AppUser{} + au.AppID = appID + au.UserID = userID + return tx.Where(au).Update("disabled", true).Error +} diff --git a/libs/auth/auth.go b/libs/auth/auth.go new file mode 100644 index 0000000..22d4b4c --- /dev/null +++ b/libs/auth/auth.go @@ -0,0 +1,66 @@ +package auth + +import ( + "OneAuth/models" + "gorm.io/gorm" +) + +// 定义oa系统权限 + +type Resource = string + +const ( + User Resource = "user" + APP Resource = "app" + Res Resource = "resource" + Role Resource = "role" + Auth Resource = "auth" +) + +func BindUserRole(tx *gorm.DB, userID uint, roleID uint) error { + r := &models.Role{} + r.ID = roleID + err := tx.Where(r).First(r).Error + if err != nil { + return err + } + ur := &models.UserRole{} + ur.RoleID = roleID + if r.IsUnique { + err = tx.Where(ur).Update("user_id", userID).Error + } else { + ur.UserID = userID + err = tx.Where(ur).FirstOrCreate(ur).Error + } + return err +} + +func BindUserAuth(tx *gorm.DB, userID uint, resID uint, level models.AuthLevel, ruid string) error { + return bind(tx, userID, resID, level, ruid, false) +} + +func BindRoleAuth(tx *gorm.DB, roleID uint, resID uint, level models.AuthLevel, ruid string) error { + return bind(tx, roleID, resID, level, ruid, true) +} + +func bind(tx *gorm.DB, id uint, resID uint, level models.AuthLevel, ruid string, isRole bool) error { + r := &models.Resource{} + r.ID = resID + err := tx.Where(r).First(r).Error + if err != nil { + return err + } + au := &models.Auth{ + AppID: r.AppID, + ResourceID: resID, + RID: r.Name, + RUID: ruid, + Level: level, + } + if isRole { + au.RoleID = &id + } else { + au.UserID = &id + } + return tx.Where(au).FirstOrCreate(au).Error +} diff --git a/libs/base/api_handler.go b/libs/base/api_handler.go index 464b787..116a514 100644 --- a/libs/base/api_handler.go +++ b/libs/base/api_handler.go @@ -1,7 +1,6 @@ package base import ( - "OneAuth/libs/auth" "OneAuth/libs/oerr" "OneAuth/libs/tools" "github.com/json-iterator/go" @@ -17,7 +16,7 @@ var json = jsoniter.ConfigFastest type ApiHandler struct { OneBD.BaseHandler - auth.UserHandler + UserHandler } func (h *ApiHandler) Init(m OneBD.Meta) error { diff --git a/libs/auth/user_handler.go b/libs/base/user_handler.go similarity index 71% rename from libs/auth/user_handler.go rename to libs/base/user_handler.go index 6be1042..29d43ba 100644 --- a/libs/auth/user_handler.go +++ b/libs/base/user_handler.go @@ -1,15 +1,15 @@ -package auth +package base import ( - "OneAuth/cfg" "OneAuth/libs/oerr" + "OneAuth/libs/token" "OneAuth/models" "github.com/veypi/OneBD" "github.com/veypi/OneBD/rfc" ) type UserHandler struct { - Payload *models.PayLoad + Payload *token.PayLoad ignoreMethod map[rfc.Method]bool } @@ -17,12 +17,16 @@ func (a *UserHandler) Init(m OneBD.Meta) error { if a.ignoreMethod != nil && a.ignoreMethod[m.Method()] { return nil } - a.Payload = new(models.PayLoad) - token := m.GetHeader("auth_token") - if token == "" { + return a.ParsePayload(m) +} + +func (a *UserHandler) ParsePayload(m OneBD.Meta) error { + a.Payload = new(token.PayLoad) + tokenStr := m.GetHeader("auth_token") + if tokenStr == "" { return oerr.NotLogin } - ok, err := models.ParseToken(token, cfg.CFG.Key, a.Payload) + ok, err := token.ParseToken(tokenStr, a.Payload) if ok { return nil } diff --git a/libs/key/app.go b/libs/key/app.go new file mode 100644 index 0000000..b1953c9 --- /dev/null +++ b/libs/key/app.go @@ -0,0 +1,11 @@ +package key + +import "OneAuth/cfg" + +func App(id uint) string { + if id == cfg.CFG.APPID { + return cfg.CFG.APPKey + } + // TODO + return "" +} diff --git a/libs/auth/user_key.go b/libs/key/user.go similarity index 55% rename from libs/auth/user_key.go rename to libs/key/user.go index 4f6fbf3..4d112da 100644 --- a/libs/auth/user_key.go +++ b/libs/key/user.go @@ -1,24 +1,24 @@ -package auth +package key import ( - "OneAuth/models" + "OneAuth/cfg" "github.com/veypi/utils" "sync" ) var keyCache = sync.Map{} -func GetUserKey(uid uint, app *models.App) string { - if app.ID == 1 { +func User(uid uint, appID uint) string { + if appID == cfg.CFG.APPID { key, _ := keyCache.LoadOrStore(uid, utils.RandSeq(16)) - return key.(string) + return cfg.CFG.APPKey + key.(string) } // TODO: 获取其他应用user_key return "" } -func RefreshUserKey(uid uint, app *models.App) string { - if app.ID == 1 { +func RefreshUser(uid uint, appID uint) string { + if appID == cfg.CFG.APPID { key := utils.RandSeq(16) keyCache.Store(uid, key) return key diff --git a/libs/token/user.go b/libs/token/user.go new file mode 100644 index 0000000..8da80a8 --- /dev/null +++ b/libs/token/user.go @@ -0,0 +1,127 @@ +package token + +import ( + "OneAuth/libs/key" + "OneAuth/models" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "strings" + "time" +) + +var ( + InvalidToken = errors.New("invalid token") + ExpiredToken = errors.New("expired token") +) + +type simpleAuth struct { + RID string `json:"rid"` + // 具体某个资源的id + RUID string `json:"ruid"` + Level models.AuthLevel `json:"level"` +} + +// TODO:: roles 是否会造成token过大 ? +type PayLoad struct { + ID uint `json:"id"` + AppID uint `json:"app_id"` + Iat int64 `json:"iat"` //token time + Exp int64 `json:"exp"` + Auth map[uint]*simpleAuth `json:"auth"` +} + +// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 +func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.AuthLevel { + res := models.AuthNone + if p == nil || p.Auth == nil { + return res + } + ruid := "" + if len(ResourceUUID) > 0 { + ruid = ResourceUUID[0] + } + for _, a := range p.Auth { + if a.RID == ResourceID { + if a.RUID != "" { + if a.RUID == ruid { + if a.Level > res { + res = a.Level + } + } else { + continue + } + } else if a.Level > res { + res = a.Level + } + } + } + return res +} + +func GetToken(u *models.User, appID uint) (string, error) { + header := map[string]string{ + "typ": "JWT", + "alg": "HS256", + } + //header := "{\"typ\": \"JWT\", \"alg\": \"HS256\"}" + now := time.Now().Unix() + payload := PayLoad{ + ID: u.ID, + AppID: appID, + Iat: now, + Exp: now + 60*60*24, + Auth: map[uint]*simpleAuth{}, + } + for _, a := range u.GetAuths() { + if appID == a.AppID { + payload.Auth[a.ID] = &simpleAuth{ + RID: a.RID, + RUID: a.RUID, + Level: a.Level, + } + } + } + a, err := json.Marshal(header) + if err != nil { + return "", err + } + b, err := json.Marshal(payload) + if err != nil { + return "", err + } + A := base64.StdEncoding.EncodeToString(a) + B := base64.StdEncoding.EncodeToString(b) + hmacCipher := hmac.New(sha256.New, []byte(key.User(payload.ID, payload.AppID))) + hmacCipher.Write([]byte(A + "." + B)) + C := hmacCipher.Sum(nil) + return A + "." + B + "." + base64.StdEncoding.EncodeToString(C), nil +} + +func ParseToken(token string, payload *PayLoad) (bool, error) { + var A, B, C string + if seqs := strings.Split(token, "."); len(seqs) == 3 { + A, B, C = seqs[0], seqs[1], seqs[2] + } else { + return false, InvalidToken + } + tempPayload, err := base64.StdEncoding.DecodeString(B) + if err != nil { + return false, err + } + if err := json.Unmarshal(tempPayload, payload); err != nil { + return false, err + } + hmacCipher := hmac.New(sha256.New, []byte(key.User(payload.ID, payload.AppID))) + hmacCipher.Write([]byte(A + "." + B)) + tempC := hmacCipher.Sum(nil) + if !hmac.Equal([]byte(C), []byte(base64.StdEncoding.EncodeToString(tempC))) { + return false, nil + } + if time.Now().Unix() > payload.Exp { + return false, ExpiredToken + } + return true, nil +} diff --git a/main.go b/main.go index ec2cd06..4e5c489 100644 --- a/main.go +++ b/main.go @@ -35,10 +35,15 @@ func main() { Value: cfg.CFG.LoggerPath, Destination: &cfg.CFG.LoggerPath, }, + &cli.UintFlag{ + Name: "id", + Value: cfg.CFG.APPID, + Destination: &cfg.CFG.APPID, + }, &cli.StringFlag{ Name: "key", - Value: cfg.CFG.Key, - Destination: &cfg.CFG.Key, + Value: cfg.CFG.APPKey, + Destination: &cfg.CFG.APPKey, }, &cli.StringFlag{ Name: "exe_dir", @@ -52,7 +57,11 @@ func main() { }, } app.Commands = []*cli.Command{ - &sub.Web, + sub.Web, + sub.App, + sub.Role, + sub.Resource, + sub.Init, } srv, err := cmd.NewSrv(app, sub.RunWeb, cfg.CFG, cfg.Path) if err != nil { diff --git a/models/app.go b/models/app.go index c34951d..e7fa0cd 100644 --- a/models/app.go +++ b/models/app.go @@ -4,9 +4,20 @@ var AppKeys = map[string]string{} type App struct { BaseModel - Name string `json:"name"` - Icon string `json:"icon"` - UUID string `json:"uuid"` + Name string `json:"name"` + Icon string `json:"icon"` + UUID string `json:"uuid" gorm:"unique"` + Des string `json:"des"` + Creator uint `json:"creator"` + UserCount uint `json:"user_count"` + Users []*User `json:"users" gorm:"many2many:app_users;"` + // 初始用户角色 + InitRoleID uint `json:"init_role_id"` + InitRole *Role `json:"init_role"` + // 是否在首页隐藏 + Hide bool `json:"hide"` + // PubKey string `json:"pub_key"` + // PrivateKey string `json:"private_key"` // 认证成功跳转链接 Host string `json:"host"` // 加解密用户token (key+key2) @@ -14,11 +25,13 @@ type App struct { // key oa发放给app 双方保存 针对app生成 每个应用有一个 // key2 app发放给oa app保存 oa使用一次销毁 针对当个用户生成 每个用户有一个 // 获取app用户加密秘钥key2 + // TODO UserRefreshUrl string `json:"user_refresh_url"` // app 校验用户token时使用 - Key string `json:"key"` - // 是否允许用户注册 - EnableRegister string `json:"enable_register"` + Key string `json:"-"` + // 是否允许用户自主注册 + EnableRegister bool `json:"enable_register"` + EnableUserKey bool `json:"enable_user_key"` EnableUser bool `json:"enable_user"` EnableWx bool `json:"enable_wx"` EnablePhone bool `json:"enable_phone"` @@ -26,6 +39,14 @@ type App struct { Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"` } +type AppUser struct { + BaseModel + AppID uint `json:"app_id"` + UserID uint `json:"user_id"` + Disabled bool `json:"disabled"` + Status string `json:"status"` +} + type Wechat struct { BaseModel AppID uint `json:"app_id"` diff --git a/models/role.go b/models/role.go index 751fe82..0ae083b 100644 --- a/models/role.go +++ b/models/role.go @@ -8,19 +8,22 @@ type UserRole struct { type Role struct { BaseModel - Name string `json:"name"` - // 角色类型 - // 1: 系统定义角色 2: 用户自定义角色 - Category uint `json:"category" gorm:"default:1"` + AppID uint `json:"app_id"` + App *App `json:"app"` + Name string `json:"name"` // 角色标签 Tag string `json:"tag" gorm:"default:''"` - Users []*User `json:"users" gorm:"many2many:user_role;"` + Users []*User `json:"users" gorm:"many2many:user_roles;"` // 具体权限 Auths []*Auth `json:"auths" gorm:"foreignkey:RoleID;references:ID"` IsUnique bool `json:"is_unique" gorm:"default:false"` } // AuthLevel 权限等级 +// 对于操作类权限 +// 0 禁止执行 +// 1 允许执行 +// 对于资源类权限 // 0 相当于没有 // 1 有限读权限 // 2 读权限 @@ -32,6 +35,7 @@ type AuthLevel uint const ( AuthNone AuthLevel = 0 + AuthDo AuthLevel = 1 // AuthPart TODO: 临时权限 AuthPart AuthLevel = 1 AuthRead AuthLevel = 2 @@ -41,6 +45,14 @@ const ( AuthAll AuthLevel = 6 ) +func (a AuthLevel) Upper(b AuthLevel) bool { + return a > b +} + +func (a AuthLevel) CanDo() bool { + return a > AuthNone +} + func (a AuthLevel) CanRead() bool { return a >= AuthRead } @@ -61,22 +73,33 @@ func (a AuthLevel) CanDoAny() bool { return a >= AuthAll } -// 资源权限 - +// Auth 资源权限 type Auth struct { BaseModel - Name string `json:"name"` // 该权限作用的应用 AppID uint `json:"app_id"` + App *App `json:"app"` // 权限绑定只能绑定一个 - RoleID uint `json:"role_id"` - UserID uint `json:"user_id"` + RoleID *uint `json:"role_id" gorm:""` + Role *Role `json:"role"` + UserID *uint `json:"user_id"` + User *User `json:"user"` // 资源id + ResourceID uint `json:"resource_id" gorm:"not null"` + Resource *Resource `json:"resource"` + // resource_name 用于其他系统方便区分权限的名字 RID string `json:"rid" gorm:""` // 具体某个资源的id - RUID string `json:"ruid"` - // 权限标签 - Tag string `json:"tag"` + RUID string `json:"ruid"` Level AuthLevel `json:"level"` - Des string `json:"des"` +} + +type Resource struct { + BaseModel + AppID uint `json:"app_id"` + App *App `json:"app"` + Name string `json:"name"` + // 权限标签 + Tag string `json:"tag"` + Des string `json:"des"` } diff --git a/models/user.go b/models/user.go index e1faf70..64fefd7 100644 --- a/models/user.go +++ b/models/user.go @@ -1,14 +1,7 @@ package models import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" "github.com/veypi/utils" - "strings" - "time" ) // User db user model @@ -25,46 +18,45 @@ type User struct { Status string `json:"status"` Icon string `json:"icon"` - Roles []*Role `json:"roles" gorm:"many2many:user_role;"` + Roles []*Role `json:"roles" gorm:"many2many:user_roles;"` + Apps []*App `json:"apps" gorm:"many2many:app_users;"` Auths []*Auth `json:"auths" gorm:"foreignkey:UserID;references:ID"` } -type simpleAuth struct { - RID string `json:"rid"` - // 具体某个资源的id - RUID string `json:"ruid"` - Level AuthLevel `json:"level"` +func (u *User) String() string { + return u.Username + ":" + u.Nickname } -// TODO:: roles 是否会造成token过大 ? -type PayLoad struct { - ID uint `json:"id"` - Iat int64 `json:"iat"` //token time - Exp int64 `json:"exp"` - Auth map[uint]*simpleAuth `json:"auth"` +func (u *User) GetAuths() []*Auth { + list := make([]*Auth, 0, 10) + for _, r := range u.Roles { + for _, a := range r.Auths { + list = append(list, a) + } + } + for _, a := range u.Auths { + list = append(list, a) + } + return list } -// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 -func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) AuthLevel { - res := AuthNone - if p == nil || p.Auth == nil { - return res - } +func (u *User) GetAuth(appID uint, ResourceID string, ResourceUUID ...string) AuthLevel { + var res = AuthNone ruid := "" if len(ResourceUUID) > 0 { ruid = ResourceUUID[0] } - for _, a := range p.Auth { - if a.RID == ResourceID { + for _, a := range u.GetAuths() { + if a.RID == ResourceID && a.AppID == appID { if a.RUID != "" { if a.RUID == ruid { - if a.Level > res { + if a.Level.Upper(res) { res = a.Level } } else { continue } - } else if a.Level > res { + } else if a.Level.Upper(res) { res = a.Level } } @@ -72,91 +64,7 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) AuthLevel { return res } -func (u *User) String() string { - return u.Username + ":" + u.Nickname -} - -func (u *User) GetToken(key string, appID uint) (string, error) { - header := map[string]string{ - "typ": "JWT", - "alg": "HS256", - } - //header := "{\"typ\": \"JWT\", \"alg\": \"HS256\"}" - now := time.Now().Unix() - payload := PayLoad{ - ID: u.ID, - Iat: now, - Exp: now + 60*60*24, - Auth: map[uint]*simpleAuth{}, - } - for _, r := range u.Roles { - for _, a := range r.Auths { - if appID == a.AppID { - payload.Auth[a.ID] = &simpleAuth{ - RID: a.RID, - RUID: a.RUID, - Level: a.Level, - } - } - } - } - for _, a := range u.Auths { - if appID == a.AppID { - payload.Auth[a.ID] = &simpleAuth{ - RID: a.RID, - RUID: a.RUID, - Level: a.Level, - } - } - } - a, err := json.Marshal(header) - if err != nil { - return "", err - } - b, err := json.Marshal(payload) - if err != nil { - return "", err - } - A := base64.StdEncoding.EncodeToString(a) - B := base64.StdEncoding.EncodeToString(b) - hmacCipher := hmac.New(sha256.New, []byte(key)) - hmacCipher.Write([]byte(A + "." + B)) - C := hmacCipher.Sum(nil) - return A + "." + B + "." + base64.StdEncoding.EncodeToString(C), nil -} - -var ( - InvalidToken = errors.New("invalid token") - ExpiredToken = errors.New("expired token") -) - -func ParseToken(token string, key string, payload *PayLoad) (bool, error) { - var A, B, C string - if seqs := strings.Split(token, "."); len(seqs) == 3 { - A, B, C = seqs[0], seqs[1], seqs[2] - } else { - return false, InvalidToken - } - hmacCipher := hmac.New(sha256.New, []byte(key)) - hmacCipher.Write([]byte(A + "." + B)) - tempC := hmacCipher.Sum(nil) - if !hmac.Equal([]byte(C), []byte(base64.StdEncoding.EncodeToString(tempC))) { - return false, nil - } - tempPayload, err := base64.StdEncoding.DecodeString(B) - if err != nil { - return false, err - } - if err := json.Unmarshal(tempPayload, payload); err != nil { - return false, err - } - if time.Now().Unix() > payload.Exp { - return false, ExpiredToken - } - return true, nil -} - -func (u *User) UpdateAuth(ps string) (err error) { +func (u *User) UpdatePass(ps string) (err error) { u.RealCode = utils.RandSeq(32) u.CheckCode, err = utils.AesEncrypt(u.RealCode, []byte(ps)) return err diff --git a/oaf/package.json b/oaf/package.json index 8693115..e89c50c 100644 --- a/oaf/package.json +++ b/oaf/package.json @@ -8,11 +8,13 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@veypi/one-icon": "^1.0.1", "axios": "^0.21.1", "core-js": "^3.6.5", "js-base64": "^3.6.0", "vue": "^2.6.11", "vue-class-component": "^7.2.3", + "vue-m-message": "^3.1.0", "vue-property-decorator": "^9.1.2", "vue-router": "^3.2.0", "vuetify": "^2.4.0", diff --git a/oaf/public/favicon.ico b/oaf/public/favicon.ico index df36fcf..e26bc40 100644 Binary files a/oaf/public/favicon.ico and b/oaf/public/favicon.ico differ diff --git a/oaf/public/index.html b/oaf/public/index.html index 642a583..fa6b9ab 100644 --- a/oaf/public/index.html +++ b/oaf/public/index.html @@ -1,19 +1,20 @@ -
- - - - -