170 lines
5.6 KiB
Go
170 lines
5.6 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"data-terminal-cloud/internal/idempotency"
|
|
"data-terminal-cloud/internal/opsaccess"
|
|
)
|
|
|
|
func CreateOpsSensitiveAccessRequestHandler(service *opsaccess.Service) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := webPrincipal(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
tenantID, key, ok := opsCreateHeaders(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var input struct {
|
|
ConversationID string `json:"conversation_id"`
|
|
Capabilities []string `json:"capabilities"`
|
|
Reason string `json:"reason"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
if !decodeWebJSON(w, r, &input, "临时敏感数据访问申请参数不正确") {
|
|
return
|
|
}
|
|
item, created, err := service.CreateRequest(r.Context(), principal, tenantID, input.ConversationID, input.Capabilities, input.Reason, input.ExpiresAt, key, RequestID(r.Context()))
|
|
if err != nil {
|
|
writeOpsAccessError(w, r, err)
|
|
return
|
|
}
|
|
status := http.StatusCreated
|
|
if !created {
|
|
status = http.StatusOK
|
|
w.Header().Set("Idempotency-Replayed", "true")
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
WriteJSON(w, r, status, item)
|
|
})
|
|
}
|
|
|
|
func ListOpsSensitiveAccessRequestsHandler(service *opsaccess.Service) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := webPrincipal(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
tenantID, ok := opsTenantHeader(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
items, err := service.ListRequests(r.Context(), principal, tenantID)
|
|
if err != nil {
|
|
writeOpsAccessError(w, r, err)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
|
})
|
|
}
|
|
|
|
func ReviewOpsSensitiveAccessRequestHandler(service *opsaccess.Service) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := webPrincipal(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
tenantID, key, ok := opsCreateHeaders(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var input struct {
|
|
Decision string `json:"decision"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
if !decodeWebJSON(w, r, &input, "临时访问审批参数不正确") {
|
|
return
|
|
}
|
|
item, created, err := service.ReviewRequest(r.Context(), principal, tenantID, r.PathValue("id"), input.Decision, input.Reason, key, RequestID(r.Context()))
|
|
if err != nil {
|
|
writeOpsAccessError(w, r, err)
|
|
return
|
|
}
|
|
if !created {
|
|
w.Header().Set("Idempotency-Replayed", "true")
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
WriteJSON(w, r, http.StatusOK, item)
|
|
})
|
|
}
|
|
|
|
func GetOpsSensitiveConversationHandler(service *opsaccess.Service) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := webPrincipal(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
tenantID, ok := opsTenantHeader(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
grantID := strings.TrimSpace(r.Header.Get("X-Ops-Access-Grant"))
|
|
if grantID == "" {
|
|
writeValidation(w, r, "临时访问凭据缺失", FieldError{Field: "X-Ops-Access-Grant", Message: "必须提供已批准且未过期的临时访问申请编号"})
|
|
return
|
|
}
|
|
item, err := service.GetSensitiveConversation(r.Context(), principal, tenantID, r.PathValue("id"), grantID, RequestID(r.Context()))
|
|
if err != nil {
|
|
writeOpsAccessError(w, r, err)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
WriteJSON(w, r, http.StatusOK, item)
|
|
})
|
|
}
|
|
|
|
func CreateOpsSensitiveRecordingAccessHandler(service *opsaccess.Service) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
principal, ok := webPrincipal(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
tenantID, ok := opsTenantHeader(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var input struct {
|
|
GrantID string `json:"grant_id"`
|
|
}
|
|
if !decodeWebJSON(w, r, &input, "录音临时访问参数不正确") {
|
|
return
|
|
}
|
|
if strings.TrimSpace(input.GrantID) == "" {
|
|
writeValidation(w, r, "录音临时访问参数不正确", FieldError{Field: "grant_id", Message: "必须提供已批准且未过期的临时访问申请编号"})
|
|
return
|
|
}
|
|
item, err := service.CreateRecordingAccess(r.Context(), principal, tenantID, r.PathValue("id"), input.GrantID, RequestID(r.Context()))
|
|
if err != nil {
|
|
writeOpsAccessError(w, r, err)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
WriteJSON(w, r, http.StatusCreated, item)
|
|
})
|
|
}
|
|
|
|
func writeOpsAccessError(w http.ResponseWriter, r *http.Request, err error) {
|
|
switch {
|
|
case errors.Is(err, opsaccess.ErrInvalid):
|
|
writeValidation(w, r, "临时敏感数据访问参数不正确", FieldError{Field: "temporary_access", Message: "通话、能力、原因或有效时间不符合要求"})
|
|
case errors.Is(err, opsaccess.ErrAccessDenied):
|
|
WriteError(w, r, http.StatusForbidden, "TEMPORARY_ACCESS_DENIED", "临时访问未批准、已过期、能力不匹配或不属于当前申请人")
|
|
case errors.Is(err, opsaccess.ErrNotFound):
|
|
WriteError(w, r, http.StatusNotFound, "RESOURCE_NOT_FOUND", "指定临时访问申请、通话或录音不存在")
|
|
case errors.Is(err, opsaccess.ErrConflict):
|
|
WriteError(w, r, http.StatusConflict, "TEMPORARY_ACCESS_STATE_CONFLICT", "已有未结束申请、申请人尝试自批或当前状态不允许操作")
|
|
case errors.Is(err, idempotency.ErrKeyConflict):
|
|
WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT", "重复请求识别码已被不同请求使用")
|
|
case errors.Is(err, idempotency.ErrInProgress):
|
|
WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_IN_PROGRESS", "相同请求正在处理中")
|
|
default:
|
|
WriteError(w, r, http.StatusInternalServerError, "INTERNAL_ERROR", "临时敏感数据访问处理失败")
|
|
}
|
|
}
|