Files
lijiaoqiao/supply-api/internal/iam/handler/iam_handler.go
Your Name 89104bd0db feat(P1/P2): 完成TDD开发及P1/P2设计文档
## 设计文档
- 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规范
2026-04-02 23:35:53 +08:00

508 lines
14 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 handler
import (
"context"
"encoding/json"
"net/http"
"strconv"
"lijiaoqiao/supply-api/internal/iam/service"
)
// IAMHandler IAM HTTP处理器
type IAMHandler struct {
iamService service.IAMServiceInterface
}
// NewIAMHandler 创建IAM处理器
func NewIAMHandler(iamService service.IAMServiceInterface) *IAMHandler {
return &IAMHandler{
iamService: iamService,
}
}
// RoleResponse HTTP响应中的角色信息
type RoleResponse struct {
Code string `json:"role_code"`
Name string `json:"role_name"`
Type string `json:"role_type"`
Level int `json:"level"`
Scopes []string `json:"scopes,omitempty"`
IsActive bool `json:"is_active"`
}
// CreateRoleRequest 创建角色请求
type CreateRoleRequest struct {
Code string `json:"code"`
Name string `json:"name"`
Type string `json:"type"`
Level int `json:"level"`
Scopes []string `json:"scopes"`
}
// UpdateRoleRequest 更新角色请求
type UpdateRoleRequest struct {
Code string `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
Scopes []string `json:"scopes"`
IsActive *bool `json:"is_active"`
}
// AssignRoleRequest 分配角色请求
type AssignRoleRequest struct {
RoleCode string `json:"role_code"`
TenantID int64 `json:"tenant_id"`
ExpiresAt string `json:"expires_at,omitempty"`
}
// HTTPError HTTP错误响应
type HTTPError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// ErrorResponse 错误响应结构
type ErrorResponse struct {
Error HTTPError `json:"error"`
}
// RegisterRoutes 注册IAM路由
func (h *IAMHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/iam/roles", h.handleRoles)
mux.HandleFunc("/api/v1/iam/roles/", h.handleRoleByCode)
mux.HandleFunc("/api/v1/iam/scopes", h.handleScopes)
mux.HandleFunc("/api/v1/iam/users/", h.handleUserRoles)
mux.HandleFunc("/api/v1/iam/check-scope", h.handleCheckScope)
}
// handleRoles 处理角色相关路由
func (h *IAMHandler) handleRoles(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.ListRoles(w, r)
case http.MethodPost:
h.CreateRole(w, r)
default:
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "method not allowed")
}
}
// handleRoleByCode 处理单个角色路由
func (h *IAMHandler) handleRoleByCode(w http.ResponseWriter, r *http.Request) {
roleCode := extractRoleCode(r.URL.Path)
switch r.Method {
case http.MethodGet:
h.GetRole(w, r, roleCode)
case http.MethodPut:
h.UpdateRole(w, r, roleCode)
case http.MethodDelete:
h.DeleteRole(w, r, roleCode)
default:
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "method not allowed")
}
}
// handleScopes 处理Scope列表路由
func (h *IAMHandler) handleScopes(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "method not allowed")
return
}
h.ListScopes(w, r)
}
// handleUserRoles 处理用户角色路由
func (h *IAMHandler) handleUserRoles(w http.ResponseWriter, r *http.Request) {
// 解析用户ID
path := r.URL.Path
userIDStr := extractUserID(path)
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil {
writeError(w, http.StatusBadRequest, "INVALID_USER_ID", "invalid user id")
return
}
switch r.Method {
case http.MethodGet:
h.GetUserRoles(w, r, userID)
case http.MethodPost:
h.AssignRole(w, r, userID)
case http.MethodDelete:
roleCode := extractRoleCodeFromUserPath(path)
tenantID := int64(0) // 从请求或context获取
h.RevokeRole(w, r, userID, roleCode, tenantID)
default:
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "method not allowed")
}
}
// handleCheckScope 处理检查Scope路由
func (h *IAMHandler) handleCheckScope(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "method not allowed")
return
}
h.CheckScope(w, r)
}
// CreateRole 处理创建角色请求
func (h *IAMHandler) CreateRole(w http.ResponseWriter, r *http.Request) {
var req CreateRoleRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
// 验证必填字段
if req.Code == "" {
writeError(w, http.StatusBadRequest, "MISSING_CODE", "role code is required")
return
}
if req.Name == "" {
writeError(w, http.StatusBadRequest, "MISSING_NAME", "role name is required")
return
}
if req.Type == "" {
writeError(w, http.StatusBadRequest, "MISSING_TYPE", "role type is required")
return
}
serviceReq := &service.CreateRoleRequest{
Code: req.Code,
Name: req.Name,
Type: req.Type,
Level: req.Level,
Scopes: req.Scopes,
}
role, err := h.iamService.CreateRole(r.Context(), serviceReq)
if err != nil {
if err == service.ErrDuplicateRoleCode {
writeError(w, http.StatusConflict, "DUPLICATE_ROLE_CODE", err.Error())
return
}
if err == service.ErrInvalidRequest {
writeError(w, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"role": toRoleResponse(role),
})
}
// GetRole 处理获取单个角色请求
func (h *IAMHandler) GetRole(w http.ResponseWriter, r *http.Request, roleCode string) {
role, err := h.iamService.GetRole(r.Context(), roleCode)
if err != nil {
if err == service.ErrRoleNotFound {
writeError(w, http.StatusNotFound, "ROLE_NOT_FOUND", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"role": toRoleResponse(role),
})
}
// ListRoles 处理列出角色请求
func (h *IAMHandler) ListRoles(w http.ResponseWriter, r *http.Request) {
roleType := r.URL.Query().Get("type")
roles, err := h.iamService.ListRoles(r.Context(), roleType)
if err != nil {
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
roleResponses := make([]*RoleResponse, len(roles))
for i, role := range roles {
roleResponses[i] = toRoleResponse(role)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"roles": roleResponses,
})
}
// UpdateRole 处理更新角色请求
func (h *IAMHandler) UpdateRole(w http.ResponseWriter, r *http.Request, roleCode string) {
var req UpdateRoleRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
req.Code = roleCode // 确保使用URL中的roleCode
serviceReq := &service.UpdateRoleRequest{
Code: req.Code,
Name: req.Name,
Description: req.Description,
Scopes: req.Scopes,
IsActive: req.IsActive,
}
role, err := h.iamService.UpdateRole(r.Context(), serviceReq)
if err != nil {
if err == service.ErrRoleNotFound {
writeError(w, http.StatusNotFound, "ROLE_NOT_FOUND", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"role": toRoleResponse(role),
})
}
// DeleteRole 处理删除角色请求
func (h *IAMHandler) DeleteRole(w http.ResponseWriter, r *http.Request, roleCode string) {
err := h.iamService.DeleteRole(r.Context(), roleCode)
if err != nil {
if err == service.ErrRoleNotFound {
writeError(w, http.StatusNotFound, "ROLE_NOT_FOUND", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "role deleted successfully",
})
}
// ListScopes 处理列出所有Scope请求
func (h *IAMHandler) ListScopes(w http.ResponseWriter, r *http.Request) {
// 从预定义Scope列表获取
scopes := []map[string]interface{}{
{"scope_code": "platform:read", "scope_name": "读取平台配置", "scope_type": "platform"},
{"scope_code": "platform:write", "scope_name": "修改平台配置", "scope_type": "platform"},
{"scope_code": "platform:admin", "scope_name": "平台级管理", "scope_type": "platform"},
{"scope_code": "tenant:read", "scope_name": "读取租户信息", "scope_type": "platform"},
{"scope_code": "supply:account:read", "scope_name": "读取供应账号", "scope_type": "supply"},
{"scope_code": "consumer:apikey:create", "scope_name": "创建API Key", "scope_type": "consumer"},
{"scope_code": "router:invoke", "scope_name": "调用模型", "scope_type": "router"},
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"scopes": scopes,
})
}
// GetUserRoles 处理获取用户角色请求
func (h *IAMHandler) GetUserRoles(w http.ResponseWriter, r *http.Request, userID int64) {
roles, err := h.iamService.GetUserRoles(r.Context(), userID)
if err != nil {
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"user_id": userID,
"roles": roles,
})
}
// AssignRole 处理分配角色请求
func (h *IAMHandler) AssignRole(w http.ResponseWriter, r *http.Request, userID int64) {
var req AssignRoleRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
serviceReq := &service.AssignRoleRequest{
UserID: userID,
RoleCode: req.RoleCode,
TenantID: req.TenantID,
}
mapping, err := h.iamService.AssignRole(r.Context(), serviceReq)
if err != nil {
if err == service.ErrRoleNotFound {
writeError(w, http.StatusNotFound, "ROLE_NOT_FOUND", err.Error())
return
}
if err == service.ErrDuplicateAssignment {
writeError(w, http.StatusConflict, "DUPLICATE_ASSIGNMENT", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"message": "role assigned successfully",
"mapping": mapping,
})
}
// RevokeRole 处理撤销角色请求
func (h *IAMHandler) RevokeRole(w http.ResponseWriter, r *http.Request, userID int64, roleCode string, tenantID int64) {
err := h.iamService.RevokeRole(r.Context(), userID, roleCode, tenantID)
if err != nil {
if err == service.ErrRoleNotFound {
writeError(w, http.StatusNotFound, "ROLE_NOT_FOUND", err.Error())
return
}
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "role revoked successfully",
})
}
// CheckScope 处理检查Scope请求
func (h *IAMHandler) CheckScope(w http.ResponseWriter, r *http.Request) {
scope := r.URL.Query().Get("scope")
if scope == "" {
writeError(w, http.StatusBadRequest, "MISSING_SCOPE", "scope parameter is required")
return
}
// 从context获取userID实际应用中应从认证中间件获取
userID := int64(1) // 模拟
hasScope, err := h.iamService.CheckScope(r.Context(), userID, scope)
if err != nil {
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"has_scope": hasScope,
"scope": scope,
"user_id": userID,
})
}
// toRoleResponse 转换为RoleResponse
func toRoleResponse(role *service.Role) *RoleResponse {
return &RoleResponse{
Code: role.Code,
Name: role.Name,
Type: role.Type,
Level: role.Level,
IsActive: role.IsActive,
}
}
// writeJSON 写入JSON响应
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
// writeError 写入错误响应
func writeError(w http.ResponseWriter, status int, code, message string) {
writeJSON(w, status, ErrorResponse{
Error: HTTPError{
Code: code,
Message: message,
},
})
}
// extractRoleCode 从URL路径提取角色代码
func extractRoleCode(path string) string {
// /api/v1/iam/roles/developer -> developer
parts := splitPath(path)
if len(parts) >= 5 {
return parts[4]
}
return ""
}
// extractUserID 从URL路径提取用户ID
func extractUserID(path string) string {
// /api/v1/iam/users/123/roles -> 123
parts := splitPath(path)
if len(parts) >= 4 {
return parts[3]
}
if len(parts) >= 6 {
return parts[3]
}
return ""
}
// extractRoleCodeFromUserPath 从用户路径提取角色代码
func extractRoleCodeFromUserPath(path string) string {
// /api/v1/iam/users/123/roles/developer -> developer
parts := splitPath(path)
if len(parts) >= 6 {
return parts[5]
}
return ""
}
// splitPath 分割URL路径
func splitPath(path string) []string {
var parts []string
var current string
for _, c := range path {
if c == '/' {
if current != "" {
parts = append(parts, current)
current = ""
}
} else {
current += string(c)
}
}
if current != "" {
parts = append(parts, current)
}
return parts
}
// RequireScope 返回一个要求特定Scope的中间件函数
func RequireScope(scope string, iamService service.IAMServiceInterface) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从context获取userID
userID := getUserIDFromContext(r.Context())
if userID == 0 {
writeError(w, http.StatusUnauthorized, "UNAUTHORIZED", "user not authenticated")
return
}
hasScope, err := iamService.CheckScope(r.Context(), userID, scope)
if err != nil {
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
return
}
if !hasScope {
writeError(w, http.StatusForbidden, "SCOPE_DENIED", "insufficient scope")
return
}
next.ServeHTTP(w, r)
})
}
}
// getUserIDFromContext 从context获取userID实际应用中应从认证中间件获取
func getUserIDFromContext(ctx context.Context) int64 {
// TODO: 从认证中间件获取真实的userID
return 1
}