172 lines
6.4 KiB
Go
172 lines
6.4 KiB
Go
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
|
||
}
|