2026-07-22 13:55:55 +08:00

172 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package host
import (
"context"
"strings"
"hyapp/pkg/xerr"
)
type RoleScope string
const (
RoleScopeCountry RoleScope = "country"
RoleScopeRegion RoleScope = "region"
RoleScopeGlobal RoleScope = "global"
RoleScopeSceneOrganizationExpansion = "organization_role_expansion"
RoleScopeSceneManagerOperations = "manager_operations"
// RoleScopeSceneManagerAddBDLeader 兼容上一版 gateway/admin 的策略查询,解析后统一返回 manager_operations。
RoleScopeSceneManagerAddBDLeader = "manager_add_bd_leader"
)
// RoleScopePolicy 是 user-service 拥有的 App 范围策略;调用方只消费解析结果,不能再按产品名复制分支。
type RoleScopePolicy struct {
Scene string
BaseScope RoleScope
ExpandedScope RoleScope
RegionExpansionConfigurable bool
UpdatedAtMS int64
}
// RoleScopePolicyRepository 只暴露小配置表的精确键读取和原子批量写入,避免把后台配置混入 Host 关系事务接口。
type RoleScopePolicyRepository interface {
GetRoleScopePolicy(ctx context.Context, scene string) (RoleScopePolicy, bool, error)
UpsertRoleScopePolicies(ctx context.Context, policies []RoleScopePolicy, updatedAtMS int64) ([]RoleScopePolicy, error)
}
// DefaultRoleScopePolicy 定义未配置新 App 的安全基线。产品差异必须写入 owner 表,不能再扩展代码注册表。
func DefaultRoleScopePolicy(rawScene string) (RoleScopePolicy, error) {
scene := normalizeRoleScopeScene(rawScene)
switch scene {
case RoleScopeSceneOrganizationExpansion:
return RoleScopePolicy{
Scene: scene,
BaseScope: RoleScopeRegion,
ExpandedScope: RoleScopeRegion,
}, nil
case RoleScopeSceneManagerOperations:
return RoleScopePolicy{
Scene: scene,
BaseScope: RoleScopeCountry,
ExpandedScope: RoleScopeRegion,
RegionExpansionConfigurable: true,
}, nil
default:
return RoleScopePolicy{}, xerr.New(xerr.InvalidArgument, "role scope scene is invalid")
}
}
// ResolveRoleScopePolicy 先验证 scene再读取 user DB 的 App 精确配置;数据库错误必须向上返回,不能降级扩大权限。
func ResolveRoleScopePolicy(ctx context.Context, repository RoleScopePolicyRepository, rawScene string) (RoleScopePolicy, error) {
defaultPolicy, err := DefaultRoleScopePolicy(rawScene)
if err != nil {
return RoleScopePolicy{}, err
}
if repository == nil {
return RoleScopePolicy{}, xerr.New(xerr.Unavailable, "role scope policy repository is not configured")
}
policy, found, err := repository.GetRoleScopePolicy(ctx, defaultPolicy.Scene)
if err != nil {
return RoleScopePolicy{}, err
}
if !found {
return defaultPolicy, nil
}
policy.Scene = defaultPolicy.Scene
if err := validateRoleScopePolicy(policy); err != nil {
return RoleScopePolicy{}, err
}
return policy, nil
}
// ResolveRoleScopePolicy 是 Host service 内所有角色边界查询的统一入口。
func (s *Service) ResolveRoleScopePolicy(ctx context.Context, rawScene string) (RoleScopePolicy, error) {
return ResolveRoleScopePolicy(ctx, s.roleScopePolicyRepository, rawScene)
}
// UpdateRoleScopePolicies 把两个 scene 作为同一 App 配置快照提交,避免后台只写一半后留下混合语义。
func (s *Service) UpdateRoleScopePolicies(ctx context.Context, policies []RoleScopePolicy) ([]RoleScopePolicy, error) {
if s == nil || s.roleScopePolicyRepository == nil {
return nil, xerr.New(xerr.Unavailable, "role scope policy repository is not configured")
}
if len(policies) != 2 {
return nil, xerr.New(xerr.InvalidArgument, "both role scope policies are required")
}
seen := make(map[string]struct{}, len(policies))
normalized := make([]RoleScopePolicy, 0, len(policies))
for _, policy := range policies {
policy.Scene = normalizeRoleScopeScene(policy.Scene)
if _, exists := seen[policy.Scene]; exists {
return nil, xerr.New(xerr.InvalidArgument, "role scope scene is duplicated")
}
if err := validateRoleScopePolicy(policy); err != nil {
return nil, err
}
seen[policy.Scene] = struct{}{}
normalized = append(normalized, policy)
}
if _, ok := seen[RoleScopeSceneOrganizationExpansion]; !ok {
return nil, xerr.New(xerr.InvalidArgument, "organization role scope policy is required")
}
if _, ok := seen[RoleScopeSceneManagerOperations]; !ok {
return nil, xerr.New(xerr.InvalidArgument, "manager role scope policy is required")
}
return s.roleScopePolicyRepository.UpsertRoleScopePolicies(ctx, normalized, s.now().UTC().UnixMilli())
}
func normalizeRoleScopeScene(rawScene string) string {
scene := strings.ToLower(strings.TrimSpace(rawScene))
if scene == RoleScopeSceneManagerAddBDLeader {
return RoleScopeSceneManagerOperations
}
return scene
}
func validateRoleScopePolicy(policy RoleScopePolicy) error {
if policy.Scene != RoleScopeSceneOrganizationExpansion && policy.Scene != RoleScopeSceneManagerOperations {
return xerr.New(xerr.InvalidArgument, "role scope scene is invalid")
}
if !validRoleScope(policy.BaseScope) || !validRoleScope(policy.ExpandedScope) {
return xerr.New(xerr.InvalidArgument, "role scope is invalid")
}
if policy.Scene == RoleScopeSceneOrganizationExpansion {
if policy.BaseScope == RoleScopeCountry || policy.RegionExpansionConfigurable || policy.ExpandedScope != policy.BaseScope {
return xerr.New(xerr.InvalidArgument, "organization role scope policy is invalid")
}
return nil
}
if !policy.RegionExpansionConfigurable && policy.ExpandedScope != policy.BaseScope {
return xerr.New(xerr.InvalidArgument, "non-configurable manager scope must keep one scope")
}
if policy.RegionExpansionConfigurable && roleScopeRank(policy.ExpandedScope) < roleScopeRank(policy.BaseScope) {
return xerr.New(xerr.InvalidArgument, "expanded manager scope cannot be narrower than base scope")
}
return nil
}
func validRoleScope(scope RoleScope) bool {
return scope == RoleScopeCountry || scope == RoleScopeRegion || scope == RoleScopeGlobal
}
func roleScopeRank(scope RoleScope) int {
switch scope {
case RoleScopeCountry:
return 1
case RoleScopeRegion:
return 2
case RoleScopeGlobal:
return 3
default:
return 0
}
}
// EffectiveScope 只允许声明为 configurable 的策略接受经理个人扩区开关。
func (p RoleScopePolicy) EffectiveScope(regionExpansionEnabled bool) RoleScope {
if p.RegionExpansionConfigurable && regionExpansionEnabled {
return p.ExpandedScope
}
return p.BaseScope
}