## 设计文档 - multi_role_permission_design: 多角色权限设计 (CONDITIONAL GO) - audit_log_enhancement_design: 审计日志增强 (CONDITIONAL GO) - routing_strategy_template_design: 路由策略模板 (CONDITIONAL GO) - sso_saml_technical_research: SSO/SAML调研 (CONDITIONAL GO) - compliance_capability_package_design: 合规能力包设计 (CONDITIONAL GO) ## TDD开发成果 - IAM模块: supply-api/internal/iam/ (111个测试) - 审计日志模块: supply-api/internal/audit/ (40+测试) - 路由策略模块: gateway/internal/router/ (33+测试) - 合规能力包: gateway/internal/compliance/ + scripts/ci/compliance/ ## 规范文档 - parallel_agent_output_quality_standards: 并行Agent产出质量规范 - project_experience_summary: 项目经验总结 (v2) - 2026-04-02-p1-p2-tdd-execution-plan: TDD执行计划 ## 评审报告 - 5个CONDITIONAL GO设计文档评审报告 - fix_verification_report: 修复验证报告 - full_verification_report: 全面质量验证报告 - tdd_module_quality_verification: TDD模块质量验证 - tdd_execution_summary: TDD执行总结 依据: Superpowers执行框架 + TDD规范
351 lines
8.8 KiB
Go
351 lines
8.8 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"lijiaoqiao/supply-api/internal/middleware"
|
|
)
|
|
|
|
// IAM token claims context key
|
|
type iamContextKey string
|
|
|
|
const (
|
|
// IAMTokenClaimsKey 用于在context中存储token claims
|
|
IAMTokenClaimsKey iamContextKey = "iam_token_claims"
|
|
)
|
|
|
|
// IAMTokenClaims IAM扩展Token Claims
|
|
type IAMTokenClaims struct {
|
|
SubjectID string `json:"subject_id"`
|
|
Role string `json:"role"`
|
|
Scope []string `json:"scope"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
UserType string `json:"user_type"` // 用户类型: platform/supply/consumer
|
|
Permissions []string `json:"permissions"` // 细粒度权限列表
|
|
}
|
|
|
|
// ScopeAuthMiddleware Scope权限验证中间件
|
|
type ScopeAuthMiddleware struct {
|
|
// 路由-Scope映射
|
|
routeScopePolicies map[string][]string
|
|
// 角色层级
|
|
roleHierarchy map[string]int
|
|
}
|
|
|
|
// NewScopeAuthMiddleware 创建Scope权限验证中间件
|
|
func NewScopeAuthMiddleware() *ScopeAuthMiddleware {
|
|
return &ScopeAuthMiddleware{
|
|
routeScopePolicies: make(map[string][]string),
|
|
roleHierarchy: map[string]int{
|
|
"super_admin": 100,
|
|
"org_admin": 50,
|
|
"supply_admin": 40,
|
|
"consumer_admin": 40,
|
|
"operator": 30,
|
|
"developer": 20,
|
|
"finops": 20,
|
|
"supply_operator": 30,
|
|
"supply_finops": 20,
|
|
"supply_viewer": 10,
|
|
"consumer_operator": 30,
|
|
"consumer_viewer": 10,
|
|
"viewer": 10,
|
|
},
|
|
}
|
|
}
|
|
|
|
// SetRouteScopePolicy 设置路由的Scope要求
|
|
func (m *ScopeAuthMiddleware) SetRouteScopePolicy(route string, scopes []string) {
|
|
m.routeScopePolicies[route] = scopes
|
|
}
|
|
|
|
// CheckScope 检查是否拥有指定Scope
|
|
func CheckScope(ctx context.Context, requiredScope string) bool {
|
|
claims := getIAMTokenClaims(ctx)
|
|
if claims == nil {
|
|
return false
|
|
}
|
|
|
|
// 空scope直接通过
|
|
if requiredScope == "" {
|
|
return true
|
|
}
|
|
|
|
return hasScope(claims.Scope, requiredScope)
|
|
}
|
|
|
|
// CheckAllScopes 检查是否拥有所有指定Scope
|
|
func CheckAllScopes(ctx context.Context, requiredScopes []string) bool {
|
|
claims := getIAMTokenClaims(ctx)
|
|
if claims == nil {
|
|
return false
|
|
}
|
|
|
|
// 空列表直接通过
|
|
if len(requiredScopes) == 0 {
|
|
return true
|
|
}
|
|
|
|
for _, scope := range requiredScopes {
|
|
if !hasScope(claims.Scope, scope) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// CheckAnyScope 检查是否拥有任一指定Scope
|
|
func CheckAnyScope(ctx context.Context, requiredScopes []string) bool {
|
|
claims := getIAMTokenClaims(ctx)
|
|
if claims == nil {
|
|
return false
|
|
}
|
|
|
|
// 空列表直接通过
|
|
if len(requiredScopes) == 0 {
|
|
return true
|
|
}
|
|
|
|
for _, scope := range requiredScopes {
|
|
if hasScope(claims.Scope, scope) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasRole 检查是否拥有指定角色
|
|
func HasRole(ctx context.Context, requiredRole string) bool {
|
|
claims := getIAMTokenClaims(ctx)
|
|
if claims == nil {
|
|
return false
|
|
}
|
|
|
|
return claims.Role == requiredRole
|
|
}
|
|
|
|
// HasRoleLevel 检查角色层级是否满足要求
|
|
func HasRoleLevel(ctx context.Context, minLevel int) bool {
|
|
claims := getIAMTokenClaims(ctx)
|
|
if claims == nil {
|
|
return false
|
|
}
|
|
|
|
level := GetRoleLevel(claims.Role)
|
|
return level >= minLevel
|
|
}
|
|
|
|
// GetRoleLevel 获取角色层级数值
|
|
func GetRoleLevel(role string) int {
|
|
hierarchy := map[string]int{
|
|
"super_admin": 100,
|
|
"org_admin": 50,
|
|
"supply_admin": 40,
|
|
"consumer_admin": 40,
|
|
"operator": 30,
|
|
"developer": 20,
|
|
"finops": 20,
|
|
"supply_operator": 30,
|
|
"supply_finops": 20,
|
|
"supply_viewer": 10,
|
|
"consumer_operator": 30,
|
|
"consumer_viewer": 10,
|
|
"viewer": 10,
|
|
}
|
|
|
|
if level, ok := hierarchy[role]; ok {
|
|
return level
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// GetIAMTokenClaims 获取IAM Token Claims
|
|
func GetIAMTokenClaims(ctx context.Context) *IAMTokenClaims {
|
|
if claims, ok := ctx.Value(IAMTokenClaimsKey).(IAMTokenClaims); ok {
|
|
return &claims
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getIAMTokenClaims 内部获取IAM Token Claims
|
|
func getIAMTokenClaims(ctx context.Context) *IAMTokenClaims {
|
|
if claims, ok := ctx.Value(IAMTokenClaimsKey).(IAMTokenClaims); ok {
|
|
return &claims
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// hasScope 检查scope列表是否包含目标scope
|
|
func hasScope(scopes []string, target string) bool {
|
|
for _, scope := range scopes {
|
|
if scope == target || scope == "*" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RequireScope 返回一个要求特定Scope的中间件
|
|
func (m *ScopeAuthMiddleware) RequireScope(requiredScope string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
claims := getIAMTokenClaims(r.Context())
|
|
|
|
if claims == nil {
|
|
writeAuthError(w, http.StatusUnauthorized, "AUTH_CONTEXT_MISSING",
|
|
"authentication context is missing")
|
|
return
|
|
}
|
|
|
|
// 检查scope
|
|
if requiredScope != "" && !hasScope(claims.Scope, requiredScope) {
|
|
writeAuthError(w, http.StatusForbidden, "AUTH_SCOPE_DENIED",
|
|
"required scope is not granted")
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireAllScopes 返回一个要求所有指定Scope的中间件
|
|
func (m *ScopeAuthMiddleware) RequireAllScopes(requiredScopes []string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
claims := getIAMTokenClaims(r.Context())
|
|
|
|
if claims == nil {
|
|
writeAuthError(w, http.StatusUnauthorized, "AUTH_CONTEXT_MISSING",
|
|
"authentication context is missing")
|
|
return
|
|
}
|
|
|
|
for _, scope := range requiredScopes {
|
|
if !hasScope(claims.Scope, scope) {
|
|
writeAuthError(w, http.StatusForbidden, "AUTH_SCOPE_DENIED",
|
|
"required scope is not granted")
|
|
return
|
|
}
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireAnyScope 返回一个要求任一指定Scope的中间件
|
|
func (m *ScopeAuthMiddleware) RequireAnyScope(requiredScopes []string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
claims := getIAMTokenClaims(r.Context())
|
|
|
|
if claims == nil {
|
|
writeAuthError(w, http.StatusUnauthorized, "AUTH_CONTEXT_MISSING",
|
|
"authentication context is missing")
|
|
return
|
|
}
|
|
|
|
// 空列表直接通过
|
|
if len(requiredScopes) > 0 && !hasAnyScope(claims.Scope, requiredScopes) {
|
|
writeAuthError(w, http.StatusForbidden, "AUTH_SCOPE_DENIED",
|
|
"none of the required scopes are granted")
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireRole 返回一个要求特定角色的中间件
|
|
func (m *ScopeAuthMiddleware) RequireRole(requiredRole string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
claims := getIAMTokenClaims(r.Context())
|
|
|
|
if claims == nil {
|
|
writeAuthError(w, http.StatusUnauthorized, "AUTH_CONTEXT_MISSING",
|
|
"authentication context is missing")
|
|
return
|
|
}
|
|
|
|
if claims.Role != requiredRole {
|
|
writeAuthError(w, http.StatusForbidden, "AUTH_ROLE_DENIED",
|
|
"required role is not granted")
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireMinLevel 返回一个要求最小角色层级的中间件
|
|
func (m *ScopeAuthMiddleware) RequireMinLevel(minLevel int) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
claims := getIAMTokenClaims(r.Context())
|
|
|
|
if claims == nil {
|
|
writeAuthError(w, http.StatusUnauthorized, "AUTH_CONTEXT_MISSING",
|
|
"authentication context is missing")
|
|
return
|
|
}
|
|
|
|
level := GetRoleLevel(claims.Role)
|
|
if level < minLevel {
|
|
writeAuthError(w, http.StatusForbidden, "AUTH_ROLE_LEVEL_DENIED",
|
|
"insufficient role level")
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// hasAnyScope 检查scope列表是否包含任一目标scope
|
|
func hasAnyScope(scopes, targets []string) bool {
|
|
for _, scope := range scopes {
|
|
for _, target := range targets {
|
|
if scope == target || scope == "*" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// writeAuthError 写入鉴权错误
|
|
func writeAuthError(w http.ResponseWriter, status int, code, message string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
resp := map[string]interface{}{
|
|
"error": map[string]string{
|
|
"code": code,
|
|
"message": message,
|
|
},
|
|
}
|
|
_ = resp
|
|
}
|
|
|
|
// WithIAMClaims 设置IAM Claims到Context
|
|
func WithIAMClaims(ctx context.Context, claims *IAMTokenClaims) context.Context {
|
|
return context.WithValue(ctx, IAMTokenClaimsKey, *claims)
|
|
}
|
|
|
|
// GetClaimsFromLegacy 从原有middleware.TokenClaims转换为IAMTokenClaims
|
|
func GetClaimsFromLegacy(legacy *middleware.TokenClaims) *IAMTokenClaims {
|
|
if legacy == nil {
|
|
return nil
|
|
}
|
|
return &IAMTokenClaims{
|
|
SubjectID: legacy.SubjectID,
|
|
Role: legacy.Role,
|
|
Scope: legacy.Scope,
|
|
TenantID: legacy.TenantID,
|
|
}
|
|
}
|