完善平台角色菜单树与授权登录导航
This commit is contained in:
@@ -59,6 +59,20 @@ func platformMenuAllowsRequest(menus []platformbase.Menu, requestPath, method st
|
||||
return false
|
||||
}
|
||||
|
||||
// isPlatformSelfMenuListRequest 判断是否为当前账号读取自身授权导航的基础请求。
|
||||
// 该接口的响应仍会按当前角色过滤,不授予菜单配置详情或写权限。
|
||||
func isPlatformSelfMenuListRequest(requestPath, method string) bool {
|
||||
if method != "GET" {
|
||||
return false
|
||||
}
|
||||
marker := "/platform/v1/"
|
||||
index := strings.Index(requestPath, marker)
|
||||
if index < 0 {
|
||||
return false
|
||||
}
|
||||
return strings.Trim(requestPath[index+len(marker):], "/") == "platform_menu"
|
||||
}
|
||||
|
||||
func platformRouteMenuIdentity(resource string) string {
|
||||
switch {
|
||||
case resource == "dashboard":
|
||||
@@ -114,7 +128,17 @@ func RequirePlatformMenuAccess() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
menus, err := platformbase.LoadPlatformMenus(claims.Role)
|
||||
if err != nil || !platformMenuAllowsRequest(menus, ctx.Request.URL.Path, ctx.Request.Method) ||
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
if isPlatformSelfMenuListRequest(ctx.Request.URL.Path, ctx.Request.Method) {
|
||||
ctx.Set(platformMenusContextKey, menus)
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
if !platformMenuAllowsRequest(menus, ctx.Request.URL.Path, ctx.Request.Method) ||
|
||||
!platformScopedRequestAllowed(ctx, menus) {
|
||||
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
|
||||
ctx.Abort()
|
||||
|
||||
@@ -6,6 +6,20 @@ import (
|
||||
platformbase "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform"
|
||||
)
|
||||
|
||||
// TestPlatformSelfMenuListRequest 保障普通账号只能免菜单能力读取自己的导航列表。
|
||||
func TestPlatformSelfMenuListRequest(t *testing.T) {
|
||||
path := "/heqi/platform/v1/platform_menu"
|
||||
if !isPlatformSelfMenuListRequest(path, "GET") {
|
||||
t.Fatal("平台账号应可读取自己的授权菜单列表")
|
||||
}
|
||||
if isPlatformSelfMenuListRequest(path, "POST") {
|
||||
t.Fatal("菜单写请求不得通过自身导航读取规则放行")
|
||||
}
|
||||
if isPlatformSelfMenuListRequest(path+"/platform_menu", "GET") {
|
||||
t.Fatal("菜单详情不得通过自身导航读取规则放行")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecondLevelMenuPermissionDoesNotGrantSibling(t *testing.T) {
|
||||
menus := []platformbase.Menu{{Identity: "delivery_basic"}}
|
||||
if !platformMenuAllowsPath(menus, "/heqi/platform/v1/delivery_basic") {
|
||||
|
||||
@@ -18,7 +18,7 @@ type platformRoleMenusRequest struct {
|
||||
MenuIdentities []string `json:"menu_identities"`
|
||||
}
|
||||
|
||||
// ReplacePlatformRoleMenus replaces every menu assignment for a role atomically.
|
||||
// ReplacePlatformRoleMenus 以事务整体替换角色的叶子菜单权限。
|
||||
func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
||||
if !common.RequirePlatformRoot(ctx) {
|
||||
return
|
||||
@@ -42,7 +42,8 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
||||
if !ok {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
if menu.ParentIdentity != "" {
|
||||
// 有子菜单的父节点仅用于导航聚合;无子菜单的顶级页面仍是有效业务权限。
|
||||
if platformMenuIsAssignable(menu) {
|
||||
menuIdentities[menu.Identity] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +72,7 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
// ListPlatformRoleMenuIdentities returns the current assignment for the role editor.
|
||||
// ListPlatformRoleMenuIdentities 返回角色编辑器当前保存的叶子菜单标识。
|
||||
func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
|
||||
if !common.RequirePlatformRoot(ctx) {
|
||||
return
|
||||
@@ -90,3 +91,16 @@ func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"menu_identities": identities})
|
||||
}
|
||||
|
||||
// platformMenuIsAssignable 区分导航分组和可持久化的业务页面权限。
|
||||
func platformMenuIsAssignable(menu platformbase.Menu) bool {
|
||||
if menu.Status != common.StatusEnable {
|
||||
return false
|
||||
}
|
||||
for _, candidate := range platformbase.AllPlatformMenus() {
|
||||
if candidate.ParentIdentity == menu.Identity {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
platformbase "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform"
|
||||
)
|
||||
|
||||
// TestPlatformMenuIsAssignable 验证导航父级不落库,独立顶级页面和普通叶子可以保存。
|
||||
func TestPlatformMenuIsAssignable(t *testing.T) {
|
||||
tests := []struct {
|
||||
identity string
|
||||
want bool
|
||||
}{
|
||||
{identity: "organization", want: false},
|
||||
{identity: "gas_basic", want: true},
|
||||
{identity: "dashboard_overview", want: true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
menu, ok := platformbase.FindPlatformMenu(test.identity)
|
||||
if !ok {
|
||||
t.Fatalf("测试菜单不存在:%s", test.identity)
|
||||
}
|
||||
if got := platformMenuIsAssignable(menu); got != test.want {
|
||||
t.Fatalf("菜单 %s 可分配状态错误:got=%v want=%v", test.identity, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user