fix platform workflow integrity and permissions

This commit is contained in:
david
2026-07-29 14:03:10 +08:00
parent afd9dbdeb5
commit d4799cd320
44 changed files with 797 additions and 224 deletions

View File

@@ -19,36 +19,35 @@ func platformMenuAllowsPath(menus []platformbase.Menu, requestPath string) bool
}
relative := strings.Trim(requestPath[index+len(marker):], "/")
resource := strings.Split(relative, "/")[0]
domain := platformRouteDomain(resource)
menuIdentity := platformRouteMenuIdentity(resource)
for _, menu := range menus {
if menu.MenuCode == domain {
return true
}
menuPath := strings.Trim(menu.Path, "/")
if menuPath != "" && strings.Split(menuPath, "/")[0] == domain {
if menu.Identity == menuIdentity {
return true
}
}
return false
}
func platformRouteDomain(resource string) string {
prefix := strings.Split(resource, "_")[0]
switch prefix {
case "product":
return "device"
case "gasorder":
return "delivery"
case "fin":
return "finance"
case "cms":
return "content"
case "cs":
return "customer_service"
case "platform":
return "platform"
func platformRouteMenuIdentity(resource string) string {
switch {
case resource == "dashboard":
return "dashboard_overview"
case strings.HasPrefix(resource, "gasorder_contract"):
return "gasorder_contract"
case resource == "gasorder_track" || resource == "gasorder_track_point":
return "gasorder_track"
case strings.HasPrefix(resource, "gasorder_"):
return "gasorder_basic"
case resource == "product_type" || resource == "product_warehouse":
return resource
case strings.HasPrefix(resource, "product_"):
return "product_info"
case strings.HasPrefix(resource, "cms_"):
return "cms_content"
case strings.HasPrefix(resource, "cs_"):
return "cs_ticket"
default:
return prefix
return resource
}
}

View File

@@ -0,0 +1,30 @@
package platform
import (
"testing"
platformbase "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform"
)
func TestSecondLevelMenuPermissionDoesNotGrantSibling(t *testing.T) {
menus := []platformbase.Menu{{Identity: "delivery_basic"}}
if !platformMenuAllowsPath(menus, "/heqi/platform/v1/delivery_basic") {
t.Fatal("selected second-level menu did not grant its resource")
}
if platformMenuAllowsPath(menus, "/heqi/platform/v1/delivery_account") {
t.Fatal("selected second-level menu granted a sibling resource")
}
if platformMenuAllowsPath(menus, "/heqi/platform/v1/gasorder_basic") {
t.Fatal("delivery permission leaked into gasorder")
}
}
func TestHiddenGasorderResourcesFollowOwningSecondLevelMenu(t *testing.T) {
orderMenus := []platformbase.Menu{{Identity: "gasorder_basic"}}
if !platformMenuAllowsPath(orderMenus, "/heqi/platform/v1/gasorder_confirm") {
t.Fatal("order confirmation was not covered by order menu")
}
if platformMenuAllowsPath(orderMenus, "/heqi/platform/v1/gasorder_contract") {
t.Fatal("order menu granted contract management")
}
}

View File

@@ -3,6 +3,7 @@ package platform
import (
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/bsm-sdk/core/middleware"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
@@ -15,7 +16,7 @@ func ListPlatformAccount(ctx *gin.Context) {
page, size := common.PageSize(ctx)
var list []models.PlatformAccount
var total int64
query := common.ApplyKeywordFilter(ctx, impl.DBService.Model(&models.PlatformAccount{}), &models.PlatformAccount{})
query := common.ApplyKeywordFilter(ctx, common.ActiveRecords(impl.DBService.Model(&models.PlatformAccount{})), &models.PlatformAccount{})
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
@@ -98,6 +99,15 @@ func UpdatePlatformAccount(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
claims, err := middleware.ParseAuth(ctx)
if err != nil {
infra.Response.Error(ctx, err)
return
}
if claims.Role != "root" && claims.Identity != ctx.Param("identity") {
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
return
}
values := gin.H{"display_name": request.DisplayName, "avatar": request.Avatar, "phone": request.Phone}
if request.PlatformRoleCode != nil {
if !common.RequirePlatformRoot(ctx) {

View File

@@ -36,19 +36,21 @@ func ReplacePlatformRoleMenus(ctx *gin.Context) {
if role.IsSystem {
return errSystemPlatformRole
}
menuCodes := make(map[string]struct{}, len(request.MenuIdentities))
menuIdentities := make(map[string]struct{}, len(request.MenuIdentities))
for _, identity := range request.MenuIdentities {
menu, ok := platformbase.FindPlatformMenu(identity)
if !ok {
return gorm.ErrRecordNotFound
}
menuCodes[menu.MenuCode] = struct{}{}
if menu.ParentIdentity != "" {
menuIdentities[menu.Identity] = struct{}{}
}
}
if err := transaction.Where("platform_role_id = ?", role.ID).Delete(&models.PlatformRoleMenu{}).Error; err != nil {
return err
}
for menuCode := range menuCodes {
relation := models.PlatformRoleMenu{PlatformRoleID: role.ID, MenuCode: menuCode}
for menuIdentity := range menuIdentities {
relation := models.PlatformRoleMenu{PlatformRoleID: role.ID, MenuIdentity: menuIdentity}
if err := transaction.Create(&relation).Error; err != nil {
return err
}
@@ -79,22 +81,12 @@ func ListPlatformRoleMenuIdentities(ctx *gin.Context) {
common.RespondRecordError(ctx, err)
return
}
var codes []string
var identities []string
if err := impl.DBService.Model(&models.PlatformRoleMenu{}).
Where("platform_role_id = ?", role.ID).
Pluck("menu_code", &codes).Error; err != nil {
Pluck("menu_identity", &identities).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
assigned := make(map[string]struct{}, len(codes))
for _, code := range codes {
assigned[code] = struct{}{}
}
identities := make([]string, 0, len(codes))
for _, menu := range platformbase.AllPlatformMenus() {
if _, ok := assigned[menu.MenuCode]; ok {
identities = append(identities, menu.Identity)
}
}
infra.Response.Success(ctx, gin.H{"menu_identities": identities})
}