From 74ddc44ce7c5bbe087317eb7c87efba3a1c7c0a3 Mon Sep 17 00:00:00 2001 From: xushuhui Date: Tue, 19 Apr 2022 12:03:56 +0800 Subject: [PATCH] feat: (rbac) init load rolePermission / validate user permission / operation log --- internal/biz/account.go | 41 +++++++++----- internal/biz/enum/const.go | 13 +++++ internal/biz/permission.go | 44 ++------------ internal/biz/role.go | 32 +++++++++-- internal/biz/user.go | 104 +++++++++++++++++++++++++++++++--- internal/middleware/user.go | 9 ++- plugin/api/account/account.go | 2 +- plugin/api/rbac/init.go | 33 ++++++----- plugin/api/rbac/user.go | 32 +++++++++-- 9 files changed, 226 insertions(+), 84 deletions(-) create mode 100644 internal/biz/enum/const.go diff --git a/internal/biz/account.go b/internal/biz/account.go index 44941811..a891b15e 100644 --- a/internal/biz/account.go +++ b/internal/biz/account.go @@ -5,7 +5,6 @@ import ( "fmt" "infini.sh/framework/core/global" "infini.sh/framework/core/util" - "net/http" "src/github.com/golang-jwt/jwt" "strings" "time" @@ -16,9 +15,9 @@ type UserClaims struct { *User } type User struct { - Username string `json:"username"` - UserId string `json:"user_id"` - ApiPermission map[string]struct{} `json:"api_permission"` + Username string `json:"username"` + UserId string `json:"user_id"` + Role []string `json:"role"` } const Secret = "console" @@ -36,9 +35,7 @@ func Login(username string, password string) (m map[string]interface{}, err erro User: &User{ Username: u, UserId: "admin", - ApiPermission: map[string]struct{}{ - "account.profile": struct{}{}, - }, + Role: []string{"admin_user"}, }, RegisteredClaims: &jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), @@ -92,8 +89,9 @@ func ValidateLogin(authorizationHeader string) (clams *UserClaims, err error) { return } -func ValidatePermission(r *http.Request, permissions string) (err error) { - reqUser, err := FromUserContext(r.Context()) +func ValidatePermission(claims *UserClaims, permissions []string) (err error) { + + reqUser := claims.User if err != nil { return @@ -102,16 +100,31 @@ func ValidatePermission(r *http.Request, permissions string) (err error) { err = errors.New("user id is empty") return } - if reqUser.ApiPermission == nil { + if reqUser.Role == nil { err = errors.New("api permission is empty") return } - if _, ok := reqUser.ApiPermission[permissions]; !ok { - err = errors.New("permission denied") - return + // 权限校验 + userPermissionMap := make(map[string]struct{}) + for _, role := range reqUser.Role { + if _, ok := RolePermission[role]; ok { + for _, v := range RolePermission[role] { + userPermissionMap[v] = struct{}{} + } + } } - + var count int + for _, v := range permissions { + if _, ok := userPermissionMap[v]; ok { + count++ + continue + } + } + if count == len(permissions) { + return nil + } + err = errors.New("permission denied") return } diff --git a/internal/biz/enum/const.go b/internal/biz/enum/const.go new file mode 100644 index 00000000..f64f892d --- /dev/null +++ b/internal/biz/enum/const.go @@ -0,0 +1,13 @@ +package enum + +const CreateUser = "create_user" +const UpdateUser = "update_user" +const DeleteUser = "delete_user" +const GetUser = "get_user" +const ListUser = "list_user" +const CreateRole = "create_role" +const UpdateRole = "update_role" +const DeleteRole = "delete_role" +const GetRole = "get_role" +const ListRole = "list_role" +const ListPermission = "list_permission" diff --git a/internal/biz/permission.go b/internal/biz/permission.go index 1bb4384b..8ba25b40 100644 --- a/internal/biz/permission.go +++ b/internal/biz/permission.go @@ -4,6 +4,7 @@ import "fmt" var ClusterApis = make([]string, 0) var EsApis = make(map[string][]string) +var RolePermission = make(map[string][]string) type RoleType = string @@ -15,10 +16,8 @@ const ( type IRole interface { ListPermission() interface{} } -type ConsoleRole struct { -} -type ElasticsearchRole struct { -} +type ConsoleRole struct{} +type ElasticsearchRole struct{} func NewRole(typ string) (r IRole, err error) { switch typ { @@ -68,31 +67,9 @@ func (r ConsoleRole) ListPermission() interface{} { return list } func (r ElasticsearchRole) ListPermission() interface{} { - list := []ConsolePermisson{ - { - Id: "cluster_overview", - Name: "平台概览", - }, - { - Id: "cluster_search", - Name: "平台搜索", - }, - { - Id: "cluster_elasticsearch", - Name: "集群监控", - }, - { - Id: "cluster_elasticsearch_refresh", - Name: "集群监控刷新", - }, - { - Id: "cluster_activities", - Name: "集群动态", - }, - { - Id: "cluster_activities_search", - Name: "集群动态搜索", - }, + list := ElasticsearchPermisson{ + ClusterPrivileges: ClusterApis, + IndexPrivileges: EsApis["indices"], } return list } @@ -101,12 +78,3 @@ type ElasticsearchPermisson struct { IndexPrivileges []string `json:"index_privileges"` ClusterPrivileges []string `json:"cluster_privileges"` } - -func ListElasticsearchPermisson() (permisson ElasticsearchPermisson, err error) { - - permisson = ElasticsearchPermisson{ - ClusterPrivileges: ClusterApis, - IndexPrivileges: EsApis["indices"], - } - return -} diff --git a/internal/biz/role.go b/internal/biz/role.go index 0a810f13..75ce6f66 100644 --- a/internal/biz/role.go +++ b/internal/biz/role.go @@ -91,11 +91,16 @@ func DeleteRole(localUser *User, id string) (err error) { "userid": localUser.UserId, "username": localUser.Username, }, - }, nil)) + }, util.MapStr{ + "id": id, + "name": role.Name, + "description": role.Description, + "permission": role.Permission, + "type": role.RoleType, + "created": role.Created.Format("2006-01-02 15:04:05"), + "updated": role.Updated.Format("2006-01-02 15:04:05"), + })) - if err != nil { - log.Error(err) - } return } @@ -111,6 +116,25 @@ func UpdateRole(localUser *User, id string, req dto.UpdateRole) (err error) { role.Permission = req.Permission role.Updated = time.Now() err = orm.Save(role) + if err != nil { + return + } + err = orm.Save(GenerateEvent(event.ActivityMetadata{ + Category: "platform", + Group: "rbac", + Name: "role", + Type: "update", + Labels: util.MapStr{ + "id": id, + "description": role.Description, + "permission": role.Permission, + "updated": role.Updated, + }, + User: util.MapStr{ + "userid": localUser.UserId, + "username": localUser.Username, + }, + }, nil)) return } func GetRole(id string) (role rbac.Role, err error) { diff --git a/internal/biz/user.go b/internal/biz/user.go index 4699290e..271761a0 100644 --- a/internal/biz/user.go +++ b/internal/biz/user.go @@ -4,6 +4,7 @@ import ( "fmt" "infini.sh/console/internal/dto" "infini.sh/console/model/rbac" + "infini.sh/framework/core/event" "infini.sh/framework/core/orm" "infini.sh/framework/core/util" @@ -13,7 +14,7 @@ import ( var ErrNotFound = fmt.Errorf("not found") -func DeleteUser(id string) (err error) { +func DeleteUser(localUser *User, id string) (err error) { user := rbac.User{} user.ID = id @@ -22,10 +23,37 @@ func DeleteUser(id string) (err error) { err = ErrNotFound return } - return orm.Delete(user) - + err = orm.Delete(user) + if err != nil { + return + } + err = orm.Save(GenerateEvent(event.ActivityMetadata{ + Category: "platform", + Group: "rbac", + Name: "user", + Type: "delete", + Labels: util.MapStr{ + "id": id, + }, + User: util.MapStr{ + "userid": localUser.UserId, + "username": localUser.Username, + }, + }, util.MapStr{ + "id": id, + "username": user.Username, + "email": user.Email, + "phone": user.Phone, + "password": user.Password, + "name": user.Name, + "tags": user.Tags, + "roles": user.Roles, + "created": user.Created, + "updated": user.Updated, + })) + return } -func CreateUser(req dto.CreateUser) (id string, err error) { +func CreateUser(localUser *User, req dto.CreateUser) (id string, err error) { q := orm.Query{Size: 1000} q.Conds = orm.And(orm.Eq("username", req.Username)) @@ -62,9 +90,32 @@ func CreateUser(req dto.CreateUser) (id string, err error) { return } - return user.ID, nil + id = user.ID + err = orm.Save(GenerateEvent(event.ActivityMetadata{ + Category: "platform", + Group: "rbac", + Name: "user", + Type: "create", + Labels: util.MapStr{ + "id": id, + "username": user.Username, + "email": user.Email, + "phone": user.Phone, + "password": user.Password, + "name": user.Name, + "tags": user.Tags, + "roles": user.Roles, + "created": user.Created, + "updated": user.Updated, + }, + User: util.MapStr{ + "userid": localUser.UserId, + "username": localUser.Username, + }, + }, nil)) + return } -func UpdateUser(id string, req dto.UpdateUser) (err error) { +func UpdateUser(localUser *User, id string, req dto.UpdateUser) (err error) { user := rbac.User{} user.ID = id _, err = orm.Get(&user) @@ -78,9 +129,30 @@ func UpdateUser(id string, req dto.UpdateUser) (err error) { user.Tags = req.Tags user.Updated = time.Now() err = orm.Save(user) + if err != nil { + return + } + err = orm.Save(GenerateEvent(event.ActivityMetadata{ + Category: "platform", + Group: "rbac", + Name: "user", + Type: "update", + Labels: util.MapStr{ + "id": id, + "email": user.Email, + "phone": user.Phone, + "name": user.Name, + "tags": user.Tags, + "updated": user.Updated, + }, + User: util.MapStr{ + "userid": localUser.UserId, + "username": localUser.Username, + }, + }, nil)) return } -func UpdateUserRole(id string, req dto.UpdateUserRole) (err error) { +func UpdateUserRole(localUser *User, id string, req dto.UpdateUserRole) (err error) { user := rbac.User{} user.ID = id _, err = orm.Get(&user) @@ -98,6 +170,24 @@ func UpdateUserRole(id string, req dto.UpdateUserRole) (err error) { user.Roles = roles user.Updated = time.Now() err = orm.Save(user) + if err != nil { + return + } + err = orm.Save(GenerateEvent(event.ActivityMetadata{ + Category: "platform", + Group: "rbac", + Name: "user", + Type: "update", + Labels: util.MapStr{ + "id": id, + "roles": user.Roles, + "updated": user.Updated, + }, + User: util.MapStr{ + "userid": localUser.UserId, + "username": localUser.Username, + }, + }, nil)) return } diff --git a/internal/middleware/user.go b/internal/middleware/user.go index 8c3f9b11..e3466cd6 100644 --- a/internal/middleware/user.go +++ b/internal/middleware/user.go @@ -20,9 +20,14 @@ func LoginRequired(h httprouter.Handle) httprouter.Handle { } } -func PermissionRequired(h httprouter.Handle, permissions string) httprouter.Handle { +func PermissionRequired(h httprouter.Handle, permissions ...string) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - err := biz.ValidatePermission(r, permissions) + claims, err := biz.ValidateLogin(r.Header.Get("Authorization")) + if err != nil { + w = handleError(w, err) + return + } + err = biz.ValidatePermission(claims, permissions) if err != nil { w = handleError(w, err) return diff --git a/plugin/api/account/account.go b/plugin/api/account/account.go index 6e661714..e0d6db45 100644 --- a/plugin/api/account/account.go +++ b/plugin/api/account/account.go @@ -20,7 +20,7 @@ func init() { //api.HandleAPIMethod(api.GET, "/account/current_user", account.CurrentUser) - api.HandleAPIMethod(api.DELETE, "/account/logout", m.LoginRequired(account.Logout)) + api.HandleAPIMethod(api.DELETE, "/account/logout", account.Logout) api.HandleAPIMethod(api.GET, "/account/profile", m.LoginRequired(account.Profile)) } diff --git a/plugin/api/rbac/init.go b/plugin/api/rbac/init.go index e3cc0ef5..b988105c 100644 --- a/plugin/api/rbac/init.go +++ b/plugin/api/rbac/init.go @@ -3,6 +3,7 @@ package rbac import ( "encoding/json" "infini.sh/console/internal/biz" + "infini.sh/console/internal/biz/enum" m "infini.sh/console/internal/middleware" "infini.sh/framework/core/api" @@ -17,24 +18,23 @@ type Rbac struct { func registerRouter() { r := Rbac{} - api.HandleAPIMethod(api.GET, "/permission/:type", m.LoginRequired(m.PermissionRequired(r.ListPermission, "list.permission"))) - api.HandleAPIMethod(api.POST, "/role/:type", m.LoginRequired(m.PermissionRequired(r.CreateRole, "create.role"))) - api.HandleAPIMethod(api.GET, "/role/:id", m.LoginRequired(m.PermissionRequired(r.GetRole, "get.role"))) - api.HandleAPIMethod(api.DELETE, "/role/:id", m.LoginRequired(m.PermissionRequired(r.DeleteRole, "delete.role"))) - api.HandleAPIMethod(api.PUT, "/role/:id", m.LoginRequired(m.PermissionRequired(r.UpdateRole, "update.role"))) - api.HandleAPIMethod(api.GET, "/role/_search", m.LoginRequired(m.PermissionRequired(r.SearchRole, "search.role"))) + api.HandleAPIMethod(api.GET, "/permission/:type", m.PermissionRequired(r.ListPermission, enum.ListPermission)) + api.HandleAPIMethod(api.POST, "/role/:type", m.PermissionRequired(r.CreateRole, enum.CreateRole)) + api.HandleAPIMethod(api.GET, "/role/:id", m.PermissionRequired(r.GetRole, enum.GetRole)) + api.HandleAPIMethod(api.DELETE, "/role/:id", m.PermissionRequired(r.DeleteRole, enum.DeleteRole)) + api.HandleAPIMethod(api.PUT, "/role/:id", m.PermissionRequired(r.UpdateRole, enum.UpdateRole)) + api.HandleAPIMethod(api.GET, "/role/_search", m.PermissionRequired(r.SearchRole, enum.ListRole)) - api.HandleAPIMethod(api.POST, "/user", m.LoginRequired(m.PermissionRequired(r.CreateUser, "create.user"))) - api.HandleAPIMethod(api.GET, "/user/:id", m.LoginRequired(m.PermissionRequired(r.GetUser, "get.user"))) - api.HandleAPIMethod(api.GET, "/user/search", m.LoginRequired(m.PermissionRequired(r.SearchUser, "search.user"))) - api.HandleAPIMethod(api.DELETE, "/user/:id", m.LoginRequired(m.PermissionRequired(r.DeleteUser, "delete.user"))) - api.HandleAPIMethod(api.PUT, "/user/:id", m.LoginRequired(m.PermissionRequired(r.UpdateUser, "update.user"))) - api.HandleAPIMethod(api.PUT, "/user/:id/role", m.LoginRequired(m.PermissionRequired(r.UpdateUserRole, "update.user.role"))) - api.HandleAPIMethod(api.GET, "/user/_search", m.LoginRequired(m.PermissionRequired(r.SearchUser, "search.user"))) + api.HandleAPIMethod(api.POST, "/user", m.PermissionRequired(r.CreateUser, enum.CreateUser)) + api.HandleAPIMethod(api.GET, "/user/:id", m.PermissionRequired(r.GetUser, enum.GetUser)) + api.HandleAPIMethod(api.GET, "/user/search", m.PermissionRequired(r.SearchUser, enum.ListUser)) + api.HandleAPIMethod(api.DELETE, "/user/:id", m.PermissionRequired(r.DeleteUser, enum.DeleteUser)) + api.HandleAPIMethod(api.PUT, "/user/:id", m.PermissionRequired(r.UpdateUser, enum.UpdateUser)) + api.HandleAPIMethod(api.PUT, "/user/:id/role", m.PermissionRequired(r.UpdateUserRole, enum.UpdateUser)) + api.HandleAPIMethod(api.GET, "/user/_search", m.PermissionRequired(r.SearchUser, enum.ListUser)) } -//TODO 权限一级配置全局变量, func loadJsonConfig() { pwd, _ := os.Getwd() @@ -56,9 +56,14 @@ func loadJsonConfig() { biz.ClusterApis = list } +func loadRolePermission() { + biz.RolePermission = make(map[string][]string) + biz.RolePermission["admin_user"] = []string{enum.GetUser} +} func init() { registerRouter() loadJsonConfig() + loadRolePermission() } type Response struct { diff --git a/plugin/api/rbac/user.go b/plugin/api/rbac/user.go index 88f51d17..d86a16ff 100644 --- a/plugin/api/rbac/user.go +++ b/plugin/api/rbac/user.go @@ -27,7 +27,13 @@ func (h Rbac) CreateUser(w http.ResponseWriter, r *http.Request, ps httprouter.P h.Error(w, err) return } - id, err := biz.CreateUser(req) + localUser, err := biz.FromUserContext(r.Context()) + if err != nil { + log.Error(err.Error()) + h.Error(w, err) + return + } + id, err := biz.CreateUser(localUser, req) if err != nil { _ = log.Error(err.Error()) h.Error(w, err) @@ -64,7 +70,13 @@ func (h Rbac) UpdateUser(w http.ResponseWriter, r *http.Request, ps httprouter.P h.Error(w, err) return } - err = biz.UpdateUser(id, req) + localUser, err := biz.FromUserContext(r.Context()) + if err != nil { + log.Error(err.Error()) + h.Error(w, err) + return + } + err = biz.UpdateUser(localUser, id, req) if err != nil { _ = log.Error(err.Error()) @@ -84,7 +96,13 @@ func (h Rbac) UpdateUserRole(w http.ResponseWriter, r *http.Request, ps httprout h.Error(w, err) return } - err = biz.UpdateUserRole(id, req) + localUser, err := biz.FromUserContext(r.Context()) + if err != nil { + log.Error(err.Error()) + h.Error(w, err) + return + } + err = biz.UpdateUserRole(localUser, id, req) if err != nil { _ = log.Error(err.Error()) @@ -97,7 +115,13 @@ func (h Rbac) UpdateUserRole(w http.ResponseWriter, r *http.Request, ps httprout func (h Rbac) DeleteUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { id := ps.MustGetParameter("id") - err := biz.DeleteUser(id) + localUser, err := biz.FromUserContext(r.Context()) + if err != nil { + log.Error(err.Error()) + h.Error(w, err) + return + } + err = biz.DeleteUser(localUser, id) if errors.Is(err, elastic.ErrNotFound) { h.WriteJSON(w, NotFoundResponse(id), http.StatusNotFound) return