Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。 Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。 Confidence: medium Scope-risk: broad Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。 Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。 Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
266 lines
9.6 KiB
Go
266 lines
9.6 KiB
Go
package admin
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
|
auditquery "github.com/break/junhong_cmp_fiber/internal/query/audit"
|
|
integrationquery "github.com/break/junhong_cmp_fiber/internal/query/integration"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
"github.com/break/junhong_cmp_fiber/pkg/response"
|
|
)
|
|
|
|
// AuditHandler 提供平台基础审计调查只读接口。
|
|
type AuditHandler struct {
|
|
auditQuery *auditquery.Query
|
|
integrationQuery *integrationquery.Query
|
|
}
|
|
|
|
// NewAuditHandler 创建平台基础审计调查 Handler。
|
|
func NewAuditHandler(auditQuery *auditquery.Query, integrationQuery *integrationquery.Query) *AuditHandler {
|
|
return &AuditHandler{auditQuery: auditQuery, integrationQuery: integrationQuery}
|
|
}
|
|
|
|
// ListEvents 查询平台全局审计事件。
|
|
// GET /api/admin/audit/events
|
|
func (h *AuditHandler) ListEvents(c *fiber.Ctx) error {
|
|
var request dto.AuditEventListRequest
|
|
if err := c.QueryParser(&request); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
|
|
if err != nil || invalidAuditPage(request.Page, request.PageSize) {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
result, err := h.auditQuery.List(c.UserContext(), auditquery.EventFilter{
|
|
CreatedFrom: from, CreatedTo: to, Action: request.Action, Category: request.Category,
|
|
ActorKind: request.ActorKind, ActorID: request.ActorID, Source: request.Source,
|
|
Result: request.Result, Risk: request.Risk, ScopeType: request.ScopeType, ScopeID: request.ScopeID,
|
|
ResourceType: request.ResourceType, ResourceID: request.ResourceID, ResourceKey: request.ResourceKey,
|
|
RequestID: request.RequestID, CorrelationID: request.CorrelationID,
|
|
Page: request.Page, PageSize: request.PageSize,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// GetEvent 查询单个稳定审计事件详情。
|
|
// GET /api/admin/audit/events/:event_id
|
|
func (h *AuditHandler) GetEvent(c *fiber.Ctx) error {
|
|
result, err := h.auditQuery.Get(c.UserContext(), c.Params("event_id"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// ListActorEvents 查询操作者行为时间线。
|
|
// GET /api/admin/audit/actors/:kind/:id/events
|
|
func (h *AuditHandler) ListActorEvents(c *fiber.Ctx) error {
|
|
var request dto.AuditActorEventsRequest
|
|
if err := c.QueryParser(&request); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
request.Kind, request.ID = c.Params("kind"), c.Params("id")
|
|
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
|
|
if err != nil || invalidAuditPage(request.Page, request.PageSize) {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
result, err := h.auditQuery.ListActorEvents(c.UserContext(), auditquery.ActorEventFilter{
|
|
Kind: request.Kind, ID: request.ID, Action: request.Action, Result: request.Result, Risk: request.Risk,
|
|
ResourceType: request.ResourceType, ResourceID: request.ResourceID,
|
|
CreatedFrom: from, CreatedTo: to, Page: request.Page, PageSize: request.PageSize,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// SearchResources 按注册业务标识精确搜索资源。
|
|
// GET /api/admin/audit/resources/search
|
|
func (h *AuditHandler) SearchResources(c *fiber.Ctx) error {
|
|
var request dto.AuditResourceSearchRequest
|
|
if err := c.QueryParser(&request); err != nil || invalidAuditPage(request.Page, request.PageSize) {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
result, err := h.auditQuery.SearchResources(c.UserContext(), auditquery.ResourceSearchFilter{
|
|
ResourceType: request.ResourceType, Keyword: request.Keyword,
|
|
Page: request.Page, PageSize: request.PageSize,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// ResourceTimeline 查询资源作为任意关系参与的通用事件时间线。
|
|
// GET /api/admin/audit/resources/:resource_type/:resource_id/timeline
|
|
func (h *AuditHandler) ResourceTimeline(c *fiber.Ctx) error {
|
|
var request dto.AuditResourceTimelineRequest
|
|
if err := c.QueryParser(&request); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
request.ResourceType, request.ResourceID = c.Params("resource_type"), c.Params("resource_id")
|
|
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
|
|
if err != nil || invalidAuditPage(request.Page, request.PageSize) {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
result, err := h.auditQuery.ResourceTimeline(c.UserContext(), auditquery.ResourceTimelineFilter{
|
|
ResourceType: request.ResourceType, ResourceID: request.ResourceID,
|
|
CreatedFrom: from, CreatedTo: to, Action: request.Action, Result: request.Result,
|
|
Page: request.Page, PageSize: request.PageSize,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// AgentResourceActivities 查询代理范围内的安全资源活动。
|
|
// GET /api/admin/agent/resource-activities/:resource_type/:identifier
|
|
func (h *AuditHandler) AgentResourceActivities(c *fiber.Ctx) error {
|
|
request, err := subjectActivityRequest(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := h.auditQuery.AgentResourceActivities(c.UserContext(), auditquery.SubjectActivityFilter{
|
|
ResourceType: request.ResourceType, Identifier: request.Identifier,
|
|
Page: request.Page, PageSize: request.PageSize,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// EnterpriseResourceActivities 查询企业当前有效授权资产的安全资源活动。
|
|
// GET /api/admin/enterprise/resource-activities/:resource_type/:identifier
|
|
func (h *AuditHandler) EnterpriseResourceActivities(c *fiber.Ctx) error {
|
|
request, err := subjectActivityRequest(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := h.auditQuery.EnterpriseResourceActivities(c.UserContext(), auditquery.SubjectActivityFilter{
|
|
ResourceType: request.ResourceType, Identifier: request.Identifier,
|
|
Page: request.Page, PageSize: request.PageSize,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
func subjectActivityRequest(c *fiber.Ctx) (dto.SubjectResourceActivityRequest, error) {
|
|
var request dto.SubjectResourceActivityRequest
|
|
if err := c.QueryParser(&request); err != nil || invalidAuditPage(request.Page, request.PageSize) {
|
|
return request, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
request.ResourceType = c.Params("resource_type")
|
|
request.Identifier = c.Params("identifier")
|
|
if request.ResourceType == "" || request.Identifier == "" {
|
|
return request, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// IntegrationOverview 查询外部集成交互总览。
|
|
// GET /api/admin/audit/integrations/overview
|
|
func (h *AuditHandler) IntegrationOverview(c *fiber.Ctx) error {
|
|
var request dto.IntegrationOverviewRequest
|
|
if err := c.QueryParser(&request); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
filter, err := integrationFilter(request.IntegrationFilterRequest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := h.integrationQuery.Overview(c.UserContext(), integrationquery.OverviewFilter{
|
|
ListFilter: filter,
|
|
Bucket: request.Bucket,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// ListIntegrations 查询外部集成交互列表。
|
|
// GET /api/admin/audit/integrations
|
|
func (h *AuditHandler) ListIntegrations(c *fiber.Ctx) error {
|
|
var request dto.IntegrationListRequest
|
|
if err := c.QueryParser(&request); err != nil || invalidAuditPage(request.Page, request.PageSize) {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
filter, err := integrationFilter(request.IntegrationFilterRequest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
filter.Page, filter.PageSize = request.Page, request.PageSize
|
|
result, err := h.integrationQuery.List(c.UserContext(), filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
// GetIntegration 查询稳定外部集成记录详情。
|
|
// GET /api/admin/audit/integrations/:integration_id
|
|
func (h *AuditHandler) GetIntegration(c *fiber.Ctx) error {
|
|
result, err := h.integrationQuery.Get(c.UserContext(), c.Params("integration_id"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, result)
|
|
}
|
|
|
|
func integrationFilter(request dto.IntegrationFilterRequest) (integrationquery.ListFilter, error) {
|
|
from, to, err := auditTimeRange(request.CreatedFrom, request.CreatedTo)
|
|
if err != nil {
|
|
return integrationquery.ListFilter{}, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
return integrationquery.ListFilter{
|
|
CreatedFrom: from, CreatedTo: to, IntegrationID: request.IntegrationID,
|
|
Provider: request.Provider, Direction: request.Direction, Operation: request.Operation,
|
|
Result: request.Result, ResultCategory: request.ResultCategory, ExternalID: request.ExternalID,
|
|
ResourceType: request.ResourceType, ResourceID: request.ResourceID, ResourceKey: request.ResourceKey,
|
|
TriggerSource: request.TriggerSource, TriggerScene: request.TriggerScene, TriggerSeries: request.TriggerSeries,
|
|
StateChanged: request.StateChanged, HTTPStatus: request.HTTPStatus, ProviderCode: request.ProviderCode,
|
|
RequestID: request.RequestID, CorrelationID: request.CorrelationID,
|
|
}, nil
|
|
}
|
|
|
|
func auditTimeRange(fromValue, toValue string) (*time.Time, *time.Time, error) {
|
|
from, err := optionalAuditTime(fromValue)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
to, err := optionalAuditTime(toValue)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if from != nil && to != nil && !from.Before(*to) {
|
|
return nil, nil, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
return from, to, nil
|
|
}
|
|
|
|
func optionalAuditTime(value string) (*time.Time, error) {
|
|
if value == "" {
|
|
return nil, nil
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &parsed, nil
|
|
}
|
|
|
|
func invalidAuditPage(page, pageSize int) bool {
|
|
return page < 0 || pageSize < 0 || pageSize > 100
|
|
}
|