From e175b6f72d0059ccc8d422c096a3e00a657f884c Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:00:34 +0800 Subject: [PATCH 01/12] docs: plan apisec cli --- .../plans/2026-05-21-apisec-cli.md | 526 ++++++++++++++++++ .../specs/2026-05-21-apisec-cli-design.md | 89 +++ 2 files changed, 615 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-21-apisec-cli.md create mode 100644 docs/superpowers/specs/2026-05-21-apisec-cli-design.md diff --git a/docs/superpowers/plans/2026-05-21-apisec-cli.md b/docs/superpowers/plans/2026-05-21-apisec-cli.md new file mode 100644 index 0000000..2721c01 --- /dev/null +++ b/docs/superpowers/plans/2026-05-21-apisec-cli.md @@ -0,0 +1,526 @@ +# APISec CLI Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add an `apisec` product command that exposes all generated APISec APIs through `raw` commands and priority workflows through semantic commands with AI-safe help text. + +**Architecture:** Build a product-local OpenAPI/mapping loader under `products/apisec/`. Generate a static APISec OpenAPI document from the APISec Django `APIView` source, embed it with `cli-mapping.yaml`, and generate Cobra commands at startup. Keep raw command generation exhaustive and semantic command generation mapping-driven. + +**Tech Stack:** Go, Cobra, embedded files, YAML/JSON, standard `net/http`, existing `config` package, Python source introspection for one-time OpenAPI generation. + +--- + +## File Structure + +- Create `products/apisec/types.go`: APISec config, OpenAPI, mapping, and command metadata types. +- Create `products/apisec/client.go`: HTTP client with `API-TOKEN` authentication, JSON request handling, dry-run support, and response parsing. +- Create `products/apisec/render.go`: table/json output rendering, initially JSON-first for predictable AI consumption. +- Create `products/apisec/schema_loader.go`: embedded `v26.05` schema and mapping loading. +- Create `products/apisec/parser.go`: raw command generation and semantic mapping command generation. +- Create `products/apisec/command.go`: `apisec` root command, flags, runtime config application, command registration. +- Create `products/apisec/testdata/openapi_minimal.json`: compact OpenAPI fixture for parser tests. +- Create `products/apisec/testdata/cli-mapping.yaml`: compact mapping fixture for semantic command tests. +- Create `products/apisec/v26.05/openapi.json`: generated static APISec schema. +- Create `products/apisec/v26.05/cli-mapping.yaml`: first semantic mapping for priority modules. +- Create `products/apisec/latest`: text file containing `v26.05`; use this instead of a symlink so Go embed works consistently. +- Create `tools/apisec-openapi-gen/README.md`: describe how the schema was generated and what it cannot infer. +- Create `tools/apisec-openapi-gen/generate.py`: source scanner that emits the first OpenAPI document from APISec `APIView` classes. +- Modify `main.go`: import and register `products/apisec`; apply runtime config in `wrapProductCommand`. +- Modify `README.md` or `config.yaml.example` only if an existing config example section exists and the change is small. + +## Task 1: APISec Types And Fixtures + +**Files:** +- Create: `products/apisec/types.go` +- Create: `products/apisec/testdata/openapi_minimal.json` +- Create: `products/apisec/testdata/cli-mapping.yaml` +- Test: `products/apisec/schema_loader_test.go` + +- [ ] **Step 1: Add failing loader tests** + +Create `products/apisec/schema_loader_test.go` with tests that load explicit fixture bytes, assert OpenAPI paths are parsed, and assert mapping command paths resolve to operation IDs. + +```go +package apisec + +import "testing" + +func TestParseOpenAPI(t *testing.T) { + api, err := parseOpenAPI([]byte(`{"openapi":"3.0.3","info":{"title":"APISec","version":"26.05"},"paths":{"/api/ApplicationAPI":{"get":{"operationId":"ApplicationAPI_get","summary":"List applications"}}}}`)) + if err != nil { + t.Fatalf("parseOpenAPI() error = %v", err) + } + if api.Paths["/api/ApplicationAPI"].Get.OperationID != "ApplicationAPI_get" { + t.Fatalf("operationId not parsed: %+v", api.Paths["/api/ApplicationAPI"].Get) + } +} + +func TestParseCLIMapping(t *testing.T) { + mapping, err := parseCLIMapping([]byte(`commands: + - path: [asset, app, list] + operationId: ApplicationAPI_get + short: List applications +`)) + if err != nil { + t.Fatalf("parseCLIMapping() error = %v", err) + } + if got := mapping.Commands[0].Path[2]; got != "list" { + t.Fatalf("path[2] = %q, want list", got) + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `go test ./products/apisec` + +Expected: fail because `products/apisec` and parser functions do not exist yet. + +- [ ] **Step 3: Implement types and parse helpers** + +Create `products/apisec/types.go` with `Config`, `OpenAPI`, `PathItem`, `Operation`, `Parameter`, `Schema`, `RequestBody`, `CLIMapping`, and `MappedCommand`. Include `operationId`, request body, schema descriptions, enum, items, and properties fields because help generation needs them. + +Create `products/apisec/schema_loader.go` with: + +```go +func parseOpenAPI(data []byte) (*OpenAPI, error) +func parseCLIMapping(data []byte) (*CLIMapping, error) +``` + +Use `encoding/json` for OpenAPI and `gopkg.in/yaml.v3` for mapping. + +- [ ] **Step 4: Add fixtures** + +Add `products/apisec/testdata/openapi_minimal.json` with one GET and one POST operation. Add `products/apisec/testdata/cli-mapping.yaml` with one mapped semantic command and one raw-hidden false default. + +- [ ] **Step 5: Run tests** + +Run: `go test ./products/apisec` + +Expected: pass. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add products/apisec/types.go products/apisec/schema_loader.go products/apisec/schema_loader_test.go products/apisec/testdata +git commit -m "feat: add apisec schema types" +``` + +## Task 2: APISec Client And Renderer + +**Files:** +- Create: `products/apisec/client.go` +- Create: `products/apisec/client_test.go` +- Create: `products/apisec/render.go` +- Create: `products/apisec/render_test.go` + +- [ ] **Step 1: Add client tests** + +Create tests with `httptest.Server` that verify `API-TOKEN` is sent, JSON body is encoded, query parameters are preserved, and APISec response envelopes render `data` when `err` is null. + +Test cases: + +- `TestClientSetsAPITokenHeader`: server asserts `r.Header.Get("API-TOKEN") == "token-1"`. +- `TestClientSendsJSONBody`: server decodes body `{"name":"app"}`. +- `TestClientReturnsAPIError`: server returns `{"err":"bad-request","msg":"invalid"}` and client returns an error containing `bad-request`. + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `go test ./products/apisec -run 'TestClient|TestRenderer'` + +Expected: fail because client and renderer do not exist. + +- [ ] **Step 3: Implement client** + +Implement `NewClient(cfg *Config, verbose bool) *Client` and `Do(ctx, method, path string, query url.Values, body any, result any) error`. + +Rules: + +- Trim trailing slash from `Config.URL`. +- Set `API-TOKEN` from `Config.APIToken` when non-empty. +- Set `Content-Type: application/json` only when body is non-nil. +- Treat HTTP status `>=400` as an error. +- For APISec response envelopes, if JSON has non-empty `err`, return an API error containing `err` and `msg`; otherwise expose `data` to the renderer. +- Respect package-level `dryRun` by printing request details and not sending the request. + +- [ ] **Step 4: Implement renderer** + +Support `--output json` and `--output table`. For first iteration, table may pretty-print compact JSON with stable indentation when data is not a simple array/object shape. JSON output must emit valid JSON to stdout. + +- [ ] **Step 5: Run tests** + +Run: `go test ./products/apisec -run 'TestClient|TestRenderer'` + +Expected: pass. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add products/apisec/client.go products/apisec/client_test.go products/apisec/render.go products/apisec/render_test.go +git commit -m "feat: add apisec client" +``` + +## Task 3: Raw Command Parser + +**Files:** +- Create: `products/apisec/parser.go` +- Create: `products/apisec/parser_test.go` + +- [ ] **Step 1: Add parser tests** + +Create tests using the minimal fixture to assert: + +- `GenerateRawCommands` creates `raw application-api get` for `/api/ApplicationAPI` GET. +- Help for raw command includes `Endpoint: GET /api/ApplicationAPI` and `Operation ID: ApplicationAPI_get`. +- Query parameters become flags with descriptions and required markers. +- Request bodies add `--body` and `--body-file`. + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `go test ./products/apisec -run TestGenerateRawCommands` + +Expected: fail because parser does not exist. + +- [ ] **Step 3: Implement raw command generation** + +Implement: + +```go +func NewParser(api *OpenAPI, mapping *CLIMapping) *Parser +func (p *Parser) GenerateRawCommands() ([]*cobra.Command, error) +``` + +Raw command naming: + +- Convert API class name to kebab case: `ApplicationAPI` -> `application-api`. +- Child command is HTTP verb: `get`, `post`, `put`, `delete`, `patch`. +- If multiple operations would collide, append operation ID suffix. + +Execution rules: + +- Path parameters replace `{name}`. +- Query parameters go into URL query. +- Header parameters are allowed except managed `API-TOKEN`, `Content-Type`, and `Accept`. +- Body comes from `--body` or `--body-file`; `--body-file` wins. + +Help rules: + +- Long help includes endpoint, operation ID, summary, parameter locations, required fields, and body fallback guidance. +- Do not infer business meaning for raw commands. + +- [ ] **Step 4: Run parser tests** + +Run: `go test ./products/apisec -run TestGenerateRawCommands` + +Expected: pass. + +- [ ] **Step 5: Commit** + +Run: + +```bash +git add products/apisec/parser.go products/apisec/parser_test.go +git commit -m "feat: generate apisec raw commands" +``` + +## Task 4: Semantic Mapping Commands + +**Files:** +- Modify: `products/apisec/parser.go` +- Modify: `products/apisec/parser_test.go` +- Modify: `products/apisec/types.go` + +- [ ] **Step 1: Add semantic command tests** + +Add tests that load mapping fixture and assert: + +- `asset app list` is generated. +- It executes the same operation as `ApplicationAPI_get`. +- Its help includes the business short text and `Operation ID: ApplicationAPI_get`. +- A mapped command can rename flags through mapping entries. + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `go test ./products/apisec -run TestGenerateSemanticCommands` + +Expected: fail because semantic generation does not exist. + +- [ ] **Step 3: Extend mapping types** + +Support this mapping shape: + +```yaml +commands: + - path: [asset, app, list] + operationId: ApplicationAPI_get + short: List applications + long: List APISec applications. + examples: + - chaitin-cli apisec asset app list --output json + flags: + page: + name: page + description: Page number. +``` + +- [ ] **Step 4: Implement semantic command generation** + +Implement `GenerateSemanticCommands`. Build parent commands from mapping paths. Reuse operation execution from raw commands. Apply mapped short, long, examples, and flag descriptions. Keep operation ID references in long help. + +- [ ] **Step 5: Run tests** + +Run: `go test ./products/apisec -run 'TestGenerateSemanticCommands|TestGenerateRawCommands'` + +Expected: pass. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add products/apisec/parser.go products/apisec/parser_test.go products/apisec/types.go +git commit -m "feat: map apisec semantic commands" +``` + +## Task 5: Product Command Integration + +**Files:** +- Create: `products/apisec/command.go` +- Create: `products/apisec/command_test.go` +- Modify: `main.go` + +- [ ] **Step 1: Add command tests** + +Test that `NewCommand()` has persistent flags `--url`, `--api-token`, `--output`, `--verbose`, and that runtime config applies values when flags were not set. + +Test that `apisec --help` mentions `API-TOKEN`, `raw`, `--body`, and `--body-file`. + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `go test ./products/apisec -run TestNewCommand` + +Expected: fail because command root does not exist. + +- [ ] **Step 3: Implement product command** + +Create `NewCommand() *cobra.Command`, `ApplyRuntimeConfig(cmd *cobra.Command, cfg config.Raw, isDryRun bool)`, and internal `applyRuntimeConfig`. + +Root command behavior: + +- `Use: "apisec"` +- Long help explains config, `API-TOKEN`, raw commands, semantic commands, and body fallback. +- Persistent flags: `--url`, `--api-token`, `--output table|json`, `--verbose`. +- Load embedded schema and mapping. +- Add `raw` parent command containing full raw operations. +- Add semantic commands at root. + +- [ ] **Step 4: Register product in root** + +Modify `main.go`: + +- Import `github.com/chaitin/chaitin-cli/products/apisec`. +- Call `a.registerProductCommand(apisec.NewCommand())` in `newApp()`. +- Add `case "apisec": apisec.ApplyRuntimeConfig(command, a.config, a.dryRun)` in `wrapProductCommand`. + +- [ ] **Step 5: Run tests** + +Run: `go test ./products/apisec ./...` + +Expected: pass or reveal unrelated existing failures that must be reported before continuing. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add products/apisec/command.go products/apisec/command_test.go main.go +git commit -m "feat: register apisec product command" +``` + +## Task 6: APISec OpenAPI Generator + +**Files:** +- Create: `tools/apisec-openapi-gen/README.md` +- Create: `tools/apisec-openapi-gen/generate.py` +- Create: `products/apisec/v26.05/openapi.json` +- Create: `products/apisec/latest` +- Modify: `products/apisec/schema_loader.go` + +- [ ] **Step 1: Add generator README** + +Document source path, command, known limits, and output path: + +```bash +python3 tools/apisec-openapi-gen/generate.py \ + --source /Users/rui.zhu/Documents/workspace/04-开发/tools_llm/product_src/a-vatar/skyview/skyview \ + --output products/apisec/v26.05/openapi.json +``` + +- [ ] **Step 2: Implement source scanner** + +Implement a conservative Python AST scanner: + +- Find `class *API(...)` in `*/views.py`. +- Include methods named `get`, `post`, `put`, `delete`, `patch`. +- Detect nearest decorators `@serialize(SomeSerializer)` and `@serialize(SomeSerializer, serializer_many=True)`. +- Emit path `/api/`. +- Emit operation ID `_`. +- Emit summary from class/method docstring when present, otherwise ` /api/`. +- For serializer-backed methods, add JSON request body for non-GET and query parameters for GET when fields can be parsed from the serializer source. +- If fields cannot be parsed, still emit operation with `x-cli-body-fallback: true`. + +- [ ] **Step 3: Generate schema** + +Run the generator command from Step 1. Inspect that output contains priority classes such as `ApplicationAPI_get`, `InterfaceAPI_get`, `DetectorConfigAPI_get`, `DiscoverDataTaskAPI_post`, `RiskEventAPI_get`, and `VulnerabilityAPI_get`. + +- [ ] **Step 4: Embed version layout** + +Create `products/apisec/latest` containing exactly: + +```text +v26.05 +``` + +Update `schema_loader.go` to use `//go:embed latest v26.05/openapi.json v26.05/cli-mapping.yaml` and load the version named by `latest`. + +- [ ] **Step 5: Run schema loader tests** + +Run: `go test ./products/apisec -run TestLoadEmbeddedSchema` + +Expected: pass after adding a test that embedded schema has non-empty paths. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add tools/apisec-openapi-gen products/apisec/v26.05/openapi.json products/apisec/latest products/apisec/schema_loader.go products/apisec/schema_loader_test.go +git commit -m "feat: generate apisec openapi schema" +``` + +## Task 7: Priority CLI Mapping + +**Files:** +- Create: `products/apisec/v26.05/cli-mapping.yaml` +- Modify: `products/apisec/parser_test.go` + +- [ ] **Step 1: Add mapping smoke test** + +Test embedded mapping contains commands for: + +- `asset api` +- `asset site` +- `asset app` +- `asset visitor` +- `asset config` +- `data rule` +- `risk config` +- `risk event` +- `risk vulnerability` + +- [ ] **Step 2: Create initial mapping** + +Map priority operations discovered in APISec source: + +- `ApplicationAPI_get/post/put/delete` under `asset app`. +- `SiteAPI_get/delete` and `SiteInfoAPI_put` under `asset site`. +- `InterfaceAPI_get/delete`, `InterfaceDetailsAPI_get`, `SchemaAPI_get`, `DataTagsAPI_get`, `InterfaceStatusAPI_get/put` under `asset api`. +- `UserSrcIpInfoQueryAPI_get`, `UserSrcIpListAPI_get`, `UserSrcIpGroupAPI_get`, `UserSrcIpAsRequesterAPI_get`, `UserSrcIpAsResponserAPI_get` under `asset visitor`. +- `DetectorConfigAPI_get/put`, `SuccessSignAPI_get/post/put/delete`, `IdentityAPI_get/post/put/delete`, `IgnoredInterfaceAPI_post/put`, `ResourceRecognitionBindingAPI_get/put` under `asset config`. +- `DiscoverDataTaskAPI_post/put`, `BuildInDiscoverDataTaskAPI_put`, `RefreshDesensitizeCacheAPI_post` under `data rule`. +- `SensitiveDataCharacteristicAPI_get` under `data sensitive-transfer` until exact export endpoint is confirmed. +- `RiskStrategyAPI_post/put`, `RiskModelConfigAPI_get/put`, `RiskFunctionStatusAPI_get`, `WeakerCipherAPI_post/delete`, `EventWhiteListAPI_post/put/delete`, `RiskStrategyAuxiliaryInfoAPI_get` under `risk config`. +- `RiskEventAPI_get`, `RiskEventGroupAPI_get/put` under `risk event`. +- `VulnerabilityAPI_get/put` under `risk vulnerability`. + +- [ ] **Step 3: Run mapping tests** + +Run: `go test ./products/apisec -run 'TestEmbeddedMapping|TestGenerateSemanticCommands'` + +Expected: pass. + +- [ ] **Step 4: Commit** + +Run: + +```bash +git add products/apisec/v26.05/cli-mapping.yaml products/apisec/parser_test.go +git commit -m "feat: map priority apisec workflows" +``` + +## Task 8: End-To-End Verification + +**Files:** +- Modify as needed only in files touched by earlier tasks. + +- [ ] **Step 1: Format** + +Run: `task fmt` + +Expected: all Go files formatted. + +- [ ] **Step 2: Run tests** + +Run: `task test` + +Expected: all Go tests pass. + +- [ ] **Step 3: Run lint** + +Run: `task lint` + +Expected: `go vet ./...` passes. + +- [ ] **Step 4: Build** + +Run: `task build` + +Expected: `bin/chaitin-cli` builds. + +- [ ] **Step 5: Help smoke tests** + +Run: + +```bash +bin/chaitin-cli apisec --help +bin/chaitin-cli apisec raw --help +bin/chaitin-cli apisec asset app --help +bin/chaitin-cli apisec risk event --help +``` + +Expected: help mentions raw coverage, semantic command groups, `API-TOKEN`, operation IDs for operation commands, and `--body` / `--body-file` where applicable. + +- [ ] **Step 6: Dry-run smoke test** + +Run a safe dry-run command against a placeholder URL, for example: + +```bash +bin/chaitin-cli --dry-run apisec --url https://apisec.example --api-token token raw application-api get --output json +``` + +Expected: request method, URL, and `API-TOKEN` header are printed; no network request is sent. + +- [ ] **Step 7: Final status** + +Run: `git status --short --branch` + +Expected: clean branch after final commit, or only intentional uncommitted verification artifacts. + +- [ ] **Step 8: Commit final fixes if needed** + +If verification required fixes, commit them: + +```bash +git add products/apisec main.go tools/apisec-openapi-gen docs/superpowers +git commit -m "test: verify apisec cli" +``` + +## Self-Review Notes + +- Spec coverage: raw full coverage is implemented in Tasks 3 and 6; semantic priority coverage is implemented in Tasks 4 and 7; help robustness is implemented and verified in Tasks 3, 4, 5, and 8; config/auth is implemented in Tasks 2 and 5. +- Scope risk: runtime product-side schema fetching is intentionally not part of this first implementation; the loader boundary in Task 6 keeps that path open. +- Known gap: sensitive data conditional export endpoint is not yet confirmed in source inspection, so the first mapping includes the closest known sensitive data endpoint and leaves exact export mapping to source confirmation during Task 7. diff --git a/docs/superpowers/specs/2026-05-21-apisec-cli-design.md b/docs/superpowers/specs/2026-05-21-apisec-cli-design.md new file mode 100644 index 0000000..e5732e6 --- /dev/null +++ b/docs/superpowers/specs/2026-05-21-apisec-cli-design.md @@ -0,0 +1,89 @@ +# APISec CLI Design + +## Goal + +Add an `apisec` product command to `chaitin-cli` using the version-aware OpenAPI and CLI mapping approach described in the product version maintenance design docs. + +The first usable version should expose all exported APISec management APIs while giving better command names and help text for the highest-value APISec workflows. + +## Product API Source + +APISec source lives in `/Users/rui.zhu/Documents/workspace/04-开发/tools_llm/product_src/a-vatar`. The management backend is primarily under `skyview/skyview`. + +The backend exports API classes derived from `utils.api.APIView`. Exported URLs are generated from class names, normally as `/api/`. Many request schemas are declared with `@serialize()`, which makes static OpenAPI generation feasible. Some endpoints read `request.GET` or `request.data` manually, so the generated schema must support a generic body fallback. + +Authentication uses the `API-TOKEN` HTTP header. The CLI config should use this shape: + +```yaml +apisec: + url: https://your-apisec.example + api_token: your-api-token +``` + +## Command Model + +Use a two-layer command model. + +The complete layer exposes every generated operation under `apisec raw`. This gives full API coverage even when no curated mapping exists. + +The curated layer maps priority workflows into stable semantic commands through `cli-mapping.yaml`. These commands should have explicit names, accurate descriptions, and parameter help text that is safe for AI agents to follow. + +Initial semantic command groups: + +- `apisec asset api`: API assets, details, schema, data tags, status, remarks, import where feasible. +- `apisec asset site`: site listing, site details, move-to-app, enable or disable related operations. +- `apisec asset app`: application management, simple application lookup, priority, relocation. +- `apisec asset visitor`: visitor/source-IP query, groups, relationship charts, request and response summaries. +- `apisec asset config`: asset discovery config, success sign, identity, ignored API, resource recognition, effective scope. +- `apisec data rule`: data discovery rule configuration and built-in rule updates. +- `apisec data sensitive-transfer`: sensitive data transfer and conditional export workflows after the exact source endpoint is confirmed. +- `apisec risk config`: risk discovery config, risk model config, weak-password or weak-cipher config, whitelist. +- `apisec risk event`: risk event and event group query/update workflows. +- `apisec risk vulnerability`: vulnerability query and status update workflows. + +## Version Layout + +Follow the directory-as-plugin layout from the maintenance design: + +```text +products/apisec/ + v26.05/ + openapi.json + cli-mapping.yaml + latest -> v26.05 +``` + +The first iteration may embed the static `v26.05` schema and mapping. The loader should keep the boundary clear so a later APISec product-side schema endpoint can replace the embedded schema without changing command behavior. + +## Help And AI Robustness + +Help text is part of the product contract. Generated help must avoid ambiguous wording and should include: + +- The exact APISec endpoint or operation ID for raw commands. +- Whether parameters are query parameters or JSON body fields. +- Required fields, default values, enum values, and array/object shape when known. +- A clear fallback rule: use `--body` or `--body-file` when a command has complex or unknown JSON input. +- Examples for priority semantic commands. + +For raw commands, prefer mechanical and explicit help over friendly prose. For semantic commands, prefer business terms but retain operation ID references so an AI agent can map the command back to the API contract. + +## Implementation Constraints + +Keep product behavior in `products/apisec/`. Root command changes should only import and register the new product and apply runtime config. Shared OpenAPI/mapping code may be introduced only if it reduces duplication with existing generated products without broad rewrites. + +No changes to the APISec source repository are required for the first iteration. Product-side schema and version endpoints are a future improvement. + +## Known Risks + +- Some APISec endpoints do not declare serializers, so the generated OpenAPI may be incomplete. +- Some GET endpoints manually inspect query keys, which may require mapping overrides or body/query fallback support. +- Export/download endpoints need special handling for file output. +- Semantic names may need product review to match APISec UI terminology. + +## Acceptance Criteria + +- `chaitin-cli apisec --help` clearly explains config, authentication, raw commands, semantic commands, and fallback input options. +- All generated APISec operations are reachable through `apisec raw`. +- The priority modules listed above have semantic command groups where the source schema is sufficient. +- Commands send `API-TOKEN` and honor `apisec.url` / `apisec.api_token` from config and equivalent flags. +- `task fmt`, `task test`, and relevant CLI help smoke tests pass. From 235b2d366fd409c5597e1a78f102aa8a2180f4ce Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:07:12 +0800 Subject: [PATCH 02/12] feat: add apisec schema types --- products/apisec/schema_loader.go | 24 +++++ products/apisec/schema_loader_test.go | 27 ++++++ products/apisec/testdata/cli-mapping.yaml | 12 +++ products/apisec/testdata/openapi_minimal.json | 46 ++++++++++ products/apisec/types.go | 91 +++++++++++++++++++ 5 files changed, 200 insertions(+) create mode 100644 products/apisec/schema_loader.go create mode 100644 products/apisec/schema_loader_test.go create mode 100644 products/apisec/testdata/cli-mapping.yaml create mode 100644 products/apisec/testdata/openapi_minimal.json create mode 100644 products/apisec/types.go diff --git a/products/apisec/schema_loader.go b/products/apisec/schema_loader.go new file mode 100644 index 0000000..3d9be57 --- /dev/null +++ b/products/apisec/schema_loader.go @@ -0,0 +1,24 @@ +package apisec + +import ( + "encoding/json" + "fmt" + + "gopkg.in/yaml.v3" +) + +func parseOpenAPI(data []byte) (*OpenAPI, error) { + var api OpenAPI + if err := json.Unmarshal(data, &api); err != nil { + return nil, fmt.Errorf("parse OpenAPI: %w", err) + } + return &api, nil +} + +func parseCLIMapping(data []byte) (*CLIMapping, error) { + var mapping CLIMapping + if err := yaml.Unmarshal(data, &mapping); err != nil { + return nil, fmt.Errorf("parse CLI mapping: %w", err) + } + return &mapping, nil +} diff --git a/products/apisec/schema_loader_test.go b/products/apisec/schema_loader_test.go new file mode 100644 index 0000000..d575ed9 --- /dev/null +++ b/products/apisec/schema_loader_test.go @@ -0,0 +1,27 @@ +package apisec + +import "testing" + +func TestParseOpenAPI(t *testing.T) { + api, err := parseOpenAPI([]byte(`{"openapi":"3.0.3","info":{"title":"APISec","version":"26.05"},"paths":{"/api/ApplicationAPI":{"get":{"operationId":"ApplicationAPI_get","summary":"List applications"}}}}`)) + if err != nil { + t.Fatalf("parseOpenAPI() error = %v", err) + } + if api.Paths["/api/ApplicationAPI"].Get.OperationID != "ApplicationAPI_get" { + t.Fatalf("operationId not parsed: %+v", api.Paths["/api/ApplicationAPI"].Get) + } +} + +func TestParseCLIMapping(t *testing.T) { + mapping, err := parseCLIMapping([]byte(`commands: + - path: [asset, app, list] + operationId: ApplicationAPI_get + short: List applications +`)) + if err != nil { + t.Fatalf("parseCLIMapping() error = %v", err) + } + if got := mapping.Commands[0].Path[2]; got != "list" { + t.Fatalf("path[2] = %q, want list", got) + } +} diff --git a/products/apisec/testdata/cli-mapping.yaml b/products/apisec/testdata/cli-mapping.yaml new file mode 100644 index 0000000..08afeb3 --- /dev/null +++ b/products/apisec/testdata/cli-mapping.yaml @@ -0,0 +1,12 @@ +commands: + - path: [asset, app, list] + operationId: ApplicationAPI_get + short: List applications + rawHidden: false + - path: [asset, app, create] + operationId: ApplicationAPI_post + short: Create application + flags: + name: + name: name + description: Application name. diff --git a/products/apisec/testdata/openapi_minimal.json b/products/apisec/testdata/openapi_minimal.json new file mode 100644 index 0000000..902fb12 --- /dev/null +++ b/products/apisec/testdata/openapi_minimal.json @@ -0,0 +1,46 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "APISec", + "version": "26.05" + }, + "paths": { + "/api/ApplicationAPI": { + "get": { + "operationId": "ApplicationAPI_get", + "summary": "List applications", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number.", + "schema": { + "type": "integer" + } + } + ] + }, + "post": { + "operationId": "ApplicationAPI_post", + "summary": "Create application", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "Application name." + } + } + } + } + } + } + } + } + } +} diff --git a/products/apisec/types.go b/products/apisec/types.go new file mode 100644 index 0000000..b6092b8 --- /dev/null +++ b/products/apisec/types.go @@ -0,0 +1,91 @@ +package apisec + +import "encoding/json" + +type Config struct { + URL string `yaml:"url"` + APIToken string `yaml:"api_token"` +} + +type OpenAPI struct { + OpenAPI string `json:"openapi"` + Info Info `json:"info"` + Tags []Tag `json:"tags,omitempty"` + Paths map[string]PathItem `json:"paths"` +} + +type Info struct { + Title string `json:"title"` + Description string `json:"description,omitempty"` + Version string `json:"version"` +} + +type Tag struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +type PathItem struct { + Get *Operation `json:"get,omitempty"` + Post *Operation `json:"post,omitempty"` + Put *Operation `json:"put,omitempty"` + Delete *Operation `json:"delete,omitempty"` + Patch *Operation `json:"patch,omitempty"` +} + +type Operation struct { + OperationID string `json:"operationId"` + Summary string `json:"summary,omitempty"` + Description string `json:"description,omitempty"` + Tags []string `json:"tags,omitempty"` + Parameters []Parameter `json:"parameters,omitempty"` + RequestBody *RequestBody `json:"requestBody,omitempty"` +} + +type Parameter struct { + Name string `json:"name"` + In string `json:"in"` + Description string `json:"description,omitempty"` + Required bool `json:"required,omitempty"` + Schema *Schema `json:"schema,omitempty"` +} + +type Schema struct { + Type string `json:"type,omitempty"` + Ref string `json:"$ref,omitempty"` + Description string `json:"description,omitempty"` + Enum []json.RawMessage `json:"enum,omitempty"` + Items *Schema `json:"items,omitempty"` + Properties map[string]*Schema `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` +} + +type RequestBody struct { + Required bool `json:"required,omitempty"` + Content map[string]MediaType `json:"content,omitempty"` +} + +type MediaType struct { + Schema *Schema `json:"schema,omitempty"` + Example any `json:"example,omitempty"` +} + +type CLIMapping struct { + Commands []MappedCommand `yaml:"commands"` +} + +type MappedCommand struct { + Path []string `yaml:"path"` + OperationID string `yaml:"operationId"` + Short string `yaml:"short,omitempty"` + Long string `yaml:"long,omitempty"` + Examples []string `yaml:"examples,omitempty"` + RawHidden bool `yaml:"rawHidden,omitempty"` + Flags map[string]MappedFlag `yaml:"flags,omitempty"` + Metadata map[string]interface{} `yaml:"metadata,omitempty"` +} + +type MappedFlag struct { + Name string `yaml:"name,omitempty"` + Description string `yaml:"description,omitempty"` +} From f905324ef5e3a08482235616942c652afe8dd4ed Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:09:01 +0800 Subject: [PATCH 03/12] feat: add apisec client --- products/apisec/client.go | 174 +++++++++++++++++++++++++++++++++ products/apisec/client_test.go | 73 ++++++++++++++ products/apisec/render.go | 38 +++++++ products/apisec/render_test.go | 36 +++++++ 4 files changed, 321 insertions(+) create mode 100644 products/apisec/client.go create mode 100644 products/apisec/client_test.go create mode 100644 products/apisec/render.go create mode 100644 products/apisec/render_test.go diff --git a/products/apisec/client.go b/products/apisec/client.go new file mode 100644 index 0000000..b683488 --- /dev/null +++ b/products/apisec/client.go @@ -0,0 +1,174 @@ +package apisec + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" +) + +var dryRun bool + +type Client struct { + config *Config + httpClient *http.Client + baseURL string + verbose bool +} + +type responseEnvelope struct { + Err any `json:"err"` + Data json.RawMessage `json:"data"` + Msg any `json:"msg"` +} + +func NewClient(cfg *Config, verbose bool) *Client { + return &Client{ + config: cfg, + httpClient: &http.Client{ + Timeout: 30 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + }, + baseURL: strings.TrimSuffix(cfg.URL, "/"), + verbose: verbose, + } +} + +func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body any, result any) error { + reqURL := c.buildURL(path) + if len(query) > 0 { + reqURL += "?" + query.Encode() + } + + var reqBody io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("marshal request body: %w", err) + } + reqBody = bytes.NewReader(data) + } + + req, err := http.NewRequestWithContext(ctx, method, reqURL, reqBody) + if err != nil { + return fmt.Errorf("create request: %w", err) + } + c.injectHeaders(req, body != nil) + + if c.verbose || dryRun { + logRequest(req, body) + } + if dryRun { + return nil + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + return c.handleResponse(resp, result) +} + +func (c *Client) buildURL(path string) string { + if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { + return path + } + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + return c.baseURL + path +} + +func (c *Client) injectHeaders(req *http.Request, hasBody bool) { + if c.config.APIToken != "" { + req.Header.Set("API-TOKEN", c.config.APIToken) + } + if hasBody { + req.Header.Set("Content-Type", "application/json") + } + req.Header.Set("Accept", "application/json") +} + +func (c *Client) handleResponse(resp *http.Response, result any) error { + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read response body: %w", err) + } + if resp.StatusCode >= 400 { + return fmt.Errorf("API request failed (status %d): %s", resp.StatusCode, strings.TrimSpace(string(body))) + } + if len(body) == 0 || result == nil { + return nil + } + + var envelope responseEnvelope + if err := json.Unmarshal(body, &envelope); err == nil && (envelope.Data != nil || envelope.Err != nil || envelope.Msg != nil) { + if hasAPIError(envelope.Err) { + return fmt.Errorf("APISec error %s: %s", valueString(envelope.Err), valueString(envelope.Msg)) + } + if envelope.Data == nil { + return nil + } + return json.Unmarshal(envelope.Data, result) + } + + if err := json.Unmarshal(body, result); err != nil { + return fmt.Errorf("parse response: %w", err) + } + return nil +} + +func hasAPIError(value any) bool { + switch v := value.(type) { + case nil: + return false + case string: + return v != "" + default: + return true + } +} + +func valueString(value any) string { + switch v := value.(type) { + case nil: + return "" + case string: + return v + default: + data, err := json.Marshal(v) + if err != nil { + return fmt.Sprint(v) + } + return string(data) + } +} + +func logRequest(req *http.Request, body any) { + fmt.Fprintf(os.Stderr, "URL: %s %s\n", req.Method, req.URL.String()) + if len(req.Header) > 0 { + data, err := json.MarshalIndent(req.Header, "", " ") + if err == nil { + fmt.Fprintf(os.Stderr, "Headers:\n%s\n", string(data)) + } + } + if body != nil { + data, err := json.MarshalIndent(body, "", " ") + if err == nil { + fmt.Fprintf(os.Stderr, "Body:\n%s\n", string(data)) + return + } + fmt.Fprintf(os.Stderr, "Body: %v\n", body) + } +} diff --git a/products/apisec/client_test.go b/products/apisec/client_test.go new file mode 100644 index 0000000..f648ac4 --- /dev/null +++ b/products/apisec/client_test.go @@ -0,0 +1,73 @@ +package apisec + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +func TestClientSetsAPITokenHeader(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get("API-TOKEN"); got != "token-1" { + t.Fatalf("API-TOKEN = %q, want token-1", got) + } + if got := r.URL.Query().Get("page"); got != "2" { + t.Fatalf("page query = %q, want 2", got) + } + _, _ = w.Write([]byte(`{"err":null,"data":{"ok":true},"msg":null}`)) + })) + defer server.Close() + + client := NewClient(&Config{URL: server.URL, APIToken: "token-1"}, false) + var result any + err := client.Do(context.Background(), http.MethodGet, "/api/ApplicationAPI", url.Values{"page": []string{"2"}}, nil, &result) + if err != nil { + t.Fatalf("Do() error = %v", err) + } + + got := result.(map[string]any) + if got["ok"] != true { + t.Fatalf("result = %#v, want data payload", result) + } +} + +func TestClientSendsJSONBody(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get("Content-Type"); !strings.HasPrefix(got, "application/json") { + t.Fatalf("Content-Type = %q, want application/json", got) + } + var body map[string]string + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("Decode body error = %v", err) + } + if body["name"] != "app" { + t.Fatalf("body name = %q, want app", body["name"]) + } + _, _ = w.Write([]byte(`{"err":null,"data":{"id":1},"msg":null}`)) + })) + defer server.Close() + + client := NewClient(&Config{URL: server.URL, APIToken: "token-1"}, false) + var result any + if err := client.Do(context.Background(), http.MethodPost, "/api/ApplicationAPI", nil, map[string]string{"name": "app"}, &result); err != nil { + t.Fatalf("Do() error = %v", err) + } +} + +func TestClientReturnsAPIError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"err":"bad-request","msg":"invalid"}`)) + })) + defer server.Close() + + client := NewClient(&Config{URL: server.URL, APIToken: "token-1"}, false) + var result any + err := client.Do(context.Background(), http.MethodGet, "/api/ApplicationAPI", nil, nil, &result) + if err == nil || !strings.Contains(err.Error(), "bad-request") { + t.Fatalf("Do() error = %v, want bad-request", err) + } +} diff --git a/products/apisec/render.go b/products/apisec/render.go new file mode 100644 index 0000000..3ed2567 --- /dev/null +++ b/products/apisec/render.go @@ -0,0 +1,38 @@ +package apisec + +import ( + "encoding/json" + "fmt" + "io" +) + +type Format string + +const ( + FormatTable Format = "table" + FormatJSON Format = "json" +) + +type Renderer struct { + format Format + out io.Writer +} + +func NewRenderer(format Format, out io.Writer) Renderer { + return Renderer{format: format, out: out} +} + +func (r Renderer) Render(value any) error { + var data []byte + var err error + if r.format == FormatJSON { + data, err = json.Marshal(value) + } else { + data, err = json.MarshalIndent(value, "", " ") + } + if err != nil { + return fmt.Errorf("render response: %w", err) + } + _, err = fmt.Fprintln(r.out, string(data)) + return err +} diff --git a/products/apisec/render_test.go b/products/apisec/render_test.go new file mode 100644 index 0000000..868964e --- /dev/null +++ b/products/apisec/render_test.go @@ -0,0 +1,36 @@ +package apisec + +import ( + "bytes" + "encoding/json" + "testing" +) + +func TestRendererWritesJSON(t *testing.T) { + var out bytes.Buffer + renderer := NewRenderer(FormatJSON, &out) + + if err := renderer.Render(map[string]any{"ok": true}); err != nil { + t.Fatalf("Render() error = %v", err) + } + + var got map[string]any + if err := json.Unmarshal(out.Bytes(), &got); err != nil { + t.Fatalf("output is not valid JSON: %v", err) + } + if got["ok"] != true { + t.Fatalf("ok = %#v, want true", got["ok"]) + } +} + +func TestRendererWritesTableAsStableJSON(t *testing.T) { + var out bytes.Buffer + renderer := NewRenderer(FormatTable, &out) + + if err := renderer.Render(map[string]any{"ok": true}); err != nil { + t.Fatalf("Render() error = %v", err) + } + if got := out.String(); got != "{\n \"ok\": true\n}\n" { + t.Fatalf("output = %q, want indented JSON", got) + } +} From e1c66b99998d5751195750635652592025de73ae Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:11:52 +0800 Subject: [PATCH 04/12] feat: generate apisec raw commands --- products/apisec/parser.go | 354 +++++++++++++++++++++++++++++++++ products/apisec/parser_test.go | 70 +++++++ 2 files changed, 424 insertions(+) create mode 100644 products/apisec/parser.go create mode 100644 products/apisec/parser_test.go diff --git a/products/apisec/parser.go b/products/apisec/parser.go new file mode 100644 index 0000000..4afe511 --- /dev/null +++ b/products/apisec/parser.go @@ -0,0 +1,354 @@ +package apisec + +import ( + "encoding/json" + "fmt" + "net/url" + "os" + "regexp" + "sort" + "strings" + "unicode" + + "github.com/spf13/cobra" +) + +type Parser struct { + api *OpenAPI + mapping *CLIMapping +} + +func NewParser(api *OpenAPI, mapping *CLIMapping) *Parser { + return &Parser{api: api, mapping: mapping} +} + +func (p *Parser) GenerateRawCommands() ([]*cobra.Command, error) { + if p.api == nil { + return nil, nil + } + parents := make(map[string]*cobra.Command) + paths := sortedPathKeys(p.api.Paths) + used := make(map[string]int) + + for _, path := range paths { + pathItem := p.api.Paths[path] + for _, item := range operationsForPath(pathItem) { + if item.op == nil { + continue + } + parentName := rawParentName(path) + parent := parents[parentName] + if parent == nil { + parent = &cobra.Command{Use: parentName, Short: fmt.Sprintf("Raw commands for %s", parentName)} + parents[parentName] = parent + } + + childName := strings.ToLower(item.method) + key := parentName + " " + childName + used[key]++ + if used[key] > 1 { + childName = childName + "-" + normalizeSegment(item.op.OperationID) + } + parent.AddCommand(p.createOperationCommand(childName, item.method, path, item.op, nil)) + } + } + + commands := make([]*cobra.Command, 0, len(parents)) + for _, name := range sortedCommandKeys(parents) { + commands = append(commands, parents[name]) + } + return commands, nil +} + +type pathOperation struct { + method string + op *Operation +} + +func operationsForPath(pathItem PathItem) []pathOperation { + return []pathOperation{ + {method: "GET", op: pathItem.Get}, + {method: "POST", op: pathItem.Post}, + {method: "PUT", op: pathItem.Put}, + {method: "DELETE", op: pathItem.Delete}, + {method: "PATCH", op: pathItem.Patch}, + } +} + +func sortedPathKeys(paths map[string]PathItem) []string { + keys := make([]string, 0, len(paths)) + for key := range paths { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func sortedCommandKeys(commands map[string]*cobra.Command) []string { + keys := make([]string, 0, len(commands)) + for key := range commands { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func rawParentName(path string) string { + segments := strings.Split(strings.Trim(path, "/"), "/") + if len(segments) == 0 { + return "root" + } + last := segments[len(segments)-1] + if last == "" { + return "root" + } + return normalizeSegment(last) +} + +func (p *Parser) createOperationCommand(use, method, path string, op *Operation, mapped *MappedCommand) *cobra.Command { + short := strings.TrimSpace(op.Summary) + if mapped != nil && strings.TrimSpace(mapped.Short) != "" { + short = strings.TrimSpace(mapped.Short) + } + if short == "" { + short = fmt.Sprintf("%s %s", method, path) + } + + cmd := &cobra.Command{ + Use: use, + Short: short, + Long: buildOperationHelp(method, path, op, mapped), + RunE: func(cmd *cobra.Command, args []string) error { + return p.executeCommand(cmd, method, path, op) + }, + } + + for _, param := range op.Parameters { + if shouldSkipManagedParam(param) { + continue + } + addParameterFlag(cmd, param) + } + if op.RequestBody != nil { + cmd.Flags().String("body", "", "Request body as JSON string. Use this for complex or generated JSON input.") + cmd.Flags().String("body-file", "", "Path to a JSON file used as request body. Takes precedence over --body.") + } + + return cmd +} + +func buildOperationHelp(method, path string, op *Operation, mapped *MappedCommand) string { + var b strings.Builder + if mapped != nil && strings.TrimSpace(mapped.Long) != "" { + b.WriteString(strings.TrimSpace(mapped.Long)) + b.WriteString("\n\n") + } else if strings.TrimSpace(op.Description) != "" { + b.WriteString(strings.TrimSpace(op.Description)) + b.WriteString("\n\n") + } + fmt.Fprintf(&b, "Endpoint: %s %s\n", method, path) + if op.OperationID != "" { + fmt.Fprintf(&b, "Operation ID: %s\n", op.OperationID) + } + if len(op.Parameters) > 0 { + b.WriteString("\nParameters:\n") + for _, param := range op.Parameters { + if shouldSkipManagedParam(param) { + continue + } + required := "optional" + if param.Required { + required = "required" + } + desc := strings.TrimSpace(param.Description) + if desc == "" { + desc = param.Name + } + fmt.Fprintf(&b, "- --%s (%s, %s): %s\n", flagNameForParam(param), param.In, required, desc) + } + } + if op.RequestBody != nil { + b.WriteString("\nBody:\n") + b.WriteString("- Use --body for inline JSON or --body-file for a JSON file. --body-file takes precedence.\n") + if op.RequestBody.Required { + b.WriteString("- Request body is required by the API schema.\n") + } + } + if mapped != nil && len(mapped.Examples) > 0 { + b.WriteString("\nExamples:\n") + for _, example := range mapped.Examples { + fmt.Fprintf(&b, " %s\n", example) + } + } + return strings.TrimRight(b.String(), "\n") +} + +func addParameterFlag(cmd *cobra.Command, param Parameter) { + name := flagNameForParam(param) + if cmd.Flags().Lookup(name) != nil { + return + } + desc := strings.TrimSpace(param.Description) + if desc == "" { + desc = fmt.Sprintf("%s %s parameter", param.In, param.Name) + } + switch schemaType(param.Schema) { + case "integer": + cmd.Flags().Int(name, 0, desc) + case "boolean": + cmd.Flags().Bool(name, false, desc) + default: + cmd.Flags().String(name, "", desc) + } + if param.Required { + _ = cmd.MarkFlagRequired(name) + } +} + +func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Operation) error { + query := make(url.Values) + body, err := collectRequestBody(cmd, op.RequestBody) + if err != nil { + return err + } + apiPath := path + + for _, param := range op.Parameters { + if shouldSkipManagedParam(param) { + continue + } + value, present, err := readParameterFlag(cmd, param) + if err != nil { + return err + } + if !present { + continue + } + switch strings.ToLower(param.In) { + case "path": + apiPath = strings.ReplaceAll(apiPath, "{"+param.Name+"}", value) + case "query": + query.Set(param.Name, value) + } + } + + client := getClient(cmd) + var result any + if err := client.Do(cmd.Context(), method, apiPath, query, body, &result); err != nil { + return err + } + return getRenderer(cmd).Render(result) +} + +func readParameterFlag(cmd *cobra.Command, param Parameter) (string, bool, error) { + flag := cmd.Flags().Lookup(flagNameForParam(param)) + if flag == nil || !flag.Changed { + return "", false, nil + } + switch schemaType(param.Schema) { + case "integer": + value, err := cmd.Flags().GetInt(flag.Name) + if err != nil { + return "", false, err + } + return fmt.Sprintf("%d", value), true, nil + case "boolean": + value, err := cmd.Flags().GetBool(flag.Name) + if err != nil { + return "", false, err + } + return fmt.Sprintf("%t", value), true, nil + default: + value, err := cmd.Flags().GetString(flag.Name) + return value, true, err + } +} + +func collectRequestBody(cmd *cobra.Command, requestBody *RequestBody) (any, error) { + if requestBody == nil { + return nil, nil + } + bodyFile, err := cmd.Flags().GetString("body-file") + if err != nil { + return nil, err + } + if bodyFile != "" { + data, err := os.ReadFile(bodyFile) + if err != nil { + return nil, fmt.Errorf("read body file %s: %w", bodyFile, err) + } + return parseJSONBody(data) + } + bodyText, err := cmd.Flags().GetString("body") + if err != nil { + return nil, err + } + if bodyText == "" { + return nil, nil + } + return parseJSONBody([]byte(bodyText)) +} + +func parseJSONBody(data []byte) (any, error) { + var body any + if err := json.Unmarshal(data, &body); err != nil { + return nil, fmt.Errorf("parse request body JSON: %w", err) + } + return body, nil +} + +func getClient(cmd *cobra.Command) *Client { + return NewClient(&Config{}, false) +} + +func getRenderer(cmd *cobra.Command) Renderer { + return NewRenderer(FormatJSON, cmd.OutOrStdout()) +} + +func shouldSkipManagedParam(param Parameter) bool { + if strings.ToLower(param.In) != "header" { + return false + } + switch strings.ToLower(param.Name) { + case "api-token", "content-type", "accept": + return true + default: + return false + } +} + +func flagNameForParam(param Parameter) string { + return normalizeSegment(param.Name) +} + +func schemaType(schema *Schema) string { + if schema == nil || schema.Type == "" { + return "string" + } + return schema.Type +} + +var nonAlphaNumeric = regexp.MustCompile(`[^a-z0-9-]+`) + +func normalizeSegment(value string) string { + value = strings.TrimSpace(value) + value = strings.Trim(value, "{}") + var b strings.Builder + var prev rune + for i, r := range value { + if i > 0 && unicode.IsUpper(r) && (unicode.IsLower(prev) || unicode.IsDigit(prev) || (prev == 'I' && r == 'A')) { + b.WriteRune('-') + } + switch r { + case '_', '.', '/', ' ': + b.WriteRune('-') + default: + b.WriteRune(unicode.ToLower(r)) + } + prev = r + } + result := nonAlphaNumeric.ReplaceAllString(b.String(), "-") + result = strings.Trim(result, "-") + result = strings.ReplaceAll(result, "a-p-i", "api") + return result +} diff --git a/products/apisec/parser_test.go b/products/apisec/parser_test.go new file mode 100644 index 0000000..eb26b8d --- /dev/null +++ b/products/apisec/parser_test.go @@ -0,0 +1,70 @@ +package apisec + +import ( + "os" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func TestGenerateRawCommands(t *testing.T) { + api := loadMinimalOpenAPI(t) + parser := NewParser(api, nil) + + commands, err := parser.GenerateRawCommands() + if err != nil { + t.Fatalf("GenerateRawCommands() error = %v", err) + } + + application := findCommand(commands, "application-api") + if application == nil { + t.Fatalf("application-api command not generated") + } + getCmd := findCommand(application.Commands(), "get") + if getCmd == nil { + t.Fatalf("application-api get command not generated") + } + if getCmd.Flags().Lookup("page") == nil { + t.Fatalf("page flag not generated") + } + if !strings.Contains(getCmd.Long, "Endpoint: GET /api/ApplicationAPI") { + t.Fatalf("help missing endpoint: %s", getCmd.Long) + } + if !strings.Contains(getCmd.Long, "Operation ID: ApplicationAPI_get") { + t.Fatalf("help missing operation ID: %s", getCmd.Long) + } + + postCmd := findCommand(application.Commands(), "post") + if postCmd == nil { + t.Fatalf("application-api post command not generated") + } + if postCmd.Flags().Lookup("body") == nil { + t.Fatalf("body flag not generated") + } + if postCmd.Flags().Lookup("body-file") == nil { + t.Fatalf("body-file flag not generated") + } +} + +func loadMinimalOpenAPI(t *testing.T) *OpenAPI { + t.Helper() + data, err := os.ReadFile("testdata/openapi_minimal.json") + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + api, err := parseOpenAPI(data) + if err != nil { + t.Fatalf("parseOpenAPI() error = %v", err) + } + return api +} + +func findCommand(commands []*cobra.Command, use string) *cobra.Command { + for _, cmd := range commands { + if cmd.Use == use { + return cmd + } + } + return nil +} From 00b1996cf0183cdb792fd9509aaf34ac312de39a Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:13:16 +0800 Subject: [PATCH 05/12] feat: map apisec semantic commands --- products/apisec/parser.go | 96 +++++++++++++++++++++++++++++++++- products/apisec/parser_test.go | 47 +++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/products/apisec/parser.go b/products/apisec/parser.go index 4afe511..9b7bb21 100644 --- a/products/apisec/parser.go +++ b/products/apisec/parser.go @@ -18,6 +18,12 @@ type Parser struct { mapping *CLIMapping } +type operationRef struct { + method string + path string + op *Operation +} + func NewParser(api *OpenAPI, mapping *CLIMapping) *Parser { return &Parser{api: api, mapping: mapping} } @@ -60,6 +66,73 @@ func (p *Parser) GenerateRawCommands() ([]*cobra.Command, error) { return commands, nil } +func (p *Parser) GenerateSemanticCommands() ([]*cobra.Command, error) { + if p.api == nil || p.mapping == nil { + return nil, nil + } + operations := p.operationsByID() + parents := make(map[string]*cobra.Command) + + for _, mapped := range p.mapping.Commands { + if len(mapped.Path) == 0 { + continue + } + ref, ok := operations[mapped.OperationID] + if !ok { + continue + } + parent := getOrCreateMappedParent(parents, mapped.Path[0]) + current := parent + for _, segment := range mapped.Path[1 : len(mapped.Path)-1] { + current = getOrCreateChild(current, segment) + } + leafName := mapped.Path[len(mapped.Path)-1] + current.AddCommand(p.createOperationCommand(leafName, ref.method, ref.path, ref.op, &mapped)) + } + + commands := make([]*cobra.Command, 0, len(parents)) + for _, name := range sortedCommandKeys(parents) { + commands = append(commands, parents[name]) + } + return commands, nil +} + +func (p *Parser) operationsByID() map[string]operationRef { + operations := make(map[string]operationRef) + for _, path := range sortedPathKeys(p.api.Paths) { + pathItem := p.api.Paths[path] + for _, item := range operationsForPath(pathItem) { + if item.op == nil || item.op.OperationID == "" { + continue + } + operations[item.op.OperationID] = operationRef{method: item.method, path: path, op: item.op} + } + } + return operations +} + +func getOrCreateMappedParent(parents map[string]*cobra.Command, name string) *cobra.Command { + name = normalizeSegment(name) + if parents[name] != nil { + return parents[name] + } + cmd := &cobra.Command{Use: name, Short: fmt.Sprintf("%s commands", name)} + parents[name] = cmd + return cmd +} + +func getOrCreateChild(parent *cobra.Command, name string) *cobra.Command { + name = normalizeSegment(name) + for _, cmd := range parent.Commands() { + if cmd.Use == name { + return cmd + } + } + cmd := &cobra.Command{Use: name, Short: fmt.Sprintf("%s commands", name)} + parent.AddCommand(cmd) + return cmd +} + type pathOperation struct { method string op *Operation @@ -127,7 +200,7 @@ func (p *Parser) createOperationCommand(use, method, path string, op *Operation, if shouldSkipManagedParam(param) { continue } - addParameterFlag(cmd, param) + addParameterFlag(cmd, param, mappedFlagForParam(mapped, param)) } if op.RequestBody != nil { cmd.Flags().String("body", "", "Request body as JSON string. Use this for complex or generated JSON input.") @@ -183,12 +256,18 @@ func buildOperationHelp(method, path string, op *Operation, mapped *MappedComman return strings.TrimRight(b.String(), "\n") } -func addParameterFlag(cmd *cobra.Command, param Parameter) { +func addParameterFlag(cmd *cobra.Command, param Parameter, mapped *MappedFlag) { name := flagNameForParam(param) + if mapped != nil && mapped.Name != "" { + name = normalizeSegment(mapped.Name) + } if cmd.Flags().Lookup(name) != nil { return } desc := strings.TrimSpace(param.Description) + if mapped != nil && strings.TrimSpace(mapped.Description) != "" { + desc = strings.TrimSpace(mapped.Description) + } if desc == "" { desc = fmt.Sprintf("%s %s parameter", param.In, param.Name) } @@ -205,6 +284,19 @@ func addParameterFlag(cmd *cobra.Command, param Parameter) { } } +func mappedFlagForParam(mapped *MappedCommand, param Parameter) *MappedFlag { + if mapped == nil || len(mapped.Flags) == 0 { + return nil + } + if flag, ok := mapped.Flags[param.Name]; ok { + return &flag + } + if flag, ok := mapped.Flags[flagNameForParam(param)]; ok { + return &flag + } + return nil +} + func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Operation) error { query := make(url.Values) body, err := collectRequestBody(cmd, op.RequestBody) diff --git a/products/apisec/parser_test.go b/products/apisec/parser_test.go index eb26b8d..4cc7277 100644 --- a/products/apisec/parser_test.go +++ b/products/apisec/parser_test.go @@ -47,6 +47,53 @@ func TestGenerateRawCommands(t *testing.T) { } } +func TestGenerateSemanticCommands(t *testing.T) { + api := loadMinimalOpenAPI(t) + mapping := &CLIMapping{Commands: []MappedCommand{ + { + Path: []string{"asset", "app", "list"}, + OperationID: "ApplicationAPI_get", + Short: "List APISec applications", + Long: "List applications with pagination.", + Examples: []string{"chaitin-cli apisec asset app list --page 1"}, + Flags: map[string]MappedFlag{ + "page": {Name: "page-number", Description: "Page number to fetch."}, + }, + }, + }} + parser := NewParser(api, mapping) + + commands, err := parser.GenerateSemanticCommands() + if err != nil { + t.Fatalf("GenerateSemanticCommands() error = %v", err) + } + asset := findCommand(commands, "asset") + if asset == nil { + t.Fatalf("asset command not generated") + } + app := findCommand(asset.Commands(), "app") + if app == nil { + t.Fatalf("asset app command not generated") + } + list := findCommand(app.Commands(), "list") + if list == nil { + t.Fatalf("asset app list command not generated") + } + if list.Short != "List APISec applications" { + t.Fatalf("Short = %q, want mapped short", list.Short) + } + if !strings.Contains(list.Long, "Operation ID: ApplicationAPI_get") { + t.Fatalf("Long missing operation ID: %s", list.Long) + } + flag := list.Flags().Lookup("page-number") + if flag == nil { + t.Fatalf("mapped page-number flag not generated") + } + if flag.Usage != "Page number to fetch." { + t.Fatalf("flag usage = %q, want mapped description", flag.Usage) + } +} + func loadMinimalOpenAPI(t *testing.T) *OpenAPI { t.Helper() data, err := os.ReadFile("testdata/openapi_minimal.json") From adb42d35bdb51976dd394047edf415c7101d42e6 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:15:45 +0800 Subject: [PATCH 06/12] feat: register apisec product command --- main.go | 4 ++ products/apisec/command.go | 90 +++++++++++++++++++++++++++++++++ products/apisec/command_test.go | 55 ++++++++++++++++++++ products/apisec/parser.go | 10 +++- 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 products/apisec/command.go create mode 100644 products/apisec/command_test.go diff --git a/main.go b/main.go index 9fa7236..f922da6 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/chaitin/chaitin-cli/config" + "github.com/chaitin/chaitin-cli/products/apisec" "github.com/chaitin/chaitin-cli/products/chaitin" "github.com/chaitin/chaitin-cli/products/cloudwalker" "github.com/chaitin/chaitin-cli/products/ddr" @@ -46,6 +47,7 @@ func newApp() (*app, error) { root.PersistentFlags().BoolVar(&a.dryRun, "dry-run", false, "Do not send requests for commands that support dry-run") a.registerProductCommand(chaitin.NewCommand()) + a.registerProductCommand(apisec.NewCommand()) a.registerProductCommand(safelinece.NewCommand()) a.registerProductCommand(cloudwalker.NewCommand()) a.registerProductCommand(ddr.NewCommand()) @@ -105,6 +107,8 @@ func (a *app) wrapProductCommand(cmd *cobra.Command) { } switch cmd.Name() { + case "apisec": + apisec.ApplyRuntimeConfig(command, a.config, a.dryRun) case "safeline-ce": safelinece.ApplyRuntimeConfig(command, a.config, a.dryRun) case "cloudwalker": diff --git a/products/apisec/command.go b/products/apisec/command.go new file mode 100644 index 0000000..de95e97 --- /dev/null +++ b/products/apisec/command.go @@ -0,0 +1,90 @@ +package apisec + +import ( + "fmt" + "os" + + "github.com/chaitin/chaitin-cli/config" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + runtimeCfg Config + verbose bool +) + +func NewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "apisec", + Short: "APISec management tool", + Long: `APISec CLI + +Authentication uses the APISec API-TOKEN HTTP header. + +Config example: + apisec: + url: https://your-apisec.example + api_token: your-api-token + +Command model: + chaitin-cli apisec raw --help + Exposes generated raw operations for all available APISec APIs. + + chaitin-cli apisec asset app --help + Exposes mapped semantic commands for priority workflows. + +For complex JSON input, use --body for inline JSON or --body-file for a JSON file. +Operation help includes endpoint and operation ID so AI agents can map commands back to the API contract.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + applyRuntimeConfig(cmd) + }, + } + + cmd.PersistentFlags().String("url", "", "APISec API URL") + cmd.PersistentFlags().String("api-token", "", "APISec API token sent as API-TOKEN header") + cmd.PersistentFlags().StringP("output", "o", "json", "Output format (table|json)") + cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print request URL, headers, and body") + + if err := loadDynamicCommands(cmd); err != nil { + fmt.Fprintf(os.Stderr, "Warning: %v\n", err) + } + + return cmd +} + +func ApplyRuntimeConfig(cmd *cobra.Command, cfg config.Raw, isDryRun bool) { + productCfg, err := config.DecodeProduct[Config](cfg, "apisec") + if err != nil { + return + } + runtimeCfg = productCfg + dryRun = isDryRun +} + +func applyRuntimeConfig(cmd *cobra.Command) { + if flag := lookupFlag(cmd, "url"); flag != nil && !flag.Changed && runtimeCfg.URL != "" { + _ = setFlag(cmd, "url", runtimeCfg.URL) + } + if flag := lookupFlag(cmd, "api-token"); flag != nil && !flag.Changed && runtimeCfg.APIToken != "" { + _ = setFlag(cmd, "api-token", runtimeCfg.APIToken) + } +} + +func lookupFlag(cmd *cobra.Command, name string) *pflag.Flag { + if flag := cmd.Flags().Lookup(name); flag != nil { + return flag + } + return cmd.PersistentFlags().Lookup(name) +} + +func setFlag(cmd *cobra.Command, name, value string) error { + if cmd.Flags().Lookup(name) != nil { + return cmd.Flags().Set(name, value) + } + return cmd.PersistentFlags().Set(name, value) +} + +func loadDynamicCommands(cmd *cobra.Command) error { + return nil +} diff --git a/products/apisec/command_test.go b/products/apisec/command_test.go new file mode 100644 index 0000000..3e9f813 --- /dev/null +++ b/products/apisec/command_test.go @@ -0,0 +1,55 @@ +package apisec + +import ( + "bytes" + "strings" + "testing" + + "github.com/chaitin/chaitin-cli/config" + "gopkg.in/yaml.v3" +) + +func TestNewCommand(t *testing.T) { + cmd := NewCommand() + for _, name := range []string{"url", "api-token", "output", "verbose"} { + if cmd.PersistentFlags().Lookup(name) == nil { + t.Fatalf("missing persistent flag --%s", name) + } + } + + var out bytes.Buffer + cmd.SetOut(&out) + cmd.SetArgs([]string{"--help"}) + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute help error = %v", err) + } + help := out.String() + for _, want := range []string{"API-TOKEN", "raw", "--body", "--body-file"} { + if !strings.Contains(help, want) { + t.Fatalf("help missing %q:\n%s", want, help) + } + } +} + +func TestApplyRuntimeConfig(t *testing.T) { + cmd := NewCommand() + cfg := config.Raw{} + var node yaml.Node + if err := node.Encode(Config{URL: "https://apisec.example", APIToken: "token-1"}); err != nil { + t.Fatalf("Encode() error = %v", err) + } + cfg["apisec"] = node + + ApplyRuntimeConfig(cmd, cfg, true) + cmd.PersistentPreRun(cmd, nil) + + if got, _ := cmd.PersistentFlags().GetString("url"); got != "https://apisec.example" { + t.Fatalf("url = %q, want config value", got) + } + if got, _ := cmd.PersistentFlags().GetString("api-token"); got != "token-1" { + t.Fatalf("api-token = %q, want config value", got) + } + if !dryRun { + t.Fatalf("dryRun = false, want true") + } +} diff --git a/products/apisec/parser.go b/products/apisec/parser.go index 9b7bb21..2ee42f1 100644 --- a/products/apisec/parser.go +++ b/products/apisec/parser.go @@ -390,11 +390,17 @@ func parseJSONBody(data []byte) (any, error) { } func getClient(cmd *cobra.Command) *Client { - return NewClient(&Config{}, false) + urlValue, _ := cmd.Flags().GetString("url") + apiToken, _ := cmd.Flags().GetString("api-token") + return NewClient(&Config{URL: urlValue, APIToken: apiToken}, verbose) } func getRenderer(cmd *cobra.Command) Renderer { - return NewRenderer(FormatJSON, cmd.OutOrStdout()) + format := FormatJSON + if output, _ := cmd.Flags().GetString("output"); output == string(FormatTable) { + format = FormatTable + } + return NewRenderer(format, cmd.OutOrStdout()) } func shouldSkipManagedParam(param Parameter) bool { From 3c4ceaa1080b7170bd5c6332b7104038e3803fd4 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:20:21 +0800 Subject: [PATCH 07/12] feat: generate apisec openapi schema --- products/apisec/command.go | 28 + products/apisec/latest | 1 + products/apisec/schema_loader.go | 37 + products/apisec/schema_loader_test.go | 13 + products/apisec/v26.05/cli-mapping.yaml | 1 + products/apisec/v26.05/openapi.json | 9849 +++++++++++++++++++++++ tools/apisec-openapi-gen/README.md | 13 + tools/apisec-openapi-gen/generate.py | 277 + 8 files changed, 10219 insertions(+) create mode 100644 products/apisec/latest create mode 100644 products/apisec/v26.05/cli-mapping.yaml create mode 100644 products/apisec/v26.05/openapi.json create mode 100644 tools/apisec-openapi-gen/README.md create mode 100644 tools/apisec-openapi-gen/generate.py diff --git a/products/apisec/command.go b/products/apisec/command.go index de95e97..a00cc4b 100644 --- a/products/apisec/command.go +++ b/products/apisec/command.go @@ -86,5 +86,33 @@ func setFlag(cmd *cobra.Command, name, value string) error { } func loadDynamicCommands(cmd *cobra.Command) error { + api, mapping, err := loadEmbeddedSchema() + if err != nil { + return err + } + parser := NewParser(api, mapping) + + rawCmd := &cobra.Command{ + Use: "raw", + Short: "Raw APISec API operations", + Long: "Raw APISec API operations generated from the embedded OpenAPI schema. Operation help includes endpoint, operation ID, parameters, and body fallback guidance.", + } + rawCommands, err := parser.GenerateRawCommands() + if err != nil { + return err + } + for _, raw := range rawCommands { + rawCmd.AddCommand(raw) + } + cmd.AddCommand(rawCmd) + + semanticCommands, err := parser.GenerateSemanticCommands() + if err != nil { + return err + } + for _, semantic := range semanticCommands { + cmd.AddCommand(semantic) + } + return nil } diff --git a/products/apisec/latest b/products/apisec/latest new file mode 100644 index 0000000..b9d0d63 --- /dev/null +++ b/products/apisec/latest @@ -0,0 +1 @@ +v26.05 diff --git a/products/apisec/schema_loader.go b/products/apisec/schema_loader.go index 3d9be57..d52bbfd 100644 --- a/products/apisec/schema_loader.go +++ b/products/apisec/schema_loader.go @@ -1,12 +1,18 @@ package apisec import ( + "embed" "encoding/json" "fmt" + "path/filepath" + "strings" "gopkg.in/yaml.v3" ) +//go:embed latest v26.05/openapi.json v26.05/cli-mapping.yaml +var schemaFS embed.FS + func parseOpenAPI(data []byte) (*OpenAPI, error) { var api OpenAPI if err := json.Unmarshal(data, &api); err != nil { @@ -22,3 +28,34 @@ func parseCLIMapping(data []byte) (*CLIMapping, error) { } return &mapping, nil } + +func loadEmbeddedSchema() (*OpenAPI, *CLIMapping, error) { + versionData, err := schemaFS.ReadFile("latest") + if err != nil { + return nil, nil, fmt.Errorf("read latest APISec schema version: %w", err) + } + version := strings.TrimSpace(string(versionData)) + if version == "" { + return nil, nil, fmt.Errorf("latest APISec schema version is empty") + } + + openAPIData, err := schemaFS.ReadFile(filepath.Join(version, "openapi.json")) + if err != nil { + return nil, nil, fmt.Errorf("read APISec OpenAPI for %s: %w", version, err) + } + api, err := parseOpenAPI(openAPIData) + if err != nil { + return nil, nil, err + } + + mappingData, err := schemaFS.ReadFile(filepath.Join(version, "cli-mapping.yaml")) + if err != nil { + return nil, nil, fmt.Errorf("read APISec CLI mapping for %s: %w", version, err) + } + mapping, err := parseCLIMapping(mappingData) + if err != nil { + return nil, nil, err + } + + return api, mapping, nil +} diff --git a/products/apisec/schema_loader_test.go b/products/apisec/schema_loader_test.go index d575ed9..576addf 100644 --- a/products/apisec/schema_loader_test.go +++ b/products/apisec/schema_loader_test.go @@ -25,3 +25,16 @@ func TestParseCLIMapping(t *testing.T) { t.Fatalf("path[2] = %q, want list", got) } } + +func TestLoadEmbeddedSchema(t *testing.T) { + api, mapping, err := loadEmbeddedSchema() + if err != nil { + t.Fatalf("loadEmbeddedSchema() error = %v", err) + } + if len(api.Paths) == 0 { + t.Fatalf("embedded OpenAPI has no paths") + } + if mapping == nil { + t.Fatalf("mapping is nil") + } +} diff --git a/products/apisec/v26.05/cli-mapping.yaml b/products/apisec/v26.05/cli-mapping.yaml new file mode 100644 index 0000000..649b286 --- /dev/null +++ b/products/apisec/v26.05/cli-mapping.yaml @@ -0,0 +1 @@ +commands: [] diff --git a/products/apisec/v26.05/openapi.json b/products/apisec/v26.05/openapi.json new file mode 100644 index 0000000..089153e --- /dev/null +++ b/products/apisec/v26.05/openapi.json @@ -0,0 +1,9849 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "APISec Management API", + "version": "26.05", + "description": "Generated from APISec skyview APIView classes." + }, + "tags": [ + { + "name": "account" + }, + { + "name": "api_assets" + }, + { + "name": "api_log" + }, + { + "name": "backup" + }, + { + "name": "buildin_waf" + }, + { + "name": "dashboard" + }, + { + "name": "data_science" + }, + { + "name": "discover_api" + }, + { + "name": "discover_data" + }, + { + "name": "evidence_log" + }, + { + "name": "filter" + }, + { + "name": "intranet" + }, + { + "name": "label" + }, + { + "name": "learning" + }, + { + "name": "merge_api" + }, + { + "name": "options" + }, + { + "name": "permanent_log" + }, + { + "name": "plugin" + }, + { + "name": "policy" + }, + { + "name": "report" + }, + { + "name": "risk" + }, + { + "name": "sensitive_data" + }, + { + "name": "split_api" + }, + { + "name": "third_party" + }, + { + "name": "trigger_rule" + }, + { + "name": "update" + }, + { + "name": "utils" + }, + { + "name": "vulnerability" + }, + { + "name": "website" + } + ], + "paths": { + "/api/APITokenLoginAPI": { + "post": { + "operationId": "APITokenLoginAPI_post", + "summary": "POST /api/APITokenLoginAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/ApiIdContextLogAPI": { + "get": { + "operationId": "ApiIdContextLogAPI_get", + "summary": "GET /api/ApiIdContextLogAPI", + "tags": [ + "api_log" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "count", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "event_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/ApplicationAPI": { + "get": { + "operationId": "ApplicationAPI_get", + "summary": "GET /api/ApplicationAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "ApplicationAPI_post", + "summary": "POST /api/ApplicationAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "data_sampling_rate": { + "type": "integer" + }, + "move_in_rules": { + "type": "array", + "items": { + "type": "string" + } + }, + "is_default": { + "type": "boolean" + }, + "static_file_handle": { + "type": "boolean" + }, + "business_upload_handle": { + "type": "boolean" + }, + "business_upload_store": { + "type": "boolean" + }, + "business_download_handle": { + "type": "boolean" + }, + "business_download_store": { + "type": "boolean" + }, + "new_api_interval_hours": { + "type": "integer" + }, + "inactive_interval_hours": { + "type": "integer" + } + }, + "required": [ + "name", + "static_file_handle", + "business_upload_handle", + "business_upload_store", + "business_download_handle", + "business_download_store", + "new_api_interval_hours", + "inactive_interval_hours" + ] + } + } + } + } + }, + "put": { + "operationId": "ApplicationAPI_put", + "summary": "PUT /api/ApplicationAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "data_sampling_rate": { + "type": "integer" + }, + "move_in_rules": { + "type": "array", + "items": { + "type": "string" + } + }, + "is_default": { + "type": "boolean" + }, + "static_file_handle": { + "type": "boolean" + }, + "business_upload_handle": { + "type": "boolean" + }, + "business_upload_store": { + "type": "boolean" + }, + "business_download_handle": { + "type": "boolean" + }, + "business_download_store": { + "type": "boolean" + }, + "new_api_interval_hours": { + "type": "integer" + }, + "inactive_interval_hours": { + "type": "integer" + } + }, + "required": [ + "business_download_handle", + "business_download_store", + "business_upload_handle", + "business_upload_store", + "inactive_interval_hours", + "name", + "new_api_interval_hours", + "static_file_handle" + ] + } + } + } + } + }, + "delete": { + "operationId": "ApplicationAPI_delete", + "summary": "DELETE /api/ApplicationAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/ApplicationPriorityAPI": { + "put": { + "operationId": "ApplicationPriorityAPI_put", + "summary": "PUT /api/ApplicationPriorityAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "priority": { + "type": "integer" + } + }, + "required": [ + "priority" + ] + } + } + } + } + } + }, + "/api/AssetDashboardDetectEventsAPI": { + "get": { + "operationId": "AssetDashboardDetectEventsAPI_get", + "summary": "GET /api/AssetDashboardDetectEventsAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardInterfaceTagAPI": { + "get": { + "operationId": "AssetDashboardInterfaceTagAPI_get", + "summary": "GET /api/AssetDashboardInterfaceTagAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardLowFrequencyAPI": { + "get": { + "operationId": "AssetDashboardLowFrequencyAPI_get", + "summary": "GET /api/AssetDashboardLowFrequencyAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardReqFailedAPI": { + "get": { + "operationId": "AssetDashboardReqFailedAPI_get", + "summary": "GET /api/AssetDashboardReqFailedAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardRequestTrendAPI": { + "get": { + "operationId": "AssetDashboardRequestTrendAPI_get", + "summary": "GET /api/AssetDashboardRequestTrendAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardRiskVulnerableAPI": { + "get": { + "operationId": "AssetDashboardRiskVulnerableAPI_get", + "summary": "GET /api/AssetDashboardRiskVulnerableAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardSameUrlAPI": { + "get": { + "operationId": "AssetDashboardSameUrlAPI_get", + "summary": "GET /api/AssetDashboardSameUrlAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardSensitiveDataAPI": { + "get": { + "operationId": "AssetDashboardSensitiveDataAPI_get", + "summary": "GET /api/AssetDashboardSensitiveDataAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardSiteInterfaceAPI": { + "get": { + "operationId": "AssetDashboardSiteInterfaceAPI_get", + "summary": "GET /api/AssetDashboardSiteInterfaceAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardTrafficCodeAPI": { + "get": { + "operationId": "AssetDashboardTrafficCodeAPI_get", + "summary": "GET /api/AssetDashboardTrafficCodeAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssetDashboardTrafficMethodAPI": { + "get": { + "operationId": "AssetDashboardTrafficMethodAPI_get", + "summary": "GET /api/AssetDashboardTrafficMethodAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/AssociateUserAccountAPI": { + "put": { + "operationId": "AssociateUserAccountAPI_put", + "summary": "PUT /api/AssociateUserAccountAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/AttackTagsAPI": { + "get": { + "operationId": "AttackTagsAPI_get", + "summary": "GET /api/AttackTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "filters", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "object" + } + } + ], + "x-cli-body-fallback": false + }, + "put": { + "operationId": "AttackTagsAPI_put", + "summary": "PUT /api/AttackTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "integer" + } + }, + "required": [ + "filters", + "status" + ] + } + } + } + } + }, + "delete": { + "operationId": "AttackTagsAPI_delete", + "summary": "DELETE /api/AttackTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + } + } + } + } + } + } + } + }, + "/api/BackupDownloadAPI": { + "get": { + "operationId": "BackupDownloadAPI_get", + "summary": "GET /api/BackupDownloadAPI", + "tags": [ + "backup" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "description": "ID", + "schema": { + "type": "integer", + "description": "ID" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/BackupRecordAPI": { + "post": { + "operationId": "BackupRecordAPI_post", + "summary": "POST /api/BackupRecordAPI", + "tags": [ + "backup" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "targets": { + "type": "string" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "targets" + ] + } + } + } + } + }, + "put": { + "operationId": "BackupRecordAPI_put", + "summary": "PUT /api/BackupRecordAPI", + "tags": [ + "backup" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "remark": { + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "operationId": "BackupRecordAPI_delete", + "summary": "DELETE /api/BackupRecordAPI", + "tags": [ + "backup" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/BatchSchemaFieldAPI": { + "put": { + "operationId": "BatchSchemaFieldAPI_put", + "summary": "PUT /api/BatchSchemaFieldAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "formats": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "formats" + ] + } + } + } + } + } + }, + "/api/BuildInDiscoverDataTaskAPI": { + "put": { + "operationId": "BuildInDiscoverDataTaskAPI_put", + "summary": "PUT /api/BuildInDiscoverDataTaskAPI", + "tags": [ + "discover_data" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "apps": { + "type": "string" + }, + "desensitize_view": { + "type": "boolean" + } + }, + "required": [ + "desensitize_view" + ] + } + } + } + } + } + }, + "/api/BuildinRuleTemplateAPI": { + "get": { + "operationId": "BuildinRuleTemplateAPI_get", + "summary": "GET /api/BuildinRuleTemplateAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/BuildinWafConfig": { + "post": { + "operationId": "BuildinWafConfig_post", + "summary": "POST /api/BuildinWafConfig", + "tags": [ + "buildin_waf" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uniq_key": { + "type": "string" + }, + "apisec_management_ip": { + "type": "string" + }, + "apisec_management_port": { + "type": "integer" + }, + "apisec_minion_token": { + "type": "string" + }, + "apisec_detector_ip": { + "type": "string" + }, + "apisec_detector_port": { + "type": "integer" + }, + "sampling_rate": { + "type": "integer" + }, + "waf_management_ip": { + "type": "string" + }, + "waf_management_port": { + "type": "integer" + }, + "waf_minion_token": { + "type": "string" + }, + "waf_minion_cert": { + "type": "string" + } + }, + "required": [ + "uniq_key", + "apisec_management_ip", + "apisec_management_port", + "apisec_minion_token", + "apisec_detector_ip", + "apisec_detector_port", + "sampling_rate", + "waf_management_ip", + "waf_management_port", + "waf_minion_token", + "waf_minion_cert" + ] + } + } + } + } + } + }, + "/api/BusinessTagAPI": { + "post": { + "operationId": "BusinessTagAPI_post", + "summary": "POST /api/BusinessTagAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "名称" + }, + "type": { + "type": "string", + "description": "类型" + }, + "mark_rules": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ] + } + } + } + } + }, + "put": { + "operationId": "BusinessTagAPI_put", + "summary": "PUT /api/BusinessTagAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "名称" + }, + "type": { + "type": "string", + "description": "类型" + }, + "mark_rules": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ] + } + } + } + } + } + }, + "/api/CSRFTokenAPI": { + "get": { + "operationId": "CSRFTokenAPI_get", + "summary": "GET /api/CSRFTokenAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/CSVHeaderAPI": { + "get": { + "operationId": "CSVHeaderAPI_get", + "summary": "GET /api/CSVHeaderAPI", + "tags": [ + "filter" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/CertAPI": { + "get": { + "operationId": "CertAPI_get", + "summary": "GET /api/CertAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "CertAPI_put", + "summary": "PUT /api/CertAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + }, + "name": { + "type": "string", + "description": "证书名称" + } + }, + "required": [ + "id", + "name" + ] + } + } + } + } + }, + "delete": { + "operationId": "CertAPI_delete", + "summary": "DELETE /api/CertAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + }, + "delete_all_resources": { + "type": "boolean", + "description": "是否使用所有资源" + } + }, + "required": [ + "delete_all_resources" + ] + } + } + } + } + } + }, + "/api/CertLoginAPI": { + "get": { + "operationId": "CertLoginAPI_get", + "summary": "GET /api/CertLoginAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "CertLoginAPI_post", + "summary": "POST /api/CertLoginAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tfa_token": { + "type": "string", + "description": "两步验证验证码" + } + } + } + } + } + } + } + }, + "/api/ChangePasswordWithoutLoginAPI": { + "put": { + "operationId": "ChangePasswordWithoutLoginAPI_put", + "summary": "PUT /api/ChangePasswordWithoutLoginAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "old_password": { + "type": "string" + }, + "new_password": { + "type": "string" + } + }, + "required": [ + "username", + "old_password", + "new_password" + ] + } + } + } + } + } + }, + "/api/ChangeUserCredentialAPI": { + "put": { + "operationId": "ChangeUserCredentialAPI_put", + "summary": "PUT /api/ChangeUserCredentialAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authentication_method": { + "type": "string" + }, + "old_password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "tfa_token": { + "type": "string" + } + }, + "required": [ + "authentication_method" + ] + } + } + } + } + } + }, + "/api/DataTagsAPI": { + "get": { + "operationId": "DataTagsAPI_get", + "summary": "GET /api/DataTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "DataTagsAPI_post", + "summary": "POST /api/DataTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "DataTagsAPI_put", + "summary": "PUT /api/DataTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "delete": { + "operationId": "DataTagsAPI_delete", + "summary": "DELETE /api/DataTagsAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/DesensitizeUserAPI": { + "put": { + "operationId": "DesensitizeUserAPI_put", + "summary": "PUT /api/DesensitizeUserAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "desensitize": { + "type": "boolean" + } + }, + "required": [ + "desensitize" + ] + } + } + } + } + } + }, + "/api/DetectorConfigAPI": { + "get": { + "operationId": "DetectorConfigAPI_get", + "summary": "GET /api/DetectorConfigAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "DetectorConfigAPI_put", + "summary": "PUT /api/DetectorConfigAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ip_source": { + "type": "string", + "description": "源 IP 获取方式" + } + }, + "required": [ + "ip_source" + ] + } + } + } + } + } + }, + "/api/DiscoverDataTaskAPI": { + "post": { + "operationId": "DiscoverDataTaskAPI_post", + "summary": "POST /api/DiscoverDataTaskAPI", + "tags": [ + "discover_data" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "apps": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "key_list": { + "type": "string" + }, + "value_list": { + "type": "string" + }, + "verify_code": { + "type": "string" + }, + "origin_scope": { + "type": "string" + }, + "desensitize_view": { + "type": "boolean" + } + }, + "required": [ + "is_enabled", + "name", + "tags", + "key_list", + "value_list", + "origin_scope", + "desensitize_view" + ] + } + } + } + } + }, + "put": { + "operationId": "DiscoverDataTaskAPI_put", + "summary": "PUT /api/DiscoverDataTaskAPI", + "tags": [ + "discover_data" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "apps": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "key_list": { + "type": "string" + }, + "value_list": { + "type": "string" + }, + "verify_code": { + "type": "string" + }, + "origin_scope": { + "type": "string" + }, + "desensitize_view": { + "type": "boolean" + } + }, + "required": [ + "desensitize_view", + "is_enabled", + "key_list", + "name", + "origin_scope", + "tags", + "value_list" + ] + } + } + } + } + } + }, + "/api/DownloadCSVTemplate": { + "get": { + "operationId": "DownloadCSVTemplate_get", + "summary": "GET /api/DownloadCSVTemplate", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/DownloadSystemUpgradePackage": { + "get": { + "operationId": "DownloadSystemUpgradePackage_get", + "summary": "GET /api/DownloadSystemUpgradePackage", + "tags": [ + "update" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/DownloadUserCertAPI": { + "get": { + "operationId": "DownloadUserCertAPI_get", + "summary": "GET /api/DownloadUserCertAPI", + "tags": [ + "account" + ], + "parameters": [ + { + "name": "token", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/EditIntranetGroupItem": { + "post": { + "operationId": "EditIntranetGroupItem_post", + "summary": "POST /api/EditIntranetGroupItem", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "delete": { + "operationId": "EditIntranetGroupItem_delete", + "summary": "DELETE /api/EditIntranetGroupItem", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/EditSSLCertAPI": { + "post": { + "operationId": "EditSSLCertAPI_post", + "summary": "POST /api/EditSSLCertAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/EffectiveScopeAPI": { + "get": { + "operationId": "EffectiveScopeAPI_get", + "summary": "GET /api/EffectiveScopeAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "effective_scope", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/EnableDisableRiskStrategyAPI": { + "put": { + "operationId": "EnableDisableRiskStrategyAPI_put", + "summary": "PUT /api/EnableDisableRiskStrategyAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/EnableSplitAPI": { + "put": { + "operationId": "EnableSplitAPI_put", + "summary": "PUT /api/EnableSplitAPI", + "tags": [ + "split_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + }, + "action": { + "type": "string", + "description": "启用状态" + } + }, + "required": [ + "action" + ] + } + } + } + } + } + }, + "/api/EventWhiteListAPI": { + "post": { + "operationId": "EventWhiteListAPI_post", + "summary": "POST /api/EventWhiteListAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "effective_scope": { + "type": "string" + }, + "effective_uuids": { + "type": "string" + }, + "strategy_id": { + "type": "integer" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "effective_scope", + "strategy_id" + ] + } + } + } + } + }, + "put": { + "operationId": "EventWhiteListAPI_put", + "summary": "PUT /api/EventWhiteListAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "effective_scope": { + "type": "string" + }, + "effective_uuids": { + "type": "string" + }, + "strategy_id": { + "type": "integer" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "effective_scope", + "strategy_id" + ] + } + } + } + } + }, + "delete": { + "operationId": "EventWhiteListAPI_delete", + "summary": "DELETE /api/EventWhiteListAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/FilterEnableDisableAPI": { + "put": { + "operationId": "FilterEnableDisableAPI_put", + "summary": "PUT /api/FilterEnableDisableAPI", + "tags": [ + "filter" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/FilterRuleAPI": { + "post": { + "operationId": "FilterRuleAPI_post", + "summary": "POST /api/FilterRuleAPI", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "是否可用" + }, + "pattern": { + "type": "object", + "description": "匹配条件" + }, + "drop": { + "type": "boolean", + "description": "过滤流量" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "expire_time": { + "type": "string", + "description": "过期时间" + } + }, + "required": [ + "is_enabled", + "pattern", + "drop", + "comment", + "expire_time" + ] + } + } + } + } + }, + "put": { + "operationId": "FilterRuleAPI_put", + "summary": "PUT /api/FilterRuleAPI", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "是否可用" + }, + "pattern": { + "type": "object", + "description": "匹配条件" + }, + "drop": { + "type": "boolean", + "description": "过滤流量" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "expire_time": { + "type": "string", + "description": "过期时间" + } + }, + "required": [ + "comment", + "drop", + "expire_time", + "is_enabled", + "pattern" + ] + } + } + } + } + } + }, + "/api/FilterRulePositionAPI": { + "put": { + "operationId": "FilterRulePositionAPI_put", + "summary": "PUT /api/FilterRulePositionAPI", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "规则 ID" + }, + "position": { + "type": "integer", + "description": "位置" + } + }, + "required": [ + "id", + "position" + ] + } + } + } + } + } + }, + "/api/FirmwareUpdateTimeAPI": { + "get": { + "operationId": "FirmwareUpdateTimeAPI_get", + "summary": "GET /api/FirmwareUpdateTimeAPI", + "tags": [ + "update" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardOwaspAPI": { + "get": { + "operationId": "HomeDashBoardOwaspAPI_get", + "summary": "GET /api/HomeDashBoardOwaspAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardQPSAPI": { + "get": { + "operationId": "HomeDashBoardQPSAPI_get", + "summary": "GET /api/HomeDashBoardQPSAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardQPSTrendAPI": { + "get": { + "operationId": "HomeDashBoardQPSTrendAPI_get", + "summary": "GET /api/HomeDashBoardQPSTrendAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardRequestHistory": { + "get": { + "operationId": "HomeDashBoardRequestHistory_get", + "summary": "GET /api/HomeDashBoardRequestHistory", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardRequestTrendAPI": { + "get": { + "operationId": "HomeDashBoardRequestTrendAPI_get", + "summary": "GET /api/HomeDashBoardRequestTrendAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardSensitiveData": { + "get": { + "operationId": "HomeDashBoardSensitiveData_get", + "summary": "GET /api/HomeDashBoardSensitiveData", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardSiteInterfaceAPI": { + "get": { + "operationId": "HomeDashBoardSiteInterfaceAPI_get", + "summary": "GET /api/HomeDashBoardSiteInterfaceAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/HomeDashBoardTotalRequestAPI": { + "get": { + "operationId": "HomeDashBoardTotalRequestAPI_get", + "summary": "GET /api/HomeDashBoardTotalRequestAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/IdentityAPI": { + "get": { + "operationId": "IdentityAPI_get", + "summary": "GET /api/IdentityAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "IdentityAPI_post", + "summary": "POST /api/IdentityAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuids": { + "type": "string" + }, + "effective_scope": { + "type": "string" + }, + "identity_type": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "uuids", + "effective_scope", + "identity_type", + "pattern" + ] + } + } + } + } + }, + "put": { + "operationId": "IdentityAPI_put", + "summary": "PUT /api/IdentityAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuids": { + "type": "string" + }, + "effective_scope": { + "type": "string" + }, + "identity_type": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "effective_scope", + "identity_type", + "pattern", + "uuids" + ] + } + } + } + } + }, + "delete": { + "operationId": "IdentityAPI_delete", + "summary": "DELETE /api/IdentityAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/IdentityMatchedAPI": { + "put": { + "operationId": "IdentityMatchedAPI_put", + "summary": "PUT /api/IdentityMatchedAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "identity_group_id": { + "type": "integer" + }, + "identity": { + "type": "string" + }, + "effective_scope": { + "type": "integer" + }, + "create_time": { + "type": "string" + } + }, + "required": [ + "identity_group_id", + "identity", + "effective_scope" + ] + } + } + } + } + } + } + }, + "/api/IgnoredInterfaceAPI": { + "post": { + "operationId": "IgnoredInterfaceAPI_post", + "summary": "POST /api/IgnoredInterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "api_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "api_id", + "status", + "reason" + ] + } + } + } + } + }, + "put": { + "operationId": "IgnoredInterfaceAPI_put", + "summary": "PUT /api/IgnoredInterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/ImportApplication": { + "get": { + "operationId": "ImportApplication_get", + "summary": "GET /api/ImportApplication", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "ImportApplication_post", + "summary": "POST /api/ImportApplication", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "ImportApplication_put", + "summary": "PUT /api/ImportApplication", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "data_sampling_rate": { + "type": "integer" + }, + "move_in_rules": { + "type": "array", + "items": { + "type": "string" + } + }, + "is_default": { + "type": "boolean" + }, + "static_file_handle": { + "type": "boolean" + }, + "business_upload_handle": { + "type": "boolean" + }, + "business_upload_store": { + "type": "boolean" + }, + "business_download_handle": { + "type": "boolean" + }, + "business_download_store": { + "type": "boolean" + }, + "new_api_interval_hours": { + "type": "integer" + }, + "inactive_interval_hours": { + "type": "integer" + } + }, + "required": [ + "business_download_handle", + "business_download_store", + "business_upload_handle", + "business_upload_store", + "inactive_interval_hours", + "name", + "new_api_interval_hours", + "static_file_handle" + ] + } + } + } + } + }, + "delete": { + "operationId": "ImportApplication_delete", + "summary": "DELETE /api/ImportApplication", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/InterfaceAPI": { + "get": { + "operationId": "InterfaceAPI_get", + "summary": "GET /api/InterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "InterfaceAPI_post", + "summary": "POST /api/InterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "InterfaceAPI_put", + "summary": "PUT /api/InterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "delete": { + "operationId": "InterfaceAPI_delete", + "summary": "DELETE /api/InterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/InterfaceDetailsAPI": { + "get": { + "operationId": "InterfaceDetailsAPI_get", + "summary": "GET /api/InterfaceDetailsAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "description": "ID", + "schema": { + "type": "integer", + "description": "ID" + } + }, + { + "name": "uuid", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/InterfaceDetailsParamAPI": { + "get": { + "operationId": "InterfaceDetailsParamAPI_get", + "summary": "GET /api/InterfaceDetailsParamAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "count", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "is_req", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "boolean" + } + }, + { + "name": "offset", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/InterfaceGroupAPI": { + "get": { + "operationId": "InterfaceGroupAPI_get", + "summary": "GET /api/InterfaceGroupAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/InterfaceIdentityAPI": { + "put": { + "operationId": "InterfaceIdentityAPI_put", + "summary": "PUT /api/InterfaceIdentityAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "asset_uuid": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "session": { + "type": "string" + }, + "device_id": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "asset_uuid" + ] + } + } + } + } + } + }, + "/api/InterfaceIdentityConfirmAPI": { + "put": { + "operationId": "InterfaceIdentityConfirmAPI_put", + "summary": "PUT /api/InterfaceIdentityConfirmAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/InterfaceIdentityImportAPI": { + "post": { + "operationId": "InterfaceIdentityImportAPI_post", + "summary": "POST /api/InterfaceIdentityImportAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/InterfaceRemarkAPI": { + "get": { + "operationId": "InterfaceRemarkAPI_get", + "summary": "GET /api/InterfaceRemarkAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "InterfaceRemarkAPI_post", + "summary": "POST /api/InterfaceRemarkAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "InterfaceRemarkAPI_put", + "summary": "PUT /api/InterfaceRemarkAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "remark": { + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "operationId": "InterfaceRemarkAPI_delete", + "summary": "DELETE /api/InterfaceRemarkAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/InterfaceResourceAPI": { + "post": { + "operationId": "InterfaceResourceAPI_post", + "summary": "POST /api/InterfaceResourceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "api_id": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "key": { + "type": "string" + }, + "status": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "required": [ + "api_id", + "origin", + "key", + "status" + ] + } + } + } + } + } + }, + "/api/InterfaceResourceRecognitionAPI": { + "put": { + "operationId": "InterfaceResourceRecognitionAPI_put", + "summary": "PUT /api/InterfaceResourceRecognitionAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "required": [ + "filters" + ] + } + } + } + } + }, + "delete": { + "operationId": "InterfaceResourceRecognitionAPI_delete", + "summary": "DELETE /api/InterfaceResourceRecognitionAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/InterfaceResourceRecognitionEnableDisableAPI": { + "put": { + "operationId": "InterfaceResourceRecognitionEnableDisableAPI_put", + "summary": "PUT /api/InterfaceResourceRecognitionEnableDisableAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "uuid": { + "type": "string" + } + }, + "required": [ + "is_enabled", + "uuid" + ] + } + } + } + } + } + }, + "/api/InterfaceSlimAPI": { + "get": { + "operationId": "InterfaceSlimAPI_get", + "summary": "GET /api/InterfaceSlimAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/InterfaceStatAPI": { + "get": { + "operationId": "InterfaceStatAPI_get", + "summary": "GET /api/InterfaceStatAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/InterfaceStatusAPI": { + "get": { + "operationId": "InterfaceStatusAPI_get", + "summary": "GET /api/InterfaceStatusAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + }, + "put": { + "operationId": "InterfaceStatusAPI_put", + "summary": "PUT /api/InterfaceStatusAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/InterfaceSuccessSignAPI": { + "put": { + "operationId": "InterfaceSuccessSignAPI_put", + "summary": "PUT /api/InterfaceSuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "asset_uuid": { + "type": "string" + }, + "success_sign": { + "type": "string" + } + }, + "required": [ + "asset_uuid", + "success_sign" + ] + } + } + } + } + } + }, + "/api/InterfaceSuccessSignConfirmAPI": { + "put": { + "operationId": "InterfaceSuccessSignConfirmAPI_put", + "summary": "PUT /api/InterfaceSuccessSignConfirmAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/InterfaceSuccessSignImportAPI": { + "post": { + "operationId": "InterfaceSuccessSignImportAPI_post", + "summary": "POST /api/InterfaceSuccessSignImportAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/InterfaceSwaggerImportAPI": { + "post": { + "operationId": "InterfaceSwaggerImportAPI_post", + "summary": "POST /api/InterfaceSwaggerImportAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/IntranetGroupConfig": { + "get": { + "operationId": "IntranetGroupConfig_get", + "summary": "GET /api/IntranetGroupConfig", + "tags": [ + "intranet" + ], + "parameters": [ + { + "name": "cidr", + "in": "query", + "required": false, + "description": "CIDR", + "schema": { + "type": "string", + "description": "CIDR" + } + }, + { + "name": "comment", + "in": "query", + "required": false, + "description": "备注", + "schema": { + "type": "string", + "description": "备注" + } + }, + { + "name": "host", + "in": "query", + "required": false, + "description": "域名", + "schema": { + "type": "string", + "description": "域名" + } + }, + { + "name": "name", + "in": "query", + "required": false, + "description": "名称", + "schema": { + "type": "string", + "description": "名称" + } + }, + { + "name": "network_type", + "in": "query", + "required": false, + "description": "网络属性", + "schema": { + "type": "string", + "description": "网络属性" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "IntranetGroupConfig_post", + "summary": "POST /api/IntranetGroupConfig", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cidrs": { + "type": "string" + }, + "name": { + "type": "string", + "description": "名称" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "ip_list": { + "type": "string", + "description": "IP 列表" + }, + "host_list": { + "type": "string", + "description": "域名列表" + }, + "address_type": { + "type": "string", + "description": "地址类型" + }, + "network_type": { + "type": "string", + "description": "网络类型" + } + }, + "required": [ + "cidrs", + "name", + "ip_list", + "host_list" + ] + } + } + } + } + }, + "put": { + "operationId": "IntranetGroupConfig_put", + "summary": "PUT /api/IntranetGroupConfig", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "名称" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "network_type": { + "type": "string", + "description": "网络类型" + } + }, + "required": [ + "name" + ] + } + } + } + } + }, + "delete": { + "operationId": "IntranetGroupConfig_delete", + "summary": "DELETE /api/IntranetGroupConfig", + "tags": [ + "intranet" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/LockInterfaceAPI": { + "put": { + "operationId": "LockInterfaceAPI_put", + "summary": "PUT /api/LockInterfaceAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "locked": { + "type": "boolean", + "description": "锁定" + }, + "filters": { + "type": "object" + }, + "all": { + "type": "boolean" + } + }, + "required": [ + "locked", + "all" + ] + } + } + } + } + } + }, + "/api/LoginAPI": { + "post": { + "operationId": "LoginAPI_post", + "summary": "POST /api/LoginAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tfa_token": { + "type": "string", + "description": "两步验证验证码" + }, + "username": { + "type": "string", + "description": "用户名" + }, + "password": { + "type": "string", + "description": "密码" + } + }, + "required": [ + "username", + "password" + ] + } + } + } + } + } + }, + "/api/LoginDisplacementAPI": { + "get": { + "operationId": "LoginDisplacementAPI_get", + "summary": "GET /api/LoginDisplacementAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "LoginDisplacementAPI_put", + "summary": "PUT /api/LoginDisplacementAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + } + } + }, + "/api/LogoutAPI": { + "post": { + "operationId": "LogoutAPI_post", + "summary": "POST /api/LogoutAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/MarkRuleAPI": { + "post": { + "operationId": "MarkRuleAPI_post", + "summary": "POST /api/MarkRuleAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "名称" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "is_enabled": { + "type": "boolean", + "description": "是否可用" + }, + "pattern": { + "type": "array", + "description": "匹配条件", + "items": { + "type": "string" + } + }, + "tags": { + "type": "string" + } + }, + "required": [ + "name", + "is_enabled", + "pattern" + ] + } + } + } + } + }, + "put": { + "operationId": "MarkRuleAPI_put", + "summary": "PUT /api/MarkRuleAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "名称" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "is_enabled": { + "type": "boolean", + "description": "是否可用" + }, + "pattern": { + "type": "array", + "description": "匹配条件", + "items": { + "type": "string" + } + }, + "tags": { + "type": "string" + } + }, + "required": [ + "is_enabled", + "name", + "pattern" + ] + } + } + } + } + } + }, + "/api/MergeConfigAPI": { + "get": { + "operationId": "MergeConfigAPI_get", + "summary": "GET /api/MergeConfigAPI", + "tags": [ + "merge_api" + ], + "parameters": [ + { + "name": "order_by", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "MergeConfigAPI_post", + "summary": "POST /api/MergeConfigAPI", + "tags": [ + "merge_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "启用状态" + }, + "name": { + "type": "string", + "description": "任务名称" + }, + "comment": { + "type": "string", + "description": "任务备注" + }, + "pattern": { + "type": "string", + "description": "聚合规则" + }, + "merge": { + "type": "boolean", + "description": "是否聚合" + }, + "site_id": { + "type": "string", + "description": "应用的站点" + } + }, + "required": [ + "is_enabled", + "name", + "pattern", + "merge", + "site_id" + ] + } + } + } + } + }, + "put": { + "operationId": "MergeConfigAPI_put", + "summary": "PUT /api/MergeConfigAPI", + "tags": [ + "merge_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "启用状态" + }, + "name": { + "type": "string", + "description": "任务名称" + }, + "comment": { + "type": "string", + "description": "任务备注" + }, + "pattern": { + "type": "string", + "description": "聚合规则" + }, + "merge": { + "type": "boolean", + "description": "是否聚合" + }, + "site_id": { + "type": "string", + "description": "应用的站点" + }, + "api_list": { + "type": "array", + "description": "API 列表", + "items": { + "type": "string" + } + }, + "uuid": { + "type": "string", + "description": "UUID" + }, + "auto": { + "type": "boolean", + "description": "自动生成" + }, + "merge_type": { + "type": "integer", + "description": "聚合类型" + }, + "strict_length": { + "type": "integer", + "description": "固定长度" + } + }, + "required": [ + "is_enabled", + "merge", + "name", + "pattern", + "site_id", + "uuid", + "auto", + "merge_type", + "strict_length" + ] + } + } + } + } + } + } + }, + "/api/MergeConfigStatsAPI": { + "get": { + "operationId": "MergeConfigStatsAPI_get", + "summary": "GET /api/MergeConfigStatsAPI", + "tags": [ + "merge_api" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/MultipleInterfaceSuccessSignAPI": { + "put": { + "operationId": "MultipleInterfaceSuccessSignAPI_put", + "summary": "PUT /api/MultipleInterfaceSuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "success_sign": { + "type": "string" + } + }, + "required": [ + "success_sign" + ] + } + } + } + } + } + }, + "/api/PasswordConfigAPI": { + "get": { + "operationId": "PasswordConfigAPI_get", + "summary": "GET /api/PasswordConfigAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "PasswordConfigAPI_put", + "summary": "PUT /api/PasswordConfigAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "update": { + "type": "string" + }, + "check": { + "type": "string" + } + }, + "required": [ + "update", + "check" + ] + } + } + } + } + } + }, + "/api/PluginPredictResultAPI": { + "get": { + "operationId": "PluginPredictResultAPI_get", + "summary": "GET /api/PluginPredictResultAPI", + "tags": [ + "plugin" + ], + "parameters": [ + { + "name": "plugin_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "PluginPredictResultAPI_post", + "summary": "POST /api/PluginPredictResultAPI", + "tags": [ + "plugin" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + } + } + } + } + }, + "/api/PolicyRuleAPI": { + "post": { + "operationId": "PolicyRuleAPI_post", + "summary": "POST /api/PolicyRuleAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pattern": { + "type": "object", + "description": "匹配条件" + }, + "percentage": { + "type": "integer", + "description": "触发概率" + }, + "action": { + "type": "string", + "description": "动作" + }, + "risk_level": { + "type": "string", + "description": "危险等级" + }, + "attack_type": { + "type": "string", + "description": "攻击类型" + }, + "delay": { + "type": "integer", + "description": "延迟处理" + }, + "log_option": { + "type": "string", + "description": "日志记录设置" + }, + "forbidden_page_config": { + "type": "string", + "description": "null 代表不指定,使用继承的配置;规则无法设置拦截页面" + }, + "is_enabled": { + "type": "boolean", + "description": "是否可用" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "expire_time": { + "type": "string", + "description": "过期时间" + }, + "is_global": { + "type": "boolean", + "description": "是否是全局策略" + }, + "sites": { + "type": "object", + "description": "站点" + }, + "cron_config": { + "type": "string", + "description": "定时方式配置" + } + }, + "required": [ + "pattern", + "percentage", + "action", + "risk_level", + "attack_type", + "delay", + "log_option", + "forbidden_page_config", + "is_enabled", + "comment", + "expire_time", + "is_global", + "sites" + ] + } + } + } + } + }, + "put": { + "operationId": "PolicyRuleAPI_put", + "summary": "PUT /api/PolicyRuleAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pattern": { + "type": "object", + "description": "匹配条件" + }, + "percentage": { + "type": "integer", + "description": "触发概率" + }, + "action": { + "type": "string", + "description": "动作" + }, + "risk_level": { + "type": "string", + "description": "危险等级" + }, + "attack_type": { + "type": "string", + "description": "攻击类型" + }, + "delay": { + "type": "integer", + "description": "延迟处理" + }, + "log_option": { + "type": "string", + "description": "日志记录设置" + }, + "forbidden_page_config": { + "type": "string", + "description": "null 代表不指定,使用继承的配置;规则无法设置拦截页面" + }, + "is_enabled": { + "type": "boolean", + "description": "是否可用" + }, + "comment": { + "type": "string", + "description": "备注" + }, + "expire_time": { + "type": "string", + "description": "过期时间" + }, + "is_global": { + "type": "boolean", + "description": "是否是全局策略" + }, + "sites": { + "type": "object", + "description": "站点" + }, + "cron_config": { + "type": "string", + "description": "定时方式配置" + } + }, + "required": [ + "action", + "attack_type", + "comment", + "delay", + "expire_time", + "forbidden_page_config", + "is_enabled", + "is_global", + "log_option", + "pattern", + "percentage", + "risk_level", + "sites" + ] + } + } + } + } + } + }, + "/api/PolicyRulePositionAPI": { + "put": { + "operationId": "PolicyRulePositionAPI_put", + "summary": "PUT /api/PolicyRulePositionAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "规则 ID" + }, + "position": { + "type": "integer", + "description": "位置" + } + }, + "required": [ + "id", + "position" + ] + } + } + } + } + } + }, + "/api/PolicyRulePriorityAPI": { + "put": { + "operationId": "PolicyRulePriorityAPI_put", + "summary": "PUT /api/PolicyRulePriorityAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "site": { + "type": "integer", + "description": "应用的站点" + }, + "policy_rule_ids": { + "type": "array", + "description": "规则 ID 列表", + "items": { + "type": "string" + } + } + }, + "required": [ + "policy_rule_ids" + ] + } + } + } + } + } + }, + "/api/PolicyRuleSiteBindingAPI": { + "post": { + "operationId": "PolicyRuleSiteBindingAPI_post", + "summary": "POST /api/PolicyRuleSiteBindingAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "site": { + "type": "integer", + "description": "应用的站点" + }, + "policy_rule_ids": { + "type": "array", + "description": "规则 ID 列表", + "items": { + "type": "string" + } + } + }, + "required": [ + "site", + "policy_rule_ids" + ] + } + } + } + } + }, + "delete": { + "operationId": "PolicyRuleSiteBindingAPI_delete", + "summary": "DELETE /api/PolicyRuleSiteBindingAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "site": { + "type": "integer", + "description": "应用的站点" + }, + "policy_rule": { + "type": "integer", + "description": "规则 ID" + } + }, + "required": [ + "site", + "policy_rule" + ] + } + } + } + } + } + }, + "/api/PreprocessPluginAPI": { + "get": { + "operationId": "PreprocessPluginAPI_get", + "summary": "GET /api/PreprocessPluginAPI", + "tags": [ + "plugin" + ], + "parameters": [ + { + "name": "name__like", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "PreprocessPluginAPI_post", + "summary": "POST /api/PreprocessPluginAPI", + "tags": [ + "plugin" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "code": { + "type": "string", + "description": "lua 脚本" + }, + "status": { + "type": "string" + }, + "plugin_type": { + "type": "string" + } + }, + "required": [ + "name", + "code", + "status", + "plugin_type" + ] + } + } + } + } + }, + "put": { + "operationId": "PreprocessPluginAPI_put", + "summary": "PUT /api/PreprocessPluginAPI", + "tags": [ + "plugin" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "code": { + "type": "string", + "description": "lua 脚本" + }, + "status": { + "type": "string" + }, + "plugin_type": { + "type": "string" + } + }, + "required": [ + "code", + "name", + "plugin_type", + "status" + ] + } + } + } + } + }, + "delete": { + "operationId": "PreprocessPluginAPI_delete", + "summary": "DELETE /api/PreprocessPluginAPI", + "tags": [ + "plugin" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/PreprocessPluginStateUpdateAPI": { + "post": { + "operationId": "PreprocessPluginStateUpdateAPI_post", + "summary": "POST /api/PreprocessPluginStateUpdateAPI", + "tags": [ + "plugin" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "timestamp": { + "type": "number" + }, + "id": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "required": [ + "timestamp", + "id", + "state" + ] + } + } + } + } + } + } + }, + "/api/ProfileAPI": { + "get": { + "operationId": "ProfileAPI_get", + "summary": "GET /api/ProfileAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/PushTriggerRuleAPI": { + "put": { + "operationId": "PushTriggerRuleAPI_put", + "summary": "PUT /api/PushTriggerRuleAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/RADIUSConfigAPI": { + "get": { + "operationId": "RADIUSConfigAPI_get", + "summary": "GET /api/RADIUSConfigAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "RADIUSConfigAPI_put", + "summary": "PUT /api/RADIUSConfigAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "config": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "config" + ] + } + } + } + } + } + }, + "/api/RefreshDesensitizeCacheAPI": { + "post": { + "operationId": "RefreshDesensitizeCacheAPI_post", + "summary": "POST /api/RefreshDesensitizeCacheAPI", + "tags": [ + "discover_data" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/RelocateSiteAPI": { + "post": { + "operationId": "RelocateSiteAPI_post", + "summary": "POST /api/RelocateSiteAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "target": { + "type": "integer" + }, + "include_no_app": { + "type": "boolean" + } + }, + "required": [ + "sources", + "target", + "include_no_app" + ] + } + } + } + } + } + }, + "/api/RemoteFilterableChoiceAPI": { + "get": { + "operationId": "RemoteFilterableChoiceAPI_get", + "summary": "GET /api/RemoteFilterableChoiceAPI", + "tags": [ + "filter" + ], + "parameters": [ + { + "name": "query_field", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RemoveTagAPI": { + "put": { + "operationId": "RemoveTagAPI_put", + "summary": "PUT /api/RemoveTagAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_id": { + "type": "integer", + "description": "标签 ID" + }, + "type": { + "type": "string", + "description": "标签类型" + } + }, + "required": [ + "tag_id", + "type" + ] + } + } + } + } + } + }, + "/api/ReportTaskPreview": { + "get": { + "operationId": "ReportTaskPreview_get", + "summary": "GET /api/ReportTaskPreview", + "tags": [ + "report" + ], + "parameters": [ + { + "name": "app_ids", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "custom", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "desensitize_enabled", + "in": "query", + "required": true, + "description": "脱敏开关", + "schema": { + "type": "boolean", + "description": "脱敏开关" + } + }, + { + "name": "end_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_re", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/ReportTemplateAPI": { + "get": { + "operationId": "ReportTemplateAPI_get", + "summary": "GET /api/ReportTemplateAPI", + "tags": [ + "report" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "ReportTemplateAPI_put", + "summary": "PUT /api/ReportTemplateAPI", + "tags": [ + "report" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "name", + "remark" + ] + } + } + } + } + }, + "delete": { + "operationId": "ReportTemplateAPI_delete", + "summary": "DELETE /api/ReportTemplateAPI", + "tags": [ + "report" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/ResetAttackTagsModelAPI": { + "put": { + "operationId": "ResetAttackTagsModelAPI_put", + "summary": "PUT /api/ResetAttackTagsModelAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + } + }, + "required": [ + "filters" + ] + } + } + } + } + } + }, + "/api/ResetUserCredentialAPI": { + "put": { + "operationId": "ResetUserCredentialAPI_put", + "summary": "PUT /api/ResetUserCredentialAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/ResourceRecognitionBindingAPI": { + "get": { + "operationId": "ResourceRecognitionBindingAPI_get", + "summary": "GET /api/ResourceRecognitionBindingAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "ResourceRecognitionBindingAPI_put", + "summary": "PUT /api/ResourceRecognitionBindingAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "uuids" + ] + } + } + } + } + } + }, + "/api/ResourceRecognitionEnableDisableAPI": { + "get": { + "operationId": "ResourceRecognitionEnableDisableAPI_get", + "summary": "GET /api/ResourceRecognitionEnableDisableAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "ResourceRecognitionEnableDisableAPI_put", + "summary": "PUT /api/ResourceRecognitionEnableDisableAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + } + }, + "required": [ + "is_enabled" + ] + } + } + } + } + } + }, + "/api/RestoreBackupRecordAPI": { + "put": { + "operationId": "RestoreBackupRecordAPI_put", + "summary": "PUT /api/RestoreBackupRecordAPI", + "tags": [ + "backup" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/RiskEventAPI": { + "get": { + "operationId": "RiskEventAPI_get", + "summary": "GET /api/RiskEventAPI", + "tags": [ + "risk" + ], + "parameters": [ + { + "name": "event_aggr_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "strategy_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "strategy_type", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "strategy_version", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RiskEventDashBoardHistoryInfoAPI": { + "get": { + "operationId": "RiskEventDashBoardHistoryInfoAPI_get", + "summary": "GET /api/RiskEventDashBoardHistoryInfoAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "detection_module", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "event_description", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_model", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_strategy", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "site", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RiskEventDashBoardInterfaceInfoAPI": { + "get": { + "operationId": "RiskEventDashBoardInterfaceInfoAPI_get", + "summary": "GET /api/RiskEventDashBoardInterfaceInfoAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "detection_module", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "event_description", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_model", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_strategy", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "site", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RiskEventDashBoardRiskNumAPI": { + "get": { + "operationId": "RiskEventDashBoardRiskNumAPI_get", + "summary": "GET /api/RiskEventDashBoardRiskNumAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "detection_module", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "event_description", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_model", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_strategy", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "site", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RiskEventDashBoardSiteInfoAPI": { + "get": { + "operationId": "RiskEventDashBoardSiteInfoAPI_get", + "summary": "GET /api/RiskEventDashBoardSiteInfoAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "api", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "app_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "detection_module", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "event_description", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_model", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "risk_strategy", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "site", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RiskEventGroupAPI": { + "get": { + "operationId": "RiskEventGroupAPI_get", + "summary": "GET /api/RiskEventGroupAPI", + "tags": [ + "risk" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "description": "ID", + "schema": { + "type": "integer", + "description": "ID" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "RiskEventGroupAPI_post", + "summary": "POST /api/RiskEventGroupAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "group_aggr_id": { + "type": "string" + }, + "vuln_aggr_id": { + "type": "string" + }, + "app_id": { + "type": "integer" + }, + "site_id": { + "type": "string" + }, + "api_id": { + "type": "string" + }, + "strategy_id": { + "type": "integer" + }, + "strategy_version": { + "type": "integer" + }, + "type": { + "type": "integer" + }, + "risk_level": { + "type": "integer" + }, + "risk_event_status": { + "type": "integer" + }, + "create_time": { + "type": "integer" + }, + "update_time": { + "type": "integer" + } + }, + "required": [ + "group_aggr_id", + "vuln_aggr_id", + "strategy_id", + "strategy_version", + "type", + "risk_level", + "risk_event_status", + "create_time", + "update_time" + ] + } + } + } + } + } + }, + "put": { + "operationId": "RiskEventGroupAPI_put", + "summary": "PUT /api/RiskEventGroupAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + } + }, + "required": [ + "filters", + "status" + ] + } + } + } + } + } + }, + "/api/RiskEventGroupStatistics": { + "get": { + "operationId": "RiskEventGroupStatistics_get", + "summary": "GET /api/RiskEventGroupStatistics", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/RiskEventStatistics": { + "get": { + "operationId": "RiskEventStatistics_get", + "summary": "GET /api/RiskEventStatistics", + "tags": [ + "risk" + ], + "parameters": [ + { + "name": "group_aggr_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RiskFunctionStatusAPI": { + "get": { + "operationId": "RiskFunctionStatusAPI_get", + "summary": "GET /api/RiskFunctionStatusAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/RiskModelConfigAPI": { + "get": { + "operationId": "RiskModelConfigAPI_get", + "summary": "GET /api/RiskModelConfigAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "RiskModelConfigAPI_put", + "summary": "PUT /api/RiskModelConfigAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "risk_model_is_enabled": { + "type": "boolean", + "description": "风险事件判断功能" + }, + "baseline_is_enabled": { + "type": "boolean", + "description": "基线风险事件判断功能" + } + }, + "required": [ + "risk_model_is_enabled", + "baseline_is_enabled" + ] + } + } + } + } + } + }, + "/api/RiskStrategyAPI": { + "post": { + "operationId": "RiskStrategyAPI_post", + "summary": "POST /api/RiskStrategyAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "effective_scope": { + "type": "string" + }, + "uuids": { + "type": "string" + }, + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "is_enabled": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "risk_level": { + "type": "string" + }, + "pattern": { + "type": "object", + "description": "模型字段" + }, + "risk_event_status": { + "type": "string" + }, + "log_storage": { + "type": "boolean" + } + }, + "required": [ + "effective_scope", + "name", + "type", + "pattern" + ] + } + } + } + } + }, + "put": { + "operationId": "RiskStrategyAPI_put", + "summary": "PUT /api/RiskStrategyAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "effective_scope": { + "type": "string" + }, + "uuids": { + "type": "string" + }, + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "is_enabled": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "risk_level": { + "type": "string" + }, + "pattern": { + "type": "object", + "description": "模型字段" + }, + "risk_event_status": { + "type": "string" + }, + "log_storage": { + "type": "boolean" + } + }, + "required": [ + "effective_scope", + "name", + "pattern", + "type" + ] + } + } + } + } + } + }, + "/api/RiskStrategyAuxiliaryInfoAPI": { + "get": { + "operationId": "RiskStrategyAuxiliaryInfoAPI_get", + "summary": "GET /api/RiskStrategyAuxiliaryInfoAPI", + "tags": [ + "risk" + ], + "parameters": [ + { + "name": "asset_uuids", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "effective_scope", + "in": "query", + "required": true, + "description": "生效范围", + "schema": { + "type": "string", + "description": "生效范围" + } + }, + { + "name": "type", + "in": "query", + "required": true, + "description": "风险类型", + "schema": { + "type": "string", + "description": "风险类型" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/RoleAPI": { + "get": { + "operationId": "RoleAPI_get", + "summary": "GET /api/RoleAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "RoleAPI_post", + "summary": "POST /api/RoleAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "user_ids": { + "type": "string" + } + }, + "required": [ + "name", + "permissions" + ] + } + } + } + } + }, + "put": { + "operationId": "RoleAPI_put", + "summary": "PUT /api/RoleAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "user_ids": { + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "operationId": "RoleAPI_delete", + "summary": "DELETE /api/RoleAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/RuleTemplateAPI": { + "post": { + "operationId": "RuleTemplateAPI_post", + "summary": "POST /api/RuleTemplateAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "remark": { + "type": "string" + }, + "device_ids": { + "type": "string", + "description": "目标设备" + }, + "strategy_id": { + "type": "integer" + }, + "template": { + "type": "object" + }, + "is_enabled": { + "type": "boolean" + } + }, + "required": [ + "remark", + "device_ids", + "strategy_id", + "template", + "is_enabled" + ] + } + } + } + } + }, + "put": { + "operationId": "RuleTemplateAPI_put", + "summary": "PUT /api/RuleTemplateAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "remark": { + "type": "string" + }, + "device_ids": { + "type": "string", + "description": "目标设备" + }, + "strategy_id": { + "type": "integer" + }, + "template": { + "type": "object" + }, + "is_enabled": { + "type": "boolean" + } + }, + "required": [ + "device_ids", + "is_enabled", + "remark", + "strategy_id", + "template" + ] + } + } + } + } + }, + "delete": { + "operationId": "RuleTemplateAPI_delete", + "summary": "DELETE /api/RuleTemplateAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "delete_rules": { + "type": "boolean", + "description": "删除联动规则" + }, + "delete_device_rules": { + "type": "boolean", + "description": "删除目标设备上生成的规则" + } + }, + "required": [ + "delete_rules", + "delete_device_rules" + ] + } + } + } + } + } + }, + "/api/RunStatAPI": { + "get": { + "operationId": "RunStatAPI_get", + "summary": "GET /api/RunStatAPI", + "tags": [ + "api_log" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": true, + "description": "API UUID", + "schema": { + "type": "string", + "description": "API UUID" + } + }, + { + "name": "duration", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/ScannerFilterAPI": { + "put": { + "operationId": "ScannerFilterAPI_put", + "summary": "PUT /api/ScannerFilterAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/ScannerFilterTriggerUpdateAPI": { + "post": { + "operationId": "ScannerFilterTriggerUpdateAPI_post", + "summary": "POST /api/ScannerFilterTriggerUpdateAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/SchemaAPI": { + "get": { + "operationId": "SchemaAPI_get", + "summary": "GET /api/SchemaAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "SchemaAPI_post", + "summary": "POST /api/SchemaAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "SchemaAPI_put", + "summary": "PUT /api/SchemaAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "delete": { + "operationId": "SchemaAPI_delete", + "summary": "DELETE /api/SchemaAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/SchemaFieldAPI": { + "get": { + "operationId": "SchemaFieldAPI_get", + "summary": "GET /api/SchemaFieldAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "is_req", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "boolean" + } + } + ], + "x-cli-body-fallback": false + }, + "put": { + "operationId": "SchemaFieldAPI_put", + "summary": "PUT /api/SchemaFieldAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "api_id": { + "type": "string" + }, + "key": { + "type": "string" + }, + "is_req": { + "type": "boolean" + }, + "format": { + "type": "string" + }, + "manual_input": { + "type": "boolean" + }, + "format_learning_status": { + "type": "string" + }, + "model_learning_enable": { + "type": "boolean" + }, + "model_detect_enable": { + "type": "boolean" + }, + "model_learning_status": { + "type": "string" + } + }, + "required": [ + "is_req", + "manual_input", + "format_learning_status", + "model_learning_enable", + "model_detect_enable", + "model_learning_status" + ] + } + } + } + } + }, + "delete": { + "operationId": "SchemaFieldAPI_delete", + "summary": "DELETE /api/SchemaFieldAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "schema_id": { + "type": "string" + }, + "all_schema": { + "type": "boolean" + }, + "is_req": { + "type": "boolean" + } + }, + "required": [ + "id", + "all_schema", + "is_req" + ] + } + } + } + } + } + }, + "/api/SensitiveCategoryAPI": { + "post": { + "operationId": "SensitiveCategoryAPI_post", + "summary": "POST /api/SensitiveCategoryAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "template_id": { + "type": "integer" + }, + "parent_id": { + "type": "integer" + } + }, + "required": [ + "name", + "remark", + "template_id" + ] + } + } + } + } + }, + "put": { + "operationId": "SensitiveCategoryAPI_put", + "summary": "PUT /api/SensitiveCategoryAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "template_id": { + "type": "integer" + }, + "parent_id": { + "type": "integer" + } + }, + "required": [ + "name", + "remark", + "template_id" + ] + } + } + } + } + }, + "delete": { + "operationId": "SensitiveCategoryAPI_delete", + "summary": "DELETE /api/SensitiveCategoryAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/SensitiveDataCharacteristicAPI": { + "get": { + "operationId": "SensitiveDataCharacteristicAPI_get", + "summary": "GET /api/SensitiveDataCharacteristicAPI", + "tags": [ + "sensitive_data" + ], + "parameters": [ + { + "name": "api_id", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_device_id_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_session_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_user_id_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_username_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "src_ip", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/SensitiveDataTypeAPI": { + "get": { + "operationId": "SensitiveDataTypeAPI_get", + "summary": "GET /api/SensitiveDataTypeAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "desensitization", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "boolean" + } + }, + { + "name": "stage_type", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "tag_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/SensitiveInterfaceInfoAPI": { + "get": { + "operationId": "SensitiveInterfaceInfoAPI_get", + "summary": "GET /api/SensitiveInterfaceInfoAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "desensitization", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "boolean" + } + }, + { + "name": "stage_type", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "tag_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/SensitiveNewInterfaceAPI": { + "get": { + "operationId": "SensitiveNewInterfaceAPI_get", + "summary": "GET /api/SensitiveNewInterfaceAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "desensitization", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "boolean" + } + }, + { + "name": "stage_type", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "tag_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/SensitiveRequestInfoAPI": { + "get": { + "operationId": "SensitiveRequestInfoAPI_get", + "summary": "GET /api/SensitiveRequestInfoAPI", + "tags": [ + "dashboard" + ], + "parameters": [ + { + "name": "datetime", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "desensitization", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "boolean" + } + }, + { + "name": "stage_type", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "tag_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/SensitiveTemplateAPI": { + "get": { + "operationId": "SensitiveTemplateAPI_get", + "summary": "GET /api/SensitiveTemplateAPI", + "tags": [ + "label" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "description": "ID", + "schema": { + "type": "integer", + "description": "ID" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "SensitiveTemplateAPI_post", + "summary": "POST /api/SensitiveTemplateAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "name", + "remark" + ] + } + } + } + } + }, + "put": { + "operationId": "SensitiveTemplateAPI_put", + "summary": "PUT /api/SensitiveTemplateAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "name", + "remark" + ] + } + } + } + } + }, + "delete": { + "operationId": "SensitiveTemplateAPI_delete", + "summary": "DELETE /api/SensitiveTemplateAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/SensitiveValueBindingAPI": { + "post": { + "operationId": "SensitiveValueBindingAPI_post", + "summary": "POST /api/SensitiveValueBindingAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "tag_id": { + "type": "integer" + }, + "level": { + "type": "string" + } + }, + "required": [ + "template_id", + "category_id", + "tag_id", + "level" + ] + } + } + } + } + }, + "put": { + "operationId": "SensitiveValueBindingAPI_put", + "summary": "PUT /api/SensitiveValueBindingAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "tag_id": { + "type": "integer" + }, + "level": { + "type": "string" + } + }, + "required": [ + "category_id", + "level", + "tag_id", + "template_id" + ] + } + } + } + } + }, + "delete": { + "operationId": "SensitiveValueBindingAPI_delete", + "summary": "DELETE /api/SensitiveValueBindingAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/ServerControlledConfigAPI": { + "get": { + "operationId": "ServerControlledConfigAPI_get", + "summary": "GET /api/ServerControlledConfigAPI", + "tags": [ + "options" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/SimpleApplicationAPI": { + "get": { + "operationId": "SimpleApplicationAPI_get", + "summary": "GET /api/SimpleApplicationAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/SiteAPI": { + "get": { + "operationId": "SiteAPI_get", + "summary": "GET /api/SiteAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "SiteAPI_post", + "summary": "POST /api/SiteAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "SiteAPI_put", + "summary": "PUT /api/SiteAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "delete": { + "operationId": "SiteAPI_delete", + "summary": "DELETE /api/SiteAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/SiteFingerprintBindingAPI": { + "put": { + "operationId": "SiteFingerprintBindingAPI_put", + "summary": "PUT /api/SiteFingerprintBindingAPI", + "tags": [ + "third_party" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "site_id": { + "type": "string" + }, + "bindings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "site_id", + "bindings" + ] + } + } + } + } + } + } + }, + "/api/SiteIdentityAPI": { + "put": { + "operationId": "SiteIdentityAPI_put", + "summary": "PUT /api/SiteIdentityAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "asset_uuid": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "session": { + "type": "string" + }, + "device_id": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "asset_uuid" + ] + } + } + } + } + } + }, + "/api/SiteIdentityConfirmAPI": { + "put": { + "operationId": "SiteIdentityConfirmAPI_put", + "summary": "PUT /api/SiteIdentityConfirmAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/SiteInfoAPI": { + "get": { + "operationId": "SiteInfoAPI_get", + "summary": "GET /api/SiteInfoAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "SiteInfoAPI_post", + "summary": "POST /api/SiteInfoAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "put": { + "operationId": "SiteInfoAPI_put", + "summary": "PUT /api/SiteInfoAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "static_file_handle": { + "type": "string" + }, + "business_upload_handle": { + "type": "string" + }, + "business_upload_store": { + "type": "string" + }, + "business_download_handle": { + "type": "string" + }, + "business_download_store": { + "type": "string" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "static_file_handle", + "business_upload_handle", + "business_upload_store", + "business_download_handle", + "business_download_store" + ] + } + } + } + } + }, + "delete": { + "operationId": "SiteInfoAPI_delete", + "summary": "DELETE /api/SiteInfoAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/SiteSuccessSignAPI": { + "put": { + "operationId": "SiteSuccessSignAPI_put", + "summary": "PUT /api/SiteSuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "asset_uuid": { + "type": "string" + }, + "success_sign": { + "type": "string" + } + }, + "required": [ + "asset_uuid", + "success_sign" + ] + } + } + } + } + } + }, + "/api/SiteSuccessSignConfirmAPI": { + "put": { + "operationId": "SiteSuccessSignConfirmAPI_put", + "summary": "PUT /api/SiteSuccessSignConfirmAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, + "/api/SkynetModuleAPI": { + "get": { + "operationId": "SkynetModuleAPI_get", + "summary": "GET /api/SkynetModuleAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "SkynetModuleAPI_put", + "summary": "PUT /api/SkynetModuleAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "high_risk_enable_log": { + "type": "integer" + }, + "medium_risk_enable_log": { + "type": "integer" + }, + "low_risk_enable_log": { + "type": "integer" + } + }, + "required": [ + "high_risk_enable_log", + "medium_risk_enable_log", + "low_risk_enable_log" + ] + } + } + } + } + } + }, + "/api/SkynetRuleAPI": { + "put": { + "operationId": "SkynetRuleAPI_put", + "summary": "PUT /api/SkynetRuleAPI", + "tags": [ + "policy" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "description": "备注" + }, + "risk_level": { + "type": "string", + "description": "危险等级" + }, + "enable_log": { + "type": "boolean", + "description": "是否记录日志" + } + }, + "required": [ + "enable_log" + ] + } + } + } + } + } + }, + "/api/SplitConfigAPI": { + "get": { + "operationId": "SplitConfigAPI_get", + "summary": "GET /api/SplitConfigAPI", + "tags": [ + "split_api" + ], + "parameters": [ + { + "name": "order_by", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "SplitConfigAPI_post", + "summary": "POST /api/SplitConfigAPI", + "tags": [ + "split_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "启用状态" + }, + "name": { + "type": "string", + "description": "规则名称" + }, + "comment": { + "type": "string", + "description": "规则备注" + }, + "origin": { + "type": "string" + }, + "key": { + "type": "string", + "description": "拆分字段" + }, + "original_api_id": { + "type": "string", + "description": "应用的 API" + } + }, + "required": [ + "is_enabled", + "name", + "origin", + "key", + "original_api_id" + ] + } + } + } + } + }, + "put": { + "operationId": "SplitConfigAPI_put", + "summary": "PUT /api/SplitConfigAPI", + "tags": [ + "split_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "启用状态" + }, + "name": { + "type": "string", + "description": "规则名称" + }, + "comment": { + "type": "string", + "description": "规则备注" + }, + "origin": { + "type": "string" + }, + "key": { + "type": "string", + "description": "拆分字段" + }, + "original_api_id": { + "type": "string", + "description": "应用的 API" + }, + "api_list": { + "type": "array", + "description": "API 列表", + "items": { + "type": "string" + } + }, + "site_id": { + "type": "string", + "description": "应用的站点" + }, + "url": { + "type": "string", + "description": "URL" + }, + "method": { + "type": "string", + "description": "方法" + }, + "uuid": { + "type": "string", + "description": "UUID" + }, + "auto": { + "type": "boolean", + "description": "自动生成" + }, + "sample": { + "type": "object", + "description": "样例" + } + }, + "required": [ + "is_enabled", + "key", + "name", + "origin", + "original_api_id", + "site_id", + "url", + "method", + "uuid", + "auto" + ] + } + } + } + } + } + } + }, + "/api/SrcIPContextLogAPI": { + "get": { + "operationId": "SrcIPContextLogAPI_get", + "summary": "GET /api/SrcIPContextLogAPI", + "tags": [ + "api_log" + ], + "parameters": [ + { + "name": "count", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "event_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "src_ip", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/StaticInfoOcrDetect": { + "post": { + "operationId": "StaticInfoOcrDetect_post", + "summary": "POST /api/StaticInfoOcrDetect", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "fid": { + "type": "string" + } + }, + "required": [ + "fid" + ] + } + } + } + } + } + }, + "/api/StrategyAndEventAPI": { + "get": { + "operationId": "StrategyAndEventAPI_get", + "summary": "GET /api/StrategyAndEventAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/StrategyOwaspInfoAPI": { + "get": { + "operationId": "StrategyOwaspInfoAPI_get", + "summary": "GET /api/StrategyOwaspInfoAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/StrategyRunningStateAPI": { + "get": { + "operationId": "StrategyRunningStateAPI_get", + "summary": "GET /api/StrategyRunningStateAPI", + "tags": [ + "dashboard" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/SuccessSignAPI": { + "get": { + "operationId": "SuccessSignAPI_get", + "summary": "GET /api/SuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "SuccessSignAPI_post", + "summary": "POST /api/SuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuids": { + "type": "string" + }, + "effective_scope": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "uuids", + "effective_scope", + "pattern" + ] + } + } + } + } + }, + "put": { + "operationId": "SuccessSignAPI_put", + "summary": "PUT /api/SuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuids": { + "type": "string" + }, + "effective_scope": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "effective_scope", + "pattern", + "uuids" + ] + } + } + } + } + }, + "delete": { + "operationId": "SuccessSignAPI_delete", + "summary": "DELETE /api/SuccessSignAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/SuccessSignMatchedAPI": { + "put": { + "operationId": "SuccessSignMatchedAPI_put", + "summary": "PUT /api/SuccessSignMatchedAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "success_sign_group_id": { + "type": "integer" + }, + "success_sign": { + "type": "string" + }, + "effective_scope": { + "type": "integer" + }, + "create_time": { + "type": "string" + } + }, + "required": [ + "success_sign_group_id", + "success_sign", + "effective_scope" + ] + } + } + } + } + } + } + }, + "/api/SystemUpdateLog": { + "get": { + "operationId": "SystemUpdateLog_get", + "summary": "GET /api/SystemUpdateLog", + "tags": [ + "update" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/SystemUpdateResultLog": { + "get": { + "operationId": "SystemUpdateResultLog_get", + "summary": "GET /api/SystemUpdateResultLog", + "tags": [ + "update" + ], + "parameters": [ + { + "name": "record_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/SystemUpgrade": { + "post": { + "operationId": "SystemUpgrade_post", + "summary": "POST /api/SystemUpgrade", + "tags": [ + "update" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/SystemUpgradePackageAPI": { + "get": { + "operationId": "SystemUpgradePackageAPI_get", + "summary": "GET /api/SystemUpgradePackageAPI", + "tags": [ + "update" + ], + "parameters": [ + { + "name": "count", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/TargetDeviceAPI": { + "get": { + "operationId": "TargetDeviceAPI_get", + "summary": "GET /api/TargetDeviceAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "TargetDeviceAPI_post", + "summary": "POST /api/TargetDeviceAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "address": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "api_token": { + "type": "string" + } + }, + "required": [ + "address", + "enabled", + "name", + "type" + ] + } + } + } + } + }, + "put": { + "operationId": "TargetDeviceAPI_put", + "summary": "PUT /api/TargetDeviceAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "address": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "api_token": { + "type": "string" + } + }, + "required": [ + "address", + "enabled", + "name", + "type" + ] + } + } + } + } + }, + "delete": { + "operationId": "TargetDeviceAPI_delete", + "summary": "DELETE /api/TargetDeviceAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "delete_device_rules": { + "type": "boolean", + "description": "删除目标设备上生成的规则" + } + }, + "required": [ + "delete_device_rules" + ] + } + } + } + } + } + }, + "/api/TransferUpgradePackage": { + "post": { + "operationId": "TransferUpgradePackage_post", + "summary": "POST /api/TransferUpgradePackage", + "tags": [ + "update" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/TriggerRuleAPI": { + "post": { + "operationId": "TriggerRuleAPI_post", + "summary": "POST /api/TriggerRuleAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "device_id": { + "type": "integer", + "description": "联动设备" + }, + "strategy_id": { + "type": "integer", + "description": "风险策略" + }, + "remark": { + "type": "string" + }, + "template": { + "type": "object" + } + }, + "required": [ + "device_id", + "remark", + "template" + ] + } + } + } + } + }, + "delete": { + "operationId": "TriggerRuleAPI_delete", + "summary": "DELETE /api/TriggerRuleAPI", + "tags": [ + "trigger_rule" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "delete_device_rule": { + "type": "boolean", + "description": "删除生成的规则" + } + }, + "required": [ + "delete_device_rule" + ] + } + } + } + } + } + }, + "/api/UnlockUserAPI": { + "put": { + "operationId": "UnlockUserAPI_put", + "summary": "PUT /api/UnlockUserAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/UploadBackupRecordAPI": { + "post": { + "operationId": "UploadBackupRecordAPI_post", + "summary": "POST /api/UploadBackupRecordAPI", + "tags": [ + "backup" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/UploadReportTemplateAPI": { + "post": { + "operationId": "UploadReportTemplateAPI_post", + "summary": "POST /api/UploadReportTemplateAPI", + "tags": [ + "report" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/UploadSSLCertAPI": { + "post": { + "operationId": "UploadSSLCertAPI_post", + "summary": "POST /api/UploadSSLCertAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/UploadSystemUpgradePackage": { + "post": { + "operationId": "UploadSystemUpgradePackage_post", + "summary": "POST /api/UploadSystemUpgradePackage", + "tags": [ + "update" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/UserAPI": { + "get": { + "operationId": "UserAPI_get", + "summary": "GET /api/UserAPI", + "tags": [ + "account" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + }, + "post": { + "operationId": "UserAPI_post", + "summary": "POST /api/UserAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "用户名" + }, + "comment": { + "type": "string" + }, + "authentication_method": { + "type": "string" + }, + "ip_policy": { + "type": "string" + } + }, + "required": [ + "username", + "comment", + "authentication_method" + ] + } + } + } + } + }, + "put": { + "operationId": "UserAPI_put", + "summary": "PUT /api/UserAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "authentication_method": { + "type": "string" + }, + "ip_policy": { + "type": "string" + }, + "is_enabled": { + "type": "boolean" + } + }, + "required": [ + "comment", + "authentication_method", + "ip_policy", + "is_enabled" + ] + } + } + } + } + }, + "delete": { + "operationId": "UserAPI_delete", + "summary": "DELETE /api/UserAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/UserAPITokenAPI": { + "get": { + "operationId": "UserAPITokenAPI_get", + "summary": "GET /api/UserAPITokenAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "UserAPITokenAPI_post", + "summary": "POST /api/UserAPITokenAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "用户名" + }, + "expire_time": { + "type": "string", + "description": "为 null 时, 表示永不过期" + }, + "ip_policy": { + "type": "string", + "description": "IP 白名单" + } + }, + "required": [ + "name", + "ip_policy" + ] + } + } + } + } + }, + "put": { + "operationId": "UserAPITokenAPI_put", + "summary": "PUT /api/UserAPITokenAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "用户名" + }, + "expire_time": { + "type": "string", + "description": "为 null 时, 表示永不过期" + }, + "ip_policy": { + "type": "string", + "description": "IP 白名单" + } + } + } + } + } + } + }, + "delete": { + "operationId": "UserAPITokenAPI_delete", + "summary": "DELETE /api/UserAPITokenAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/UserAccountAPI": { + "post": { + "operationId": "UserAccountAPI_post", + "summary": "POST /api/UserAccountAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "site_id": { + "type": "string" + }, + "api_id": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "email": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "org_info": { + "type": "object" + }, + "login_info": { + "type": "object" + }, + "user_id": { + "type": "string" + }, + "sessions": { + "type": "array", + "items": { + "type": "string" + } + }, + "devices": { + "type": "array", + "items": { + "type": "string" + } + }, + "pattern": { + "type": "object" + } + }, + "required": [ + "username", + "site_id", + "api_id" + ] + } + } + } + } + } + }, + "put": { + "operationId": "UserAccountAPI_put", + "summary": "PUT /api/UserAccountAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "email": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "org_info": { + "type": "object" + }, + "sessions": { + "type": "array", + "items": { + "type": "string" + } + }, + "devices": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_user_accounts": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/UserAccountStatisticsAPI": { + "get": { + "operationId": "UserAccountStatisticsAPI_get", + "summary": "GET /api/UserAccountStatisticsAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "duration", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "site_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "username", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/UserContextLogAPI": { + "get": { + "operationId": "UserContextLogAPI_get", + "summary": "GET /api/UserContextLogAPI", + "tags": [ + "api_log" + ], + "parameters": [ + { + "name": "count", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + }, + { + "name": "event_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_device_id_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_session_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_user_id_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "pii_username_value", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "req_start_time", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "integer" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/UserLoginThrottlingAPI": { + "get": { + "operationId": "UserLoginThrottlingAPI_get", + "summary": "GET /api/UserLoginThrottlingAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "UserLoginThrottlingAPI_put", + "summary": "PUT /api/UserLoginThrottlingAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "period": { + "type": "integer" + }, + "threshold": { + "type": "integer" + } + }, + "required": [ + "enabled", + "period", + "threshold" + ] + } + } + } + } + } + }, + "/api/UserModelAPI": { + "get": { + "operationId": "UserModelAPI_get", + "summary": "GET /api/UserModelAPI", + "tags": [ + "learning" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "description": "ID", + "schema": { + "type": "integer", + "description": "ID" + } + } + ], + "x-cli-body-fallback": false + }, + "put": { + "operationId": "UserModelAPI_put", + "summary": "PUT /api/UserModelAPI", + "tags": [ + "learning" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "strategy_id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "strategy_id", + "status" + ] + } + } + } + } + }, + "delete": { + "operationId": "UserModelAPI_delete", + "summary": "DELETE /api/UserModelAPI", + "tags": [ + "learning" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "strategy_id": { + "type": "integer" + } + }, + "required": [ + "strategy_id" + ] + } + } + } + } + } + }, + "/api/UserModelStrategyAPI": { + "post": { + "operationId": "UserModelStrategyAPI_post", + "summary": "POST /api/UserModelStrategyAPI", + "tags": [ + "learning" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "start_time": { + "type": "string" + }, + "end_time": { + "type": "string" + } + }, + "required": [ + "start_time", + "end_time" + ] + } + } + } + } + }, + "put": { + "operationId": "UserModelStrategyAPI_put", + "summary": "PUT /api/UserModelStrategyAPI", + "tags": [ + "learning" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sites": { + "type": "string" + } + }, + "required": [ + "sites" + ] + } + } + } + } + }, + "delete": { + "operationId": "UserModelStrategyAPI_delete", + "summary": "DELETE /api/UserModelStrategyAPI", + "tags": [ + "learning" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/api/UserRoleBindingAPI": { + "post": { + "operationId": "UserRoleBindingAPI_post", + "summary": "POST /api/UserRoleBindingAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user_ids": { + "type": "string" + }, + "role_ids": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/UserSrcIpApiRequestTop5API": { + "get": { + "operationId": "UserSrcIpApiRequestTop5API_get", + "summary": "GET /api/UserSrcIpApiRequestTop5API", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpAsRequesterAPI": { + "get": { + "operationId": "UserSrcIpAsRequesterAPI_get", + "summary": "GET /api/UserSrcIpAsRequesterAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "ip", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "request_time_tsrange", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/UserSrcIpAsResponserAPI": { + "get": { + "operationId": "UserSrcIpAsResponserAPI_get", + "summary": "GET /api/UserSrcIpAsResponserAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "ip", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "request_time_tsrange", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/UserSrcIpCountryDistributionPI": { + "get": { + "operationId": "UserSrcIpCountryDistributionPI_get", + "summary": "GET /api/UserSrcIpCountryDistributionPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpDstIpResponseTop10API": { + "get": { + "operationId": "UserSrcIpDstIpResponseTop10API_get", + "summary": "GET /api/UserSrcIpDstIpResponseTop10API", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpDstNodeRelationshipGraphTop20API": { + "get": { + "operationId": "UserSrcIpDstNodeRelationshipGraphTop20API_get", + "summary": "GET /api/UserSrcIpDstNodeRelationshipGraphTop20API", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpFlowChartAPI": { + "get": { + "operationId": "UserSrcIpFlowChartAPI_get", + "summary": "GET /api/UserSrcIpFlowChartAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "ip", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "request_time_tsrange", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/UserSrcIpGroupAPI": { + "get": { + "operationId": "UserSrcIpGroupAPI_get", + "summary": "GET /api/UserSrcIpGroupAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpInfoQueryAPI": { + "get": { + "operationId": "UserSrcIpInfoQueryAPI_get", + "summary": "GET /api/UserSrcIpInfoQueryAPI", + "tags": [ + "api_assets" + ], + "parameters": [ + { + "name": "ip", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "request_time_tsrange", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/UserSrcIpListAPI": { + "get": { + "operationId": "UserSrcIpListAPI_get", + "summary": "GET /api/UserSrcIpListAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpNetworkSegmentPieChartAPI": { + "get": { + "operationId": "UserSrcIpNetworkSegmentPieChartAPI_get", + "summary": "GET /api/UserSrcIpNetworkSegmentPieChartAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpNetworkSegmentRequestTop5API": { + "get": { + "operationId": "UserSrcIpNetworkSegmentRequestTop5API_get", + "summary": "GET /api/UserSrcIpNetworkSegmentRequestTop5API", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpPerDayRequestInfoAPI": { + "get": { + "operationId": "UserSrcIpPerDayRequestInfoAPI_get", + "summary": "GET /api/UserSrcIpPerDayRequestInfoAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserSrcIpPerHourRequestInfoPI": { + "get": { + "operationId": "UserSrcIpPerHourRequestInfoPI_get", + "summary": "GET /api/UserSrcIpPerHourRequestInfoPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/UserTFAConfigAPI": { + "get": { + "operationId": "UserTFAConfigAPI_get", + "summary": "GET /api/UserTFAConfigAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "UserTFAConfigAPI_post", + "summary": "POST /api/UserTFAConfigAPI", + "tags": [ + "account" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tfa_token": { + "type": "string", + "description": "两步验证验证码" + }, + "action": { + "type": "string" + } + }, + "required": [ + "action" + ] + } + } + } + } + } + }, + "/api/UsernameExistsCheckAPI": { + "get": { + "operationId": "UsernameExistsCheckAPI_get", + "summary": "GET /api/UsernameExistsCheckAPI", + "tags": [ + "account" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "required": true, + "description": "用户名", + "schema": { + "type": "string", + "description": "用户名" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/ValueCategoryAPI": { + "post": { + "operationId": "ValueCategoryAPI_post", + "summary": "POST /api/ValueCategoryAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "private": { + "type": "boolean" + }, + "identifiable": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + } + }, + "required": [ + "name", + "type", + "private", + "identifiable", + "unique" + ] + } + } + } + } + }, + "put": { + "operationId": "ValueCategoryAPI_put", + "summary": "PUT /api/ValueCategoryAPI", + "tags": [ + "label" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "private": { + "type": "boolean" + }, + "identifiable": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + } + }, + "required": [ + "identifiable", + "name", + "private", + "type", + "unique" + ] + } + } + } + } + } + }, + "/api/VulnerabilityAPI": { + "put": { + "operationId": "VulnerabilityAPI_put", + "summary": "PUT /api/VulnerabilityAPI", + "tags": [ + "vulnerability" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filters": { + "type": "object" + }, + "status": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "required": [ + "filters" + ] + } + } + } + } + } + }, + "/api/VulnerabilityDetailStatistics": { + "get": { + "operationId": "VulnerabilityDetailStatistics_get", + "summary": "GET /api/VulnerabilityDetailStatistics", + "tags": [ + "vulnerability" + ], + "parameters": [ + { + "name": "vuln_aggr_id", + "in": "query", + "required": true, + "description": "", + "schema": { + "type": "string" + } + } + ], + "x-cli-body-fallback": false + } + }, + "/api/VulnerabilityStatistics": { + "get": { + "operationId": "VulnerabilityStatistics_get", + "summary": "GET /api/VulnerabilityStatistics", + "tags": [ + "vulnerability" + ], + "parameters": [], + "x-cli-body-fallback": true + } + }, + "/api/WeakerCipherAPI": { + "post": { + "operationId": "WeakerCipherAPI_post", + "summary": "POST /api/WeakerCipherAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "passwords": { + "type": "string" + } + }, + "required": [ + "passwords" + ] + } + } + } + } + }, + "delete": { + "operationId": "WeakerCipherAPI_delete", + "summary": "DELETE /api/WeakerCipherAPI", + "tags": [ + "risk" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + } + } + } + } + } + } + } + }, + "/api/WebsiteAPI": { + "post": { + "operationId": "WebsiteAPI_post", + "summary": "POST /api/WebsiteAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "站点状态" + }, + "name": { + "type": "string", + "description": "站点名称" + }, + "server_names": { + "type": "string", + "description": "域名" + }, + "ports": { + "type": "array", + "description": "监听端口", + "items": { + "type": "string" + } + }, + "ssl_cert": { + "type": "integer", + "description": "SSL 证书 ID" + }, + "servers": { + "type": "array", + "description": "业务服务器地址", + "items": { + "type": "string" + } + }, + "remark": { + "type": "string", + "description": "备注信息" + } + }, + "required": [ + "is_enabled", + "is_enabled", + "name", + "server_names", + "ports", + "servers" + ] + } + } + } + } + }, + "put": { + "operationId": "WebsiteAPI_put", + "summary": "PUT /api/WebsiteAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID" + }, + "is_enabled": { + "type": "boolean", + "description": "站点状态" + }, + "name": { + "type": "string", + "description": "站点名称" + }, + "server_names": { + "type": "string", + "description": "域名" + }, + "ports": { + "type": "array", + "description": "监听端口", + "items": { + "type": "string" + } + }, + "ssl_cert": { + "type": "integer", + "description": "SSL 证书 ID" + }, + "servers": { + "type": "array", + "description": "业务服务器地址", + "items": { + "type": "string" + } + }, + "remark": { + "type": "string", + "description": "备注信息" + } + }, + "required": [ + "id", + "is_enabled", + "name", + "ports", + "server_names", + "servers" + ] + } + } + } + } + }, + "delete": { + "operationId": "WebsiteAPI_delete", + "summary": "DELETE /api/WebsiteAPI", + "tags": [ + "website" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id__in": { + "type": "string", + "description": "ID 列表" + }, + "delete_all_resources": { + "type": "boolean", + "description": "是否使用所有资源" + } + }, + "required": [ + "delete_all_resources" + ] + } + } + } + } + } + }, + "/api/WebsiteEnableDisableAPI": { + "get": { + "operationId": "WebsiteEnableDisableAPI_get", + "summary": "GET /api/WebsiteEnableDisableAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "put": { + "operationId": "WebsiteEnableDisableAPI_put", + "summary": "PUT /api/WebsiteEnableDisableAPI", + "tags": [ + "discover_api" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + } + }, + "required": [ + "is_enabled" + ] + } + } + } + } + } + }, + "/api/WebsiteMoveInAppAPI": { + "put": { + "operationId": "WebsiteMoveInAppAPI_put", + "summary": "PUT /api/WebsiteMoveInAppAPI", + "tags": [ + "api_assets" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "website_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "filters": { + "type": "object" + }, + "app_id": { + "type": "integer" + }, + "all": { + "type": "boolean" + } + }, + "required": [ + "all" + ] + } + } + } + } + } + } + } +} diff --git a/tools/apisec-openapi-gen/README.md b/tools/apisec-openapi-gen/README.md new file mode 100644 index 0000000..2224408 --- /dev/null +++ b/tools/apisec-openapi-gen/README.md @@ -0,0 +1,13 @@ +# APISec OpenAPI Generator + +This tool generates the first static APISec management OpenAPI document from the APISec Django source tree. + +Run from the `chaitin-cli` repository root: + +```bash +python3 tools/apisec-openapi-gen/generate.py \ + --source /Users/rui.zhu/Documents/workspace/04-开发/tools_llm/product_src/a-vatar/skyview/skyview \ + --output products/apisec/v26.05/openapi.json +``` + +The generator is intentionally conservative. It scans `views.py` files for exported `*API` classes and HTTP methods, then tries to map `@serialize(SomeSerializer)` decorators to fields in sibling `serializers.py` files. When serializer fields cannot be inferred, the operation is still emitted with `x-cli-body-fallback: true` so the CLI can expose the endpoint through `--body` or `--body-file`. diff --git a/tools/apisec-openapi-gen/generate.py b/tools/apisec-openapi-gen/generate.py new file mode 100644 index 0000000..95fd779 --- /dev/null +++ b/tools/apisec-openapi-gen/generate.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +import argparse +import ast +import json +from pathlib import Path + + +HTTP_METHODS = {"get", "post", "put", "delete", "patch"} + + +def main(): + parser = argparse.ArgumentParser(description="Generate APISec management OpenAPI from skyview source") + parser.add_argument("--source", required=True, help="Path to a-vatar/skyview/skyview") + parser.add_argument("--output", required=True, help="Output openapi.json path") + args = parser.parse_args() + + source = Path(args.source) + serializers = load_serializers(source) + paths = {} + tags = set() + + for view_file in sorted(source.glob("*/views.py")): + module = view_file.parent.name + tags.add(module) + tree = parse_file(view_file) + class_nodes = {node.name: node for node in tree.body if isinstance(node, ast.ClassDef)} + for cls in [node for node in tree.body if isinstance(node, ast.ClassDef) and is_exported_api_view(node, class_nodes)]: + methods = collect_methods(cls, class_nodes) + if not methods: + continue + path = f"/api/{cls.name}" + item = paths.setdefault(path, {}) + for method in methods: + serializer_name, many = serializer_for_method(method) + operation = { + "operationId": f"{cls.name}_{method.name}", + "summary": summary_for(cls.name, method.name), + "tags": [module], + "parameters": [], + "x-cli-body-fallback": True, + } + schema = serializers.get(serializer_name or "") + if schema: + operation["x-cli-body-fallback"] = False + if many: + schema = {"type": "array", "items": schema} + if method.name == "get": + operation["parameters"] = parameters_from_schema(schema) + else: + operation["requestBody"] = { + "required": True, + "content": {"application/json": {"schema": schema}}, + } + elif method.name != "get": + operation["requestBody"] = { + "required": False, + "content": {"application/json": {"schema": {"type": "object"}}}, + } + item[method.name] = operation + + openapi = { + "openapi": "3.0.3", + "info": { + "title": "APISec Management API", + "version": "26.05", + "description": "Generated from APISec skyview APIView classes.", + }, + "tags": [{"name": tag} for tag in sorted(tags)], + "paths": dict(sorted(paths.items())), + } + + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(openapi, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def parse_file(path): + return ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + + +def load_serializers(source): + serializers = {} + for serializer_file in sorted(source.glob("*/serializers.py")): + tree = parse_file(serializer_file) + class_nodes = {node.name: node for node in tree.body if isinstance(node, ast.ClassDef)} + pending = set(class_nodes) + while pending: + progressed = False + for name in list(pending): + node = class_nodes[name] + bases = [base_name(base) for base in node.bases] + if any(base in class_nodes and base not in serializers for base in bases): + continue + schema = {"type": "object", "properties": {}, "required": []} + for base in bases: + if base in serializers: + merge_schema(schema, serializers[base]) + for stmt in node.body: + if isinstance(stmt, ast.Assign): + field_name = assigned_name(stmt) + if not field_name: + continue + field_schema, required = schema_for_field(stmt.value) + if field_schema is None: + continue + schema["properties"][field_name] = field_schema + if required: + schema["required"].append(field_name) + if not schema["required"]: + schema.pop("required") + serializers[name] = schema + pending.remove(name) + progressed = True + if not progressed: + break + return serializers + + +def merge_schema(target, source): + target["properties"].update(source.get("properties", {})) + required = set(target.get("required", [])) + required.update(source.get("required", [])) + target["required"] = sorted(required) + + +def collect_methods(cls, class_nodes): + methods = {} + + def visit(node): + for base in node.bases: + base = base_name(base) + if base in class_nodes: + visit(class_nodes[base]) + for item in node.body: + if isinstance(item, ast.FunctionDef) and item.name in HTTP_METHODS: + methods[item.name] = item + + visit(cls) + return [methods[name] for name in sorted(methods, key=lambda item: ["get", "post", "put", "delete", "patch"].index(item))] + + +def is_exported_api_view(cls, class_nodes): + if class_has_true_assignment(cls, "__is_abstract"): + return False + + seen = set() + + def inherits_api_view(node): + if node.name in seen: + return False + seen.add(node.name) + for base in node.bases: + name = base_name(base) + if name in {"APIView", "CSRFExemptAPIView"}: + return True + if name in class_nodes and inherits_api_view(class_nodes[name]): + return True + return False + + return inherits_api_view(cls) + + +def class_has_true_assignment(cls, name): + for stmt in cls.body: + if not isinstance(stmt, ast.Assign): + continue + for target in stmt.targets: + if isinstance(target, ast.Name) and target.id == name and literal_bool(stmt.value): + return True + return False + + +def serializer_for_method(method): + for decorator in method.decorator_list: + call = decorator if isinstance(decorator, ast.Call) else None + if call is None or call_name(call.func) != "serialize": + continue + serializer_name = None + if call.args: + serializer_name = call_name(call.args[0]) + many = any(keyword.arg == "serializer_many" and literal_bool(keyword.value) for keyword in call.keywords) + return serializer_name, many + return None, False + + +def parameters_from_schema(schema): + params = [] + required = set(schema.get("required", [])) + for name, prop in sorted(schema.get("properties", {}).items()): + params.append({ + "name": name, + "in": "query", + "required": name in required, + "description": prop.get("description", ""), + "schema": prop, + }) + return params + + +def schema_for_field(value): + call = value if isinstance(value, ast.Call) else None + if call is None: + return None, False + name = call_name(call.func) + if not name: + return None, False + required = keyword_value(call, "required") + required = True if required is None else bool(required) + help_text = keyword_value(call, "help_text") + schema = {"type": field_type(name)} + if help_text: + schema["description"] = str(help_text) + if name.endswith("ListField") or name.endswith("ListSerializer"): + schema["type"] = "array" + schema["items"] = {"type": "string"} + if name.endswith("JSONField") or name.endswith("DictField"): + schema["type"] = "object" + return schema, required + + +def field_type(name): + if name.endswith("IntegerField"): + return "integer" + if name.endswith("BooleanField"): + return "boolean" + if name.endswith("FloatField"): + return "number" + if name.endswith("JSONField") or name.endswith("DictField"): + return "object" + return "string" + + +def assigned_name(stmt): + if len(stmt.targets) != 1: + return None + target = stmt.targets[0] + return target.id if isinstance(target, ast.Name) else None + + +def keyword_value(call, name): + for keyword in call.keywords: + if keyword.arg == name: + try: + return ast.literal_eval(keyword.value) + except Exception: + return None + return None + + +def literal_bool(node): + try: + return bool(ast.literal_eval(node)) + except Exception: + return False + + +def base_name(node): + return call_name(node) + + +def call_name(node): + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.Attribute): + parent = call_name(node.value) + return f"{parent}.{node.attr}" if parent else node.attr + if isinstance(node, ast.Call): + return call_name(node.func) + return None + + +def summary_for(class_name, method): + return f"{method.upper()} /api/{class_name}" + + +if __name__ == "__main__": + main() From d8c856ebea94ee94637df97e136d8c8f396ff62d Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:22:18 +0800 Subject: [PATCH 08/12] feat: map priority apisec workflows --- products/apisec/parser_test.go | 41 +++++ products/apisec/v26.05/cli-mapping.yaml | 222 +++++++++++++++++++++++- 2 files changed, 262 insertions(+), 1 deletion(-) diff --git a/products/apisec/parser_test.go b/products/apisec/parser_test.go index 4cc7277..486fbad 100644 --- a/products/apisec/parser_test.go +++ b/products/apisec/parser_test.go @@ -94,6 +94,33 @@ func TestGenerateSemanticCommands(t *testing.T) { } } +func TestEmbeddedMappingCoversPriorityGroups(t *testing.T) { + api, mapping, err := loadEmbeddedSchema() + if err != nil { + t.Fatalf("loadEmbeddedSchema() error = %v", err) + } + commands, err := NewParser(api, mapping).GenerateSemanticCommands() + if err != nil { + t.Fatalf("GenerateSemanticCommands() error = %v", err) + } + + for _, path := range [][]string{ + {"asset", "api"}, + {"asset", "site"}, + {"asset", "app"}, + {"asset", "visitor"}, + {"asset", "config"}, + {"data", "rule"}, + {"risk", "config"}, + {"risk", "event"}, + {"risk", "vulnerability"}, + } { + if findCommandPath(commands, path...) == nil { + t.Fatalf("embedded mapping missing command path %v", path) + } + } +} + func loadMinimalOpenAPI(t *testing.T) *OpenAPI { t.Helper() data, err := os.ReadFile("testdata/openapi_minimal.json") @@ -115,3 +142,17 @@ func findCommand(commands []*cobra.Command, use string) *cobra.Command { } return nil } + +func findCommandPath(commands []*cobra.Command, path ...string) *cobra.Command { + if len(path) == 0 { + return nil + } + cmd := findCommand(commands, path[0]) + for _, segment := range path[1:] { + if cmd == nil { + return nil + } + cmd = findCommand(cmd.Commands(), segment) + } + return cmd +} diff --git a/products/apisec/v26.05/cli-mapping.yaml b/products/apisec/v26.05/cli-mapping.yaml index 649b286..e177ff6 100644 --- a/products/apisec/v26.05/cli-mapping.yaml +++ b/products/apisec/v26.05/cli-mapping.yaml @@ -1 +1,221 @@ -commands: [] +commands: + - path: [asset, app, list] + operationId: ApplicationAPI_get + short: List applications + long: List APISec applications. Operation help keeps the API operation ID for precise automation. + examples: + - chaitin-cli apisec asset app list --output json + - path: [asset, app, create] + operationId: ApplicationAPI_post + short: Create application + examples: + - chaitin-cli apisec asset app create --body-file app.json + - path: [asset, app, update] + operationId: ApplicationAPI_put + short: Update application + - path: [asset, app, delete] + operationId: ApplicationAPI_delete + short: Delete applications + - path: [asset, app, simple-list] + operationId: SimpleApplicationAPI_get + short: List simple application records + - path: [asset, app, priority] + operationId: ApplicationPriorityAPI_put + short: Update application priority + - path: [asset, app, relocate-site] + operationId: RelocateSiteAPI_post + short: Relocate sites between applications + + - path: [asset, site, list] + operationId: SiteAPI_get + short: List sites + - path: [asset, site, create] + operationId: SiteAPI_post + short: Create sites + - path: [asset, site, update] + operationId: SiteAPI_put + short: Update sites + - path: [asset, site, delete] + operationId: SiteAPI_delete + short: Delete sites + - path: [asset, site, update-info] + operationId: SiteInfoAPI_put + short: Update site information + - path: [asset, site, move-to-app] + operationId: WebsiteMoveInAppAPI_put + short: Move websites into an application + - path: [asset, site, enabled] + operationId: WebsiteEnableDisableAPI_get + short: Get website enable status + - path: [asset, site, set-enabled] + operationId: WebsiteEnableDisableAPI_put + short: Enable or disable website discovery + + - path: [asset, api, list] + operationId: InterfaceAPI_get + short: List API assets + - path: [asset, api, create] + operationId: InterfaceAPI_post + short: Create API assets + - path: [asset, api, update] + operationId: InterfaceAPI_put + short: Update API assets + - path: [asset, api, delete] + operationId: InterfaceAPI_delete + short: Delete API assets + - path: [asset, api, detail] + operationId: InterfaceDetailsAPI_get + short: Get API asset details + - path: [asset, api, schema] + operationId: SchemaAPI_get + short: List API schemas + - path: [asset, api, data-tags] + operationId: DataTagsAPI_get + short: List API data tags + - path: [asset, api, status] + operationId: InterfaceStatusAPI_get + short: Get API status + - path: [asset, api, set-status] + operationId: InterfaceStatusAPI_put + short: Update API status + - path: [asset, api, remark] + operationId: InterfaceRemarkAPI_put + short: Update API remark + - path: [asset, api, slim-list] + operationId: InterfaceSlimAPI_get + short: List slim API asset records + + - path: [asset, visitor, query] + operationId: UserSrcIpInfoQueryAPI_get + short: Query visitor source IP information + - path: [asset, visitor, list] + operationId: UserSrcIpListAPI_get + short: List visitor source IPs + - path: [asset, visitor, group] + operationId: UserSrcIpGroupAPI_get + short: Group visitor source IPs + - path: [asset, visitor, requester] + operationId: UserSrcIpAsRequesterAPI_get + short: Query visitor requester view + - path: [asset, visitor, responder] + operationId: UserSrcIpAsResponserAPI_get + short: Query visitor responder view + - path: [asset, visitor, flow-chart] + operationId: UserSrcIpFlowChartAPI_get + short: Query visitor flow chart + + - path: [asset, config, detector] + operationId: DetectorConfigAPI_get + short: Get asset discovery detector config + - path: [asset, config, set-detector] + operationId: DetectorConfigAPI_put + short: Update asset discovery detector config + - path: [asset, config, success-sign] + operationId: SuccessSignAPI_get + short: List success sign rules + - path: [asset, config, create-success-sign] + operationId: SuccessSignAPI_post + short: Create success sign rule + - path: [asset, config, update-success-sign] + operationId: SuccessSignAPI_put + short: Update success sign rule + - path: [asset, config, delete-success-sign] + operationId: SuccessSignAPI_delete + short: Delete success sign rules + - path: [asset, config, identity] + operationId: IdentityAPI_get + short: List identity rules + - path: [asset, config, create-identity] + operationId: IdentityAPI_post + short: Create identity rule + - path: [asset, config, update-identity] + operationId: IdentityAPI_put + short: Update identity rule + - path: [asset, config, delete-identity] + operationId: IdentityAPI_delete + short: Delete identity rules + - path: [asset, config, create-ignored-api] + operationId: IgnoredInterfaceAPI_post + short: Create ignored API rule + - path: [asset, config, update-ignored-api] + operationId: IgnoredInterfaceAPI_put + short: Update ignored API rule + - path: [asset, config, resource-recognition] + operationId: ResourceRecognitionBindingAPI_get + short: Get resource recognition binding + - path: [asset, config, set-resource-recognition] + operationId: ResourceRecognitionBindingAPI_put + short: Update resource recognition binding + - path: [asset, config, effective-scope] + operationId: EffectiveScopeAPI_get + short: Get effective scope + + - path: [data, rule, create] + operationId: DiscoverDataTaskAPI_post + short: Create data discovery rule + - path: [data, rule, update] + operationId: DiscoverDataTaskAPI_put + short: Update data discovery rule + - path: [data, rule, update-built-in] + operationId: BuildInDiscoverDataTaskAPI_put + short: Update built-in data discovery rule + - path: [data, rule, refresh-desensitize-cache] + operationId: RefreshDesensitizeCacheAPI_post + short: Refresh desensitize cache + - path: [data, sensitive-transfer, characteristic] + operationId: SensitiveDataCharacteristicAPI_get + short: Query sensitive data transfer characteristics + + - path: [risk, config, create-strategy] + operationId: RiskStrategyAPI_post + short: Create risk discovery strategy + - path: [risk, config, update-strategy] + operationId: RiskStrategyAPI_put + short: Update risk discovery strategy + - path: [risk, config, model] + operationId: RiskModelConfigAPI_get + short: Get risk model config + - path: [risk, config, set-model] + operationId: RiskModelConfigAPI_put + short: Update risk model config + - path: [risk, config, function-status] + operationId: RiskFunctionStatusAPI_get + short: Get risk function status + - path: [risk, config, create-weak-cipher] + operationId: WeakerCipherAPI_post + short: Create weak cipher or weak password config + - path: [risk, config, delete-weak-cipher] + operationId: WeakerCipherAPI_delete + short: Delete weak cipher or weak password config + - path: [risk, config, create-whitelist] + operationId: EventWhiteListAPI_post + short: Create risk event whitelist + - path: [risk, config, update-whitelist] + operationId: EventWhiteListAPI_put + short: Update risk event whitelist + - path: [risk, config, delete-whitelist] + operationId: EventWhiteListAPI_delete + short: Delete risk event whitelist + - path: [risk, config, auxiliary-info] + operationId: RiskStrategyAuxiliaryInfoAPI_get + short: Get risk strategy auxiliary info + + - path: [risk, event, list] + operationId: RiskEventAPI_get + short: Query risk events + - path: [risk, event, group] + operationId: RiskEventGroupAPI_get + short: Query risk event group + - path: [risk, event, update-group] + operationId: RiskEventGroupAPI_put + short: Update risk event group + + - path: [risk, vulnerability, update] + operationId: VulnerabilityAPI_put + short: Update vulnerability status or comment + - path: [risk, vulnerability, statistics] + operationId: VulnerabilityStatistics_get + short: Query vulnerability statistics + - path: [risk, vulnerability, detail-statistics] + operationId: VulnerabilityDetailStatistics_get + short: Query vulnerability detail statistics From 0646e40f29d1b50c16040e954c4dde1da61019b1 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:24:53 +0800 Subject: [PATCH 09/12] test: verify apisec cli --- products/apisec/client.go | 6 +++++- products/apisec/parser.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/products/apisec/client.go b/products/apisec/client.go index b683488..7c3ad0a 100644 --- a/products/apisec/client.go +++ b/products/apisec/client.go @@ -16,6 +16,10 @@ import ( var dryRun bool +type dryRunResult struct{} + +func (dryRunResult) Error() string { return "dry run" } + type Client struct { config *Config httpClient *http.Client @@ -68,7 +72,7 @@ func (c *Client) Do(ctx context.Context, method, path string, query url.Values, logRequest(req, body) } if dryRun { - return nil + return dryRunResult{} } resp, err := c.httpClient.Do(req) diff --git a/products/apisec/parser.go b/products/apisec/parser.go index 2ee42f1..b8a50e4 100644 --- a/products/apisec/parser.go +++ b/products/apisec/parser.go @@ -327,6 +327,9 @@ func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Ope client := getClient(cmd) var result any if err := client.Do(cmd.Context(), method, apiPath, query, body, &result); err != nil { + if _, ok := err.(dryRunResult); ok { + return nil + } return err } return getRenderer(cmd).Render(result) From 62ca3a8e93d9c1e2798b0c0ffb0bdb232124a988 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 17:41:57 +0800 Subject: [PATCH 10/12] fix: validate apisec risk event commands --- products/apisec/parser.go | 53 +++++++++++++++ products/apisec/parser_test.go | 19 ++++++ products/apisec/types.go | 1 + products/apisec/v26.05/cli-mapping.yaml | 13 +++- products/apisec/v26.05/openapi.json | 89 +++++++++++++++++++++++++ tools/apisec-openapi-gen/generate.py | 2 + 6 files changed, 176 insertions(+), 1 deletion(-) diff --git a/products/apisec/parser.go b/products/apisec/parser.go index b8a50e4..58e5fac 100644 --- a/products/apisec/parser.go +++ b/products/apisec/parser.go @@ -1,6 +1,7 @@ package apisec import ( + "context" "encoding/json" "fmt" "net/url" @@ -166,6 +167,15 @@ func sortedCommandKeys(commands map[string]*cobra.Command) []string { return keys } +func sortedStringKeys(values map[string]string) []string { + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + func rawParentName(path string) string { segments := strings.Split(strings.Trim(path, "/"), "/") if len(segments) == 0 { @@ -192,6 +202,9 @@ func (p *Parser) createOperationCommand(use, method, path string, op *Operation, Short: short, Long: buildOperationHelp(method, path, op, mapped), RunE: func(cmd *cobra.Command, args []string) error { + if mapped != nil { + cmd.SetContext(context.WithValue(cmd.Context(), mappedCommandContextKey{}, mapped)) + } return p.executeCommand(cmd, method, path, op) }, } @@ -206,6 +219,7 @@ func (p *Parser) createOperationCommand(use, method, path string, op *Operation, cmd.Flags().String("body", "", "Request body as JSON string. Use this for complex or generated JSON input.") cmd.Flags().String("body-file", "", "Path to a JSON file used as request body. Takes precedence over --body.") } + cmd.Flags().StringArray("query", nil, "Additional query parameter in key=value form. Repeat for multiple filters or dynamic API query keys.") return cmd } @@ -247,6 +261,14 @@ func buildOperationHelp(method, path string, op *Operation, mapped *MappedComman b.WriteString("- Request body is required by the API schema.\n") } } + if mapped != nil && len(mapped.Query) > 0 { + b.WriteString("\nDefault query:\n") + for _, key := range sortedStringKeys(mapped.Query) { + fmt.Fprintf(&b, "- %s=%s\n", key, mapped.Query[key]) + } + } + b.WriteString("\nDynamic query filters:\n") + b.WriteString("- Use --query key=value for query keys not listed as dedicated flags. Repeat --query for multiple values.\n") if mapped != nil && len(mapped.Examples) > 0 { b.WriteString("\nExamples:\n") for _, example := range mapped.Examples { @@ -299,6 +321,11 @@ func mappedFlagForParam(mapped *MappedCommand, param Parameter) *MappedFlag { func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Operation) error { query := make(url.Values) + if mapped := mappedCommandFromContext(cmd); mapped != nil { + for key, value := range mapped.Query { + query.Set(key, value) + } + } body, err := collectRequestBody(cmd, op.RequestBody) if err != nil { return err @@ -323,6 +350,9 @@ func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Ope query.Set(param.Name, value) } } + if err := collectDynamicQuery(cmd, query); err != nil { + return err + } client := getClient(cmd) var result any @@ -335,6 +365,29 @@ func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Ope return getRenderer(cmd).Render(result) } +func mappedCommandFromContext(cmd *cobra.Command) *MappedCommand { + value := cmd.Context().Value(mappedCommandContextKey{}) + mapped, _ := value.(*MappedCommand) + return mapped +} + +type mappedCommandContextKey struct{} + +func collectDynamicQuery(cmd *cobra.Command, query url.Values) error { + values, err := cmd.Flags().GetStringArray("query") + if err != nil { + return err + } + for _, item := range values { + key, value, ok := strings.Cut(item, "=") + if !ok || strings.TrimSpace(key) == "" { + return fmt.Errorf("invalid --query %q, want key=value", item) + } + query.Add(key, value) + } + return nil +} + func readParameterFlag(cmd *cobra.Command, param Parameter) (string, bool, error) { flag := cmd.Flags().Lookup(flagNameForParam(param)) if flag == nil || !flag.Changed { diff --git a/products/apisec/parser_test.go b/products/apisec/parser_test.go index 486fbad..95ec730 100644 --- a/products/apisec/parser_test.go +++ b/products/apisec/parser_test.go @@ -49,6 +49,7 @@ func TestGenerateRawCommands(t *testing.T) { func TestGenerateSemanticCommands(t *testing.T) { api := loadMinimalOpenAPI(t) + api.Paths["/api/FilterAPI"] = PathItem{Get: &Operation{OperationID: "FilterAPI_get", Summary: "Filter data"}} mapping := &CLIMapping{Commands: []MappedCommand{ { Path: []string{"asset", "app", "list"}, @@ -60,6 +61,14 @@ func TestGenerateSemanticCommands(t *testing.T) { "page": {Name: "page-number", Description: "Page number to fetch."}, }, }, + { + Path: []string{"risk", "event", "list"}, + OperationID: "FilterAPI_get", + Short: "List risk event groups", + Query: map[string]string{ + "scope": "risk:risk_event", + }, + }, }} parser := NewParser(api, mapping) @@ -92,6 +101,16 @@ func TestGenerateSemanticCommands(t *testing.T) { if flag.Usage != "Page number to fetch." { t.Fatalf("flag usage = %q, want mapped description", flag.Usage) } + riskList := findCommandPath(commands, "risk", "event", "list") + if riskList == nil { + t.Fatalf("risk event list command not generated") + } + if !strings.Contains(riskList.Long, "Default query:") || !strings.Contains(riskList.Long, "scope=risk:risk_event") { + t.Fatalf("risk event list help missing default query: %s", riskList.Long) + } + if riskList.Flags().Lookup("query") == nil { + t.Fatalf("query flag not generated") + } } func TestEmbeddedMappingCoversPriorityGroups(t *testing.T) { diff --git a/products/apisec/types.go b/products/apisec/types.go index b6092b8..20df32f 100644 --- a/products/apisec/types.go +++ b/products/apisec/types.go @@ -80,6 +80,7 @@ type MappedCommand struct { Short string `yaml:"short,omitempty"` Long string `yaml:"long,omitempty"` Examples []string `yaml:"examples,omitempty"` + Query map[string]string `yaml:"query,omitempty"` RawHidden bool `yaml:"rawHidden,omitempty"` Flags map[string]MappedFlag `yaml:"flags,omitempty"` Metadata map[string]interface{} `yaml:"metadata,omitempty"` diff --git a/products/apisec/v26.05/cli-mapping.yaml b/products/apisec/v26.05/cli-mapping.yaml index e177ff6..5594cf0 100644 --- a/products/apisec/v26.05/cli-mapping.yaml +++ b/products/apisec/v26.05/cli-mapping.yaml @@ -201,11 +201,22 @@ commands: short: Get risk strategy auxiliary info - path: [risk, event, list] + operationId: FilterAPI_get + short: List risk event groups + long: List risk event groups through APISec FilterAPI. The default query sets scope=risk:risk_event; add filters with --query key=value. + query: + scope: risk:risk_event + examples: + - chaitin-cli apisec risk event list --query count=20 --query offset=0 --output json + - path: [risk, event, detail] operationId: RiskEventAPI_get - short: Query risk events + short: Query risk event detail - path: [risk, event, group] operationId: RiskEventGroupAPI_get short: Query risk event group + - path: [risk, event, statistics] + operationId: RiskEventGroupStatistics_get + short: Query risk event group statistics - path: [risk, event, update-group] operationId: RiskEventGroupAPI_put short: Update risk event group diff --git a/products/apisec/v26.05/openapi.json b/products/apisec/v26.05/openapi.json index 089153e..d0854b6 100644 --- a/products/apisec/v26.05/openapi.json +++ b/products/apisec/v26.05/openapi.json @@ -1941,6 +1941,35 @@ "x-cli-body-fallback": false } }, + "/api/EnableDisablePreprocessPluginAPI": { + "put": { + "operationId": "EnableDisablePreprocessPluginAPI_put", + "summary": "PUT /api/EnableDisablePreprocessPluginAPI", + "tags": [ + "plugin" + ], + "parameters": [], + "x-cli-body-fallback": false, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + } + } + }, "/api/EnableDisableRiskStrategyAPI": { "put": { "operationId": "EnableDisableRiskStrategyAPI_put", @@ -2097,6 +2126,66 @@ } } }, + "/api/FilterAPI": { + "get": { + "operationId": "FilterAPI_get", + "summary": "GET /api/FilterAPI", + "tags": [ + "filter" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "delete": { + "operationId": "FilterAPI_delete", + "summary": "DELETE /api/FilterAPI", + "tags": [ + "filter" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "/api/FilterDownloadAPI": { + "get": { + "operationId": "FilterDownloadAPI_get", + "summary": "GET /api/FilterDownloadAPI", + "tags": [ + "filter" + ], + "parameters": [], + "x-cli-body-fallback": true + }, + "post": { + "operationId": "FilterDownloadAPI_post", + "summary": "POST /api/FilterDownloadAPI", + "tags": [ + "filter" + ], + "parameters": [], + "x-cli-body-fallback": true, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, "/api/FilterEnableDisableAPI": { "put": { "operationId": "FilterEnableDisableAPI_put", diff --git a/tools/apisec-openapi-gen/generate.py b/tools/apisec-openapi-gen/generate.py index 95fd779..38f7c53 100644 --- a/tools/apisec-openapi-gen/generate.py +++ b/tools/apisec-openapi-gen/generate.py @@ -142,6 +142,8 @@ def visit(node): def is_exported_api_view(cls, class_nodes): if class_has_true_assignment(cls, "__is_abstract"): return False + if cls.name.endswith("API"): + return True seen = set() From 5ff243e9be9d7fa04065ac5f18e13ec2841d26df Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 21 May 2026 19:17:19 +0800 Subject: [PATCH 11/12] docs: clarify apisec asset commands --- README.md | 22 ++++++++++++ products/apisec/command.go | 6 ++++ products/apisec/parser_test.go | 45 +++++++++++++++++++++++++ products/apisec/v26.05/cli-mapping.yaml | 16 +++++++-- 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 087b3ae..2dac72d 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ npx skills add chaitin/chaitin-cli | `cloudwalker` | CloudWalker CWPP 事件、资产、漏洞、防护策略和系统管理 | | `tanswer` | T-Answer 防火墙、白名单和阻断规则管理 | | `ddr` | DDR API Token 和连接配置辅助能力 | +| `apisec` | APISec API 资产、站点、应用、访问者、数据安全和风险事件管理 | 根命令负责配置加载、产品命令注册和 BusyBox 风格调用分发;各产品目录负责自己的命令、参数、配置解析和 API 调用逻辑。 @@ -106,6 +107,10 @@ ddr: xray: url: https://xray.example.com/api/v2 api_key: YOUR_API_KEY + +apisec: + url: https://apisec.example.com + api_token: YOUR_API_TOKEN ``` 也可以把同样的配置放到环境变量或本地 `.env` 文件中。变量命名规则为 `_`: @@ -119,12 +124,29 @@ ddr.api_key -> DDR_API_KEY ddr.company_id -> DDR_COMPANY_ID xray.url -> XRAY_URL xray.api_key -> XRAY_API_KEY +apisec.url -> APISEC_URL +apisec.api_token -> APISEC_API_TOKEN safeline-ce.url -> SAFELINE_CE_URL safeline-ce.api_key -> SAFELINE_CE_API_KEY safeline.url -> SAFELINE_URL safeline.api_key -> SAFELINE_API_KEY ``` +APISec 常用查询不需要手动传内部 `scope`,优先使用语义化命令: + +```bash +# 查询站点资产 +chaitin-cli apisec asset site list --query count=100 --query offset=0 --output json + +# 查询 API 资产 +chaitin-cli apisec asset api list --query count=100 --query offset=0 --output json + +# 查询风险事件 +chaitin-cli apisec risk event list --query count=20 --query offset=0 --output json +``` + +`apisec raw` 保留为高级入口,用于调用生成出的底层 API 操作;日常查询建议先运行 `chaitin-cli apisec --help` 或对应语义命令的 `--help`。 + `.env` 示例: ```bash diff --git a/products/apisec/command.go b/products/apisec/command.go index a00cc4b..94bf06a 100644 --- a/products/apisec/command.go +++ b/products/apisec/command.go @@ -31,6 +31,12 @@ Command model: chaitin-cli apisec raw --help Exposes generated raw operations for all available APISec APIs. + chaitin-cli apisec asset site list --query count=100 --query offset=0 --output json + Lists APISec site assets without requiring internal FilterAPI scope names. + + chaitin-cli apisec asset api list --query count=100 --query offset=0 --output json + Lists APISec API assets without requiring internal FilterAPI scope names. + chaitin-cli apisec asset app --help Exposes mapped semantic commands for priority workflows. diff --git a/products/apisec/parser_test.go b/products/apisec/parser_test.go index 95ec730..811285b 100644 --- a/products/apisec/parser_test.go +++ b/products/apisec/parser_test.go @@ -140,6 +140,51 @@ func TestEmbeddedMappingCoversPriorityGroups(t *testing.T) { } } +func TestEmbeddedAssetListCommandsHideInternalScopes(t *testing.T) { + api, mapping, err := loadEmbeddedSchema() + if err != nil { + t.Fatalf("loadEmbeddedSchema() error = %v", err) + } + commands, err := NewParser(api, mapping).GenerateSemanticCommands() + if err != nil { + t.Fatalf("GenerateSemanticCommands() error = %v", err) + } + + tests := []struct { + name string + path []string + wantScope string + wantPhrase string + }{ + { + name: "site list", + path: []string{"asset", "site", "list"}, + wantScope: "scope=inventory:site", + wantPhrase: "without passing the internal scope", + }, + { + name: "api list", + path: []string{"asset", "api", "list"}, + wantScope: "scope=inventory:api", + wantPhrase: "without passing the internal scope", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := findCommandPath(commands, tt.path...) + if cmd == nil { + t.Fatalf("command path %v not generated", tt.path) + } + if !strings.Contains(cmd.Long, tt.wantScope) { + t.Fatalf("help missing default scope %q:\n%s", tt.wantScope, cmd.Long) + } + if !strings.Contains(cmd.Long, tt.wantPhrase) { + t.Fatalf("help missing user-facing guidance %q:\n%s", tt.wantPhrase, cmd.Long) + } + }) + } +} + func loadMinimalOpenAPI(t *testing.T) *OpenAPI { t.Helper() data, err := os.ReadFile("testdata/openapi_minimal.json") diff --git a/products/apisec/v26.05/cli-mapping.yaml b/products/apisec/v26.05/cli-mapping.yaml index 5594cf0..53591df 100644 --- a/products/apisec/v26.05/cli-mapping.yaml +++ b/products/apisec/v26.05/cli-mapping.yaml @@ -27,8 +27,13 @@ commands: short: Relocate sites between applications - path: [asset, site, list] - operationId: SiteAPI_get - short: List sites + operationId: FilterAPI_get + short: List site assets + long: List APISec site assets without passing the internal scope. The command sets scope=inventory:site by default; use --query count=100 --query offset=0 for pagination. + query: + scope: inventory:site + examples: + - chaitin-cli apisec asset site list --query count=100 --query offset=0 --output json - path: [asset, site, create] operationId: SiteAPI_post short: Create sites @@ -52,8 +57,13 @@ commands: short: Enable or disable website discovery - path: [asset, api, list] - operationId: InterfaceAPI_get + operationId: FilterAPI_get short: List API assets + long: List APISec API assets without passing the internal scope. The command sets scope=inventory:api by default; use --query count=100 --query offset=0 for pagination and additional filters. + query: + scope: inventory:api + examples: + - chaitin-cli apisec asset api list --query count=100 --query offset=0 --output json - path: [asset, api, create] operationId: InterfaceAPI_post short: Create API assets From fa918c89c35ffcdd2a392c78b66da60fbaa8a2fd Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 27 May 2026 19:13:21 +0800 Subject: [PATCH 12/12] improve apisec operation safety --- main.go | 2 +- main_test.go | 15 + products/apisec/client.go | 30 +- products/apisec/client_test.go | 44 +++ products/apisec/command.go | 25 +- products/apisec/command_test.go | 22 ++ products/apisec/parser.go | 425 +++++++++++++++++++++++- products/apisec/parser_test.go | 290 ++++++++++++++++ products/apisec/types.go | 14 + products/apisec/v26.05/cli-mapping.yaml | 125 ++++++- 10 files changed, 981 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index f922da6..1064fc3 100644 --- a/main.go +++ b/main.go @@ -44,7 +44,7 @@ func newApp() (*app, error) { } root.PersistentFlags().StringVarP(&a.configPath, "config", "c", a.configPath, "Config file path") - root.PersistentFlags().BoolVar(&a.dryRun, "dry-run", false, "Do not send requests for commands that support dry-run") + root.PersistentFlags().BoolVar(&a.dryRun, "dry-run", false, "Do not send requests; commands that support dry-run print a request summary") a.registerProductCommand(chaitin.NewCommand()) a.registerProductCommand(apisec.NewCommand()) diff --git a/main_test.go b/main_test.go index 74df415..98b5b59 100644 --- a/main_test.go +++ b/main_test.go @@ -3,6 +3,7 @@ package main import ( "os" "path/filepath" + "strings" "testing" ) @@ -17,6 +18,20 @@ func TestNewAppUsesDefaultConfigPath(t *testing.T) { } } +func TestDryRunHelpMentionsRequestSummary(t *testing.T) { + app, err := newApp() + if err != nil { + t.Fatalf("newApp() error = %v", err) + } + flag := app.root.PersistentFlags().Lookup("dry-run") + if flag == nil { + t.Fatalf("missing --dry-run flag") + } + if !strings.Contains(flag.Usage, "request summary") { + t.Fatalf("dry-run usage = %q, want request summary mention", flag.Usage) + } +} + func TestEnsureRuntimeConfigLoaded(t *testing.T) { tmpDir := t.TempDir() configPath := filepath.Join(tmpDir, "custom.yaml") diff --git a/products/apisec/client.go b/products/apisec/client.go index 7c3ad0a..4bb9510 100644 --- a/products/apisec/client.go +++ b/products/apisec/client.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/http" + "net/textproto" "net/url" "os" "strings" @@ -162,7 +163,11 @@ func valueString(value any) string { func logRequest(req *http.Request, body any) { fmt.Fprintf(os.Stderr, "URL: %s %s\n", req.Method, req.URL.String()) if len(req.Header) > 0 { - data, err := json.MarshalIndent(req.Header, "", " ") + headers := req.Header.Clone() + if !verboseSensitive { + maskHeader(headers, "API-TOKEN") + } + data, err := json.MarshalIndent(headers, "", " ") if err == nil { fmt.Fprintf(os.Stderr, "Headers:\n%s\n", string(data)) } @@ -176,3 +181,26 @@ func logRequest(req *http.Request, body any) { fmt.Fprintf(os.Stderr, "Body: %v\n", body) } } + +func maskHeader(headers http.Header, name string) { + name = textproto.CanonicalMIMEHeaderKey(name) + values, ok := headers[name] + if !ok { + return + } + masked := make([]string, 0, len(values)) + for _, value := range values { + masked = append(masked, maskSecret(value)) + } + headers[name] = masked +} + +func maskSecret(value string) string { + if value == "" { + return "" + } + if len(value) <= 8 { + return "***" + } + return value[:4] + "..." + value[len(value)-4:] +} diff --git a/products/apisec/client_test.go b/products/apisec/client_test.go index f648ac4..77cce09 100644 --- a/products/apisec/client_test.go +++ b/products/apisec/client_test.go @@ -3,9 +3,11 @@ package apisec import ( "context" "encoding/json" + "io" "net/http" "net/http/httptest" "net/url" + "os" "strings" "testing" ) @@ -71,3 +73,45 @@ func TestClientReturnsAPIError(t *testing.T) { t.Fatalf("Do() error = %v, want bad-request", err) } } + +func TestDryRunMasksAPIToken(t *testing.T) { + oldDryRun := dryRun + oldVerboseSensitive := verboseSensitive + dryRun = true + verboseSensitive = false + t.Cleanup(func() { + dryRun = oldDryRun + verboseSensitive = oldVerboseSensitive + }) + + stderr := captureStderr(t, func() { + client := NewClient(&Config{URL: "https://apisec.example", APIToken: "HJn812345678a4cb"}, false) + var result any + _ = client.Do(context.Background(), http.MethodPost, "/api/RiskStrategyAPI", nil, map[string]any{"name": "x"}, &result) + }) + + if strings.Contains(stderr, "HJn812345678a4cb") { + t.Fatalf("dry-run output leaked full token:\n%s", stderr) + } + if !strings.Contains(stderr, "HJn8...a4cb") { + t.Fatalf("dry-run output missing masked token:\n%s", stderr) + } +} + +func captureStderr(t *testing.T, fn func()) string { + t.Helper() + old := os.Stderr + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("Pipe() error = %v", err) + } + os.Stderr = w + fn() + _ = w.Close() + os.Stderr = old + data, err := io.ReadAll(r) + if err != nil { + t.Fatalf("ReadAll() error = %v", err) + } + return string(data) +} diff --git a/products/apisec/command.go b/products/apisec/command.go index 94bf06a..48c03de 100644 --- a/products/apisec/command.go +++ b/products/apisec/command.go @@ -10,8 +10,9 @@ import ( ) var ( - runtimeCfg Config - verbose bool + runtimeCfg Config + verbose bool + verboseSensitive bool ) func NewCommand() *cobra.Command { @@ -51,14 +52,34 @@ Operation help includes endpoint and operation ID so AI agents can map commands cmd.PersistentFlags().String("api-token", "", "APISec API token sent as API-TOKEN header") cmd.PersistentFlags().StringP("output", "o", "json", "Output format (table|json)") cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print request URL, headers, and body") + cmd.PersistentFlags().BoolVar(&verboseSensitive, "verbose-sensitive", false, "Print sensitive values such as API tokens in verbose output") if err := loadDynamicCommands(cmd); err != nil { fmt.Fprintf(os.Stderr, "Warning: %v\n", err) } + cmd.AddCommand(newScopesCommand()) return cmd } +func newScopesCommand() *cobra.Command { + return &cobra.Command{ + Use: "scopes", + Short: "List common APISec FilterAPI scopes", + RunE: func(cmd *cobra.Command, args []string) error { + scopes := []map[string]string{ + {"scope": "inventory:app", "description": "application assets"}, + {"scope": "inventory:site", "description": "site assets"}, + {"scope": "inventory:api", "description": "API assets"}, + {"scope": "inventory:detect:split", "description": "predicate split rules"}, + {"scope": "risk:detect:strategy", "description": "risk discovery strategies"}, + {"scope": "risk:risk_event", "description": "risk event groups"}, + } + return getRenderer(cmd).Render(scopes) + }, + } +} + func ApplyRuntimeConfig(cmd *cobra.Command, cfg config.Raw, isDryRun bool) { productCfg, err := config.DecodeProduct[Config](cfg, "apisec") if err != nil { diff --git a/products/apisec/command_test.go b/products/apisec/command_test.go index 3e9f813..d1f0603 100644 --- a/products/apisec/command_test.go +++ b/products/apisec/command_test.go @@ -2,6 +2,7 @@ package apisec import ( "bytes" + "encoding/json" "strings" "testing" @@ -31,7 +32,28 @@ func TestNewCommand(t *testing.T) { } } +func TestScopesCommandHonorsJSONOutput(t *testing.T) { + cmd := NewCommand() + cmd.SetArgs([]string{"--output", "json", "scopes"}) + var out bytes.Buffer + cmd.SetOut(&out) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + var scopes []map[string]string + if err := json.Unmarshal(out.Bytes(), &scopes); err != nil { + t.Fatalf("scopes output is not JSON: %v\n%s", err, out.String()) + } + if len(scopes) == 0 || scopes[0]["scope"] == "" { + t.Fatalf("scopes output = %#v, want scope objects", scopes) + } +} + func TestApplyRuntimeConfig(t *testing.T) { + oldDryRun := dryRun + t.Cleanup(func() { dryRun = oldDryRun }) + cmd := NewCommand() cfg := config.Raw{} var node yaml.Node diff --git a/products/apisec/parser.go b/products/apisec/parser.go index 58e5fac..000fcf5 100644 --- a/products/apisec/parser.go +++ b/products/apisec/parser.go @@ -1,6 +1,7 @@ package apisec import ( + "bytes" "context" "encoding/json" "fmt" @@ -15,8 +16,9 @@ import ( ) type Parser struct { - api *OpenAPI - mapping *CLIMapping + api *OpenAPI + mapping *CLIMapping + operationMap map[string]*MappedCommand } type operationRef struct { @@ -26,7 +28,46 @@ type operationRef struct { } func NewParser(api *OpenAPI, mapping *CLIMapping) *Parser { - return &Parser{api: api, mapping: mapping} + return &Parser{api: api, mapping: mapping, operationMap: mappedCommandsByOperationID(mapping)} +} + +func mappedCommandsByOperationID(mapping *CLIMapping) map[string]*MappedCommand { + result := make(map[string]*MappedCommand) + if mapping == nil { + return result + } + for i := range mapping.Commands { + mapped := rawHelpMetadata(&mapping.Commands[i]) + if mapped.OperationID == "" { + continue + } + if _, exists := result[mapped.OperationID]; !exists || hasHelpMetadata(mapped) { + result[mapped.OperationID] = mapped + } + } + return result +} + +func rawHelpMetadata(mapped *MappedCommand) *MappedCommand { + if mapped == nil { + return nil + } + return &MappedCommand{ + OperationID: mapped.OperationID, + Short: mapped.Short, + Long: mapped.Long, + Examples: mapped.Examples, + EnumHints: mapped.EnumHints, + BodyExample: mapped.BodyExample, + Aliases: mapped.Aliases, + Rollback: mapped.Rollback, + Flags: mapped.Flags, + Metadata: mapped.Metadata, + } +} + +func hasHelpMetadata(mapped *MappedCommand) bool { + return mapped != nil && (len(mapped.EnumHints) > 0 || len(mapped.BodyExample) > 0 || len(mapped.Aliases) > 0 || mapped.Rollback != nil) } func (p *Parser) GenerateRawCommands() ([]*cobra.Command, error) { @@ -56,7 +97,7 @@ func (p *Parser) GenerateRawCommands() ([]*cobra.Command, error) { if used[key] > 1 { childName = childName + "-" + normalizeSegment(item.op.OperationID) } - parent.AddCommand(p.createOperationCommand(childName, item.method, path, item.op, nil)) + parent.AddCommand(p.createOperationCommand(childName, item.method, path, item.op, p.operationMap[item.op.OperationID])) } } @@ -205,6 +246,20 @@ func (p *Parser) createOperationCommand(use, method, path string, op *Operation, if mapped != nil { cmd.SetContext(context.WithValue(cmd.Context(), mappedCommandContextKey{}, mapped)) } + showRollback, err := cmd.Flags().GetBool("rollback-plan") + if err != nil { + return err + } + if showRollback { + return renderRollbackPlan(cmd, method, path, op, mapped) + } + showSchema, err := cmd.Flags().GetBool("schema") + if err != nil { + return err + } + if showSchema { + return renderSchema(cmd, method, path, op, mapped) + } return p.executeCommand(cmd, method, path, op) }, } @@ -220,10 +275,50 @@ func (p *Parser) createOperationCommand(use, method, path string, op *Operation, cmd.Flags().String("body-file", "", "Path to a JSON file used as request body. Takes precedence over --body.") } cmd.Flags().StringArray("query", nil, "Additional query parameter in key=value form. Repeat for multiple filters or dynamic API query keys.") + cmd.Flags().Bool("schema", false, "Print request schema, enum hints, and examples without sending a request") + cmd.Flags().Bool("rollback-plan", false, "Print rollback guidance for this write operation without sending a request") + if isDangerousOperation(method, mapped) { + cmd.Flags().Bool("yes", false, "Confirm this write operation") + } + addSemanticFlags(cmd, mapped) return cmd } +func isDangerousOperation(method string, mapped *MappedCommand) bool { + method = strings.ToUpper(method) + if method == "DELETE" { + return true + } + if mapped == nil { + return false + } + path := strings.Join(mapped.Path, " ") + return method == "PUT" && (strings.Contains(path, " enable") || strings.Contains(path, " disable") || strings.Contains(path, "delete")) +} + +func addSemanticFlags(cmd *cobra.Command, mapped *MappedCommand) { + if mapped == nil { + return + } + switch strings.Join(mapped.Path, " ") { + case "asset split create": + cmd.Flags().String("api-id", "", "Original API UUID to split") + cmd.Flags().String("origin", "query", "Split source (query|body|1|4)") + cmd.Flags().String("key", "", "Predicate key to split by") + cmd.Flags().String("name", "", "Predicate split rule name") + cmd.Flags().String("comment", "", "Predicate split rule comment") + cmd.Flags().Bool("disabled", false, "Create the rule disabled") + case "risk strategy create-account-abuse-ip": + cmd.Flags().String("scope", "site", "Effective scope (api|site|app|global|1|2|3|4)") + cmd.Flags().StringArray("uuid", nil, "Target UUID. Repeat for multiple targets.") + cmd.Flags().String("name", "账号滥用 IP", "Risk strategy name") + cmd.Flags().Int("src-ip-dis-cnt", 10, "Distinct source IP count threshold") + cmd.Flags().Int("detection-cycle", 2, "Detection cycle") + cmd.Flags().Bool("disabled", false, "Create the strategy disabled") + } +} + func buildOperationHelp(method, path string, op *Operation, mapped *MappedCommand) string { var b strings.Builder if mapped != nil && strings.TrimSpace(mapped.Long) != "" { @@ -257,9 +352,36 @@ func buildOperationHelp(method, path string, op *Operation, mapped *MappedComman if op.RequestBody != nil { b.WriteString("\nBody:\n") b.WriteString("- Use --body for inline JSON or --body-file for a JSON file. --body-file takes precedence.\n") + b.WriteString("- Use --schema to print request schema, enum hints, and examples without sending a request.\n") if op.RequestBody.Required { b.WriteString("- Request body is required by the API schema.\n") } + if schema := requestJSONSchema(op.RequestBody); schema != nil { + b.WriteString("\nRequest schema:\n") + writeSchemaHelp(&b, schema, mapped, "") + } + } + if mapped != nil && len(mapped.Aliases) > 0 { + b.WriteString("\nProduct concepts:\n") + for _, alias := range mapped.Aliases { + fmt.Fprintf(&b, "- %s\n", alias) + } + } + if mapped != nil && len(mapped.BodyExample) > 0 { + b.WriteString("\nExample body:\n") + if data, err := marshalPretty(mapped.BodyExample); err == nil { + b.WriteString(string(data)) + b.WriteString("\n") + } + } + if mapped != nil && mapped.Rollback != nil { + b.WriteString("\nRollback:\n") + if mapped.Rollback.Description != "" { + fmt.Fprintf(&b, "- %s\n", mapped.Rollback.Description) + } + if mapped.Rollback.Command != "" { + fmt.Fprintf(&b, " %s\n", mapped.Rollback.Command) + } } if mapped != nil && len(mapped.Query) > 0 { b.WriteString("\nDefault query:\n") @@ -278,6 +400,131 @@ func buildOperationHelp(method, path string, op *Operation, mapped *MappedComman return strings.TrimRight(b.String(), "\n") } +func requestJSONSchema(requestBody *RequestBody) *Schema { + if requestBody == nil { + return nil + } + media, ok := requestBody.Content["application/json"] + if !ok { + return nil + } + return media.Schema +} + +func writeSchemaHelp(b *strings.Builder, schema *Schema, mapped *MappedCommand, prefix string) { + if schema == nil { + return + } + if schema.Type == "array" { + fmt.Fprintf(b, "- %s: array\n", printableSchemaName(prefix, "body")) + writeSchemaHelp(b, schema.Items, mapped, prefix+"[]") + return + } + if len(schema.Properties) == 0 { + return + } + required := make(map[string]bool) + for _, name := range schema.Required { + required[name] = true + } + for _, name := range sortedSchemaKeys(schema.Properties) { + prop := schema.Properties[name] + fieldName := name + if prefix != "" { + fieldName = prefix + "." + name + } + req := "optional" + if required[name] { + req = "required" + } + desc := strings.TrimSpace(prop.Description) + if desc == "" { + desc = name + } + fmt.Fprintf(b, "- %s (%s, %s): %s", fieldName, schemaType(prop), req, desc) + if mapped != nil { + if hints := mapped.EnumHints[name]; len(hints) > 0 { + b.WriteString(". Values: ") + parts := make([]string, 0, len(hints)) + for _, hint := range hints { + parts = append(parts, hint.Label+"="+hint.Value) + } + b.WriteString(strings.Join(parts, ", ")) + } + } + b.WriteString("\n") + } +} + +func printableSchemaName(prefix, fallback string) string { + if prefix == "" { + return fallback + } + return prefix +} + +func sortedSchemaKeys(values map[string]*Schema) []string { + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func renderSchema(cmd *cobra.Command, method, path string, op *Operation, mapped *MappedCommand) error { + var b strings.Builder + fmt.Fprintf(&b, "Endpoint: %s %s\n", method, path) + if op.OperationID != "" { + fmt.Fprintf(&b, "Operation ID: %s\n", op.OperationID) + } + if schema := requestJSONSchema(op.RequestBody); schema != nil { + b.WriteString("\nRequest schema:\n") + writeSchemaHelp(&b, schema, mapped, "") + } + if mapped != nil && len(mapped.BodyExample) > 0 { + b.WriteString("\nExample body:\n") + if data, err := marshalPretty(mapped.BodyExample); err == nil { + b.WriteString(string(data)) + b.WriteString("\n") + } + } + _, err := fmt.Fprintln(cmd.OutOrStdout(), strings.TrimRight(b.String(), "\n")) + return err +} + +func marshalPretty(value any) ([]byte, error) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + if err := enc.Encode(value); err != nil { + return nil, err + } + return bytes.TrimRight(buf.Bytes(), "\n"), nil +} + +func renderRollbackPlan(cmd *cobra.Command, method, path string, op *Operation, mapped *MappedCommand) error { + var b strings.Builder + fmt.Fprintf(&b, "Will execute: %s %s\n", method, path) + if op.OperationID != "" { + fmt.Fprintf(&b, "Operation ID: %s\n", op.OperationID) + } + if mapped != nil && mapped.Rollback != nil { + b.WriteString("\nRollback:\n") + if mapped.Rollback.Description != "" { + fmt.Fprintf(&b, "%s\n", mapped.Rollback.Description) + } + if mapped.Rollback.Command != "" { + fmt.Fprintf(&b, "%s\n", mapped.Rollback.Command) + } + } else { + b.WriteString("\nRollback: no product-specific rollback plan is available for this operation.\n") + } + _, err := fmt.Fprintln(cmd.OutOrStdout(), strings.TrimRight(b.String(), "\n")) + return err +} + func addParameterFlag(cmd *cobra.Command, param Parameter, mapped *MappedFlag) { name := flagNameForParam(param) if mapped != nil && mapped.Name != "" { @@ -320,6 +567,9 @@ func mappedFlagForParam(mapped *MappedCommand, param Parameter) *MappedFlag { } func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Operation) error { + if err := requireConfirmation(cmd, method, mappedCommandFromContext(cmd)); err != nil { + return err + } query := make(url.Values) if mapped := mappedCommandFromContext(cmd); mapped != nil { for key, value := range mapped.Query { @@ -362,9 +612,89 @@ func (p *Parser) executeCommand(cmd *cobra.Command, method, path string, op *Ope } return err } + if wrapped, ok := wrapMutationResult(method, result, mappedCommandFromContext(cmd)); ok { + return getRenderer(cmd).Render(wrapped) + } return getRenderer(cmd).Render(result) } +func requireConfirmation(cmd *cobra.Command, method string, mapped *MappedCommand) error { + if !isDangerousOperation(method, mapped) { + return nil + } + yes, err := cmd.Flags().GetBool("yes") + if err != nil { + return err + } + if yes { + return nil + } + return fmt.Errorf("this operation can modify or delete resources; pass --yes to confirm") +} + +func wrapMutationResult(method string, result any, mapped *MappedCommand) (map[string]any, bool) { + if mapped == nil || mapped.Rollback == nil || strings.ToUpper(method) != "POST" { + return nil, false + } + id := extractResultID(result) + rollback := mapped.Rollback.Command + if id != "" { + rollback = strings.ReplaceAll(rollback, "", id) + } + wrapped := map[string]any{ + "action": "created", + "resource": resourceName(mapped), + "result": result, + "rollback": rollback, + } + if id != "" { + wrapped["id"] = id + } + return wrapped, true +} + +func resourceName(mapped *MappedCommand) string { + if mapped == nil { + return "unknown" + } + if len(mapped.Path) > 1 { + return strings.Join(mapped.Path[:len(mapped.Path)-1], ":") + } + if len(mapped.Path) == 1 { + return mapped.Path[0] + } + if mapped.OperationID != "" { + return mapped.OperationID + } + return "unknown" +} + +func extractResultID(result any) string { + if result == nil { + return "" + } + switch values := result.(type) { + case map[string]any: + for _, key := range []string{"id", "uuid"} { + if value, ok := values[key]; ok && value != nil { + return fmt.Sprint(value) + } + } + for _, value := range values { + if id := extractResultID(value); id != "" { + return id + } + } + case []any: + for _, value := range values { + if id := extractResultID(value); id != "" { + return id + } + } + } + return "" +} + func mappedCommandFromContext(cmd *cobra.Command) *MappedCommand { value := cmd.Context().Value(mappedCommandContextKey{}) mapped, _ := value.(*MappedCommand) @@ -432,11 +762,98 @@ func collectRequestBody(cmd *cobra.Command, requestBody *RequestBody) (any, erro return nil, err } if bodyText == "" { + if body, ok, err := collectSemanticBody(cmd); ok || err != nil { + return body, err + } return nil, nil } return parseJSONBody([]byte(bodyText)) } +func collectSemanticBody(cmd *cobra.Command) (any, bool, error) { + mapped := mappedCommandFromContext(cmd) + if mapped == nil { + return nil, false, nil + } + switch strings.Join(mapped.Path, " ") { + case "asset split create": + apiID, _ := cmd.Flags().GetString("api-id") + key, _ := cmd.Flags().GetString("key") + name, _ := cmd.Flags().GetString("name") + comment, _ := cmd.Flags().GetString("comment") + origin, _ := cmd.Flags().GetString("origin") + disabled, _ := cmd.Flags().GetBool("disabled") + if apiID == "" && key == "" && name == "" { + return nil, false, nil + } + if apiID == "" || key == "" || name == "" { + return nil, true, fmt.Errorf("--api-id, --key, and --name are required when using split create flags") + } + body := map[string]any{ + "is_enabled": !disabled, + "name": name, + "origin": normalizeSplitOrigin(origin), + "key": key, + "original_api_id": apiID, + } + if comment != "" { + body["comment"] = comment + } + return body, true, nil + case "risk strategy create-account-abuse-ip": + if !cmd.Flags().Changed("uuid") && !cmd.Flags().Changed("src-ip-dis-cnt") && !cmd.Flags().Changed("detection-cycle") && !cmd.Flags().Changed("scope") { + return nil, false, nil + } + scope, _ := cmd.Flags().GetString("scope") + uuids, _ := cmd.Flags().GetStringArray("uuid") + name, _ := cmd.Flags().GetString("name") + srcIPDisCnt, _ := cmd.Flags().GetInt("src-ip-dis-cnt") + detectionCycle, _ := cmd.Flags().GetInt("detection-cycle") + disabled, _ := cmd.Flags().GetBool("disabled") + body := map[string]any{ + "effective_scope": normalizeEffectiveScope(scope), + "uuids": uuids, + "name": name, + "type": "55", + "is_enabled": !disabled, + "pattern": map[string]any{ + "detection_cycle": detectionCycle, + "src_ip_dis_cnt": srcIPDisCnt, + "business_tag": []any{}, + }, + } + return body, true, nil + default: + return nil, false, nil + } +} + +func normalizeSplitOrigin(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "query", "1": + return "1" + case "body", "4": + return "4" + default: + return value + } +} + +func normalizeEffectiveScope(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "api", "1": + return "1" + case "site", "2": + return "2" + case "app", "3": + return "3" + case "global", "4": + return "4" + default: + return value + } +} + func parseJSONBody(data []byte) (any, error) { var body any if err := json.Unmarshal(data, &body); err != nil { diff --git a/products/apisec/parser_test.go b/products/apisec/parser_test.go index 811285b..3e1108b 100644 --- a/products/apisec/parser_test.go +++ b/products/apisec/parser_test.go @@ -1,6 +1,10 @@ package apisec import ( + "bytes" + "context" + "net/http" + "net/http/httptest" "os" "strings" "testing" @@ -47,6 +51,236 @@ func TestGenerateRawCommands(t *testing.T) { } } +func TestSplitCreateBuildsBodyFromFlagsInDryRun(t *testing.T) { + oldDryRun := dryRun + dryRun = true + t.Cleanup(func() { dryRun = oldDryRun }) + + cmd := NewCommand() + cmd.SetContext(context.Background()) + cmd.SetArgs([]string{"--url", "https://apisec.example", "asset", "split", "create", "--api-id", "a-1", "--origin", "query", "--key", "user_id", "--name", "按 user_id 拆分", "--disabled"}) + var out bytes.Buffer + cmd.SetOut(&out) + + stderr := captureStderr(t, func() { + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + }) + + for _, want := range []string{"\"original_api_id\": \"a-1\"", "\"origin\": \"1\"", "\"key\": \"user_id\"", "\"is_enabled\": false"} { + if !strings.Contains(stderr, want) { + t.Fatalf("dry-run output missing %q:\n%s", want, stderr) + } + } +} + +func TestRawFilterGetDoesNotUseSemanticDefaultScope(t *testing.T) { + oldDryRun := dryRun + dryRun = true + t.Cleanup(func() { dryRun = oldDryRun }) + + cmd := NewCommand() + cmd.SetArgs([]string{"--url", "https://apisec.example", "raw", "filter-api", "get", "--query", "scope=risk:detect:strategy", "--query", "type__in=55"}) + + stderr := captureStderr(t, func() { + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + }) + + if strings.Contains(stderr, "inventory%3Aapp") || strings.Contains(stderr, "inventory:app") { + t.Fatalf("raw filter-api get included semantic default scope:\n%s", stderr) + } + if got := strings.Count(stderr, "scope="); got != 1 { + t.Fatalf("raw filter-api get scope count = %d, want 1:\n%s", got, stderr) + } + if !strings.Contains(stderr, "scope=risk%3Adetect%3Astrategy") { + t.Fatalf("raw filter-api get missing explicit scope:\n%s", stderr) + } +} + +func TestSemanticListStillUsesDefaultScope(t *testing.T) { + oldDryRun := dryRun + dryRun = true + t.Cleanup(func() { dryRun = oldDryRun }) + + cmd := NewCommand() + cmd.SetArgs([]string{"--url", "https://apisec.example", "risk", "event", "list"}) + + stderr := captureStderr(t, func() { + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + }) + + if !strings.Contains(stderr, "scope=risk%3Arisk_event") { + t.Fatalf("semantic risk event list missing default scope:\n%s", stderr) + } +} + +func TestRollbackPlanPrintsWithoutRequest(t *testing.T) { + cmd := NewCommand() + cmd.SetArgs([]string{"risk", "strategy", "create-account-abuse-ip", "--rollback-plan"}) + var out bytes.Buffer + cmd.SetOut(&out) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + got := out.String() + for _, want := range []string{"Rollback", "risk:detect:strategy", "", "--yes"} { + if !strings.Contains(got, want) { + t.Fatalf("rollback plan missing %q:\n%s", want, got) + } + } +} + +func TestRawSplitPostUsesSchemaAndRollbackMetadata(t *testing.T) { + cmd := NewCommand() + cmd.SetArgs([]string{"raw", "split-config-api", "post", "--schema"}) + var out bytes.Buffer + cmd.SetOut(&out) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute schema error = %v", err) + } + got := out.String() + for _, want := range []string{"origin", "query=1", "body=4", "original_api_id", "按 user_id 拆分"} { + if !strings.Contains(got, want) { + t.Fatalf("raw schema missing %q:\n%s", want, got) + } + } + + out.Reset() + cmd = NewCommand() + cmd.SetArgs([]string{"raw", "split-config-api", "post", "--rollback-plan"}) + cmd.SetOut(&out) + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute rollback-plan error = %v", err) + } + got = out.String() + for _, want := range []string{"inventory:detect:split", "", "--yes"} { + if !strings.Contains(got, want) { + t.Fatalf("raw rollback plan missing %q:\n%s", want, got) + } + } +} + +func TestScopesCommandListsKnownScopes(t *testing.T) { + cmd := NewCommand() + cmd.SetArgs([]string{"scopes"}) + var out bytes.Buffer + cmd.SetOut(&out) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + got := out.String() + for _, want := range []string{"inventory:detect:split", "risk:detect:strategy", "inventory:api"} { + if !strings.Contains(got, want) { + t.Fatalf("scopes output missing %q:\n%s", want, got) + } + } +} + +func TestDangerousDeleteRequiresYes(t *testing.T) { + cmd := NewCommand() + cmd.SetArgs([]string{"asset", "split", "delete", "--body", `{"id__in":["s-1"]}`}) + + err := cmd.Execute() + if err == nil || !strings.Contains(err.Error(), "--yes") { + t.Fatalf("Execute() error = %v, want --yes requirement", err) + } +} + +func TestCreateWithRollbackMetadataOutputsRollbackCommand(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"err":null,"data":{"id":"54"},"msg":null}`)) + })) + defer server.Close() + + cmd := NewCommand() + cmd.SetArgs([]string{"--url", server.URL, "risk", "strategy", "create-account-abuse-ip", "--uuid", "s-1", "--src-ip-dis-cnt", "10"}) + var out bytes.Buffer + cmd.SetOut(&out) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + got := out.String() + for _, want := range []string{"\"action\":\"created\"", "\"id\":\"54\"", "risk:detect:strategy", "\"54\"", "--yes"} { + if !strings.Contains(got, want) { + t.Fatalf("output missing %q:\n%s", want, got) + } + } +} + +func TestRawSplitCreateWithRollbackMetadataDoesNotPanic(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"err":null,"data":{"id":"4"},"msg":null}`)) + })) + defer server.Close() + + cmd := NewCommand() + cmd.SetArgs([]string{"--url", server.URL, "raw", "split-config-api", "post", "--body", `{"is_enabled":false,"name":"codex-final-split-test","original_api_id":"a-1","origin":1,"key":"codex_test_split_key"}`}) + var out bytes.Buffer + cmd.SetOut(&out) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + got := out.String() + for _, want := range []string{"\"action\":\"created\"", "\"id\":\"4\"", "SplitConfigAPI_post", "inventory:detect:split"} { + if !strings.Contains(got, want) { + t.Fatalf("raw split create output missing %q:\n%s", want, got) + } + } +} + +func TestCreateWrapperExtractsNestedID(t *testing.T) { + result := map[string]any{ + "created": map[string]any{"uuid": "rs-9"}, + } + mapped := &MappedCommand{ + Path: []string{"risk", "strategy", "create-account-abuse-ip"}, + Rollback: &RollbackHint{ + Command: `chaitin-cli apisec raw filter-api delete --body '{"scope":"risk:detect:strategy","id__in":[""]}' --yes`, + }, + } + + wrapped, ok := wrapMutationResult("POST", result, mapped) + if !ok { + t.Fatalf("wrapMutationResult ok = false, want true") + } + if wrapped["id"] != "rs-9" { + t.Fatalf("id = %#v, want rs-9", wrapped["id"]) + } + if !strings.Contains(wrapped["rollback"].(string), "rs-9") { + t.Fatalf("rollback = %q, want nested id substituted", wrapped["rollback"]) + } +} + +func TestCreateWrapperHandlesRawMetadataWithoutPath(t *testing.T) { + mapped := &MappedCommand{ + OperationID: "SplitConfigAPI_post", + Rollback: &RollbackHint{ + Command: `chaitin-cli apisec raw filter-api delete --body '{"scope":"inventory:detect:split","id__in":[""]}' --yes`, + }, + } + + wrapped, ok := wrapMutationResult("POST", map[string]any{"id": "4"}, mapped) + if !ok { + t.Fatalf("wrapMutationResult ok = false, want true") + } + if wrapped["resource"] != "SplitConfigAPI_post" { + t.Fatalf("resource = %#v, want operation ID fallback", wrapped["resource"]) + } + if wrapped["id"] != "4" { + t.Fatalf("id = %#v, want 4", wrapped["id"]) + } +} + func TestGenerateSemanticCommands(t *testing.T) { api := loadMinimalOpenAPI(t) api.Paths["/api/FilterAPI"] = PathItem{Get: &Operation{OperationID: "FilterAPI_get", Summary: "Filter data"}} @@ -127,10 +361,12 @@ func TestEmbeddedMappingCoversPriorityGroups(t *testing.T) { {"asset", "api"}, {"asset", "site"}, {"asset", "app"}, + {"asset", "split"}, {"asset", "visitor"}, {"asset", "config"}, {"data", "rule"}, {"risk", "config"}, + {"risk", "strategy"}, {"risk", "event"}, {"risk", "vulnerability"}, } { @@ -140,6 +376,47 @@ func TestEmbeddedMappingCoversPriorityGroups(t *testing.T) { } } +func TestEmbeddedPriorityCommandsExposeSchemaAndExamples(t *testing.T) { + api, mapping, err := loadEmbeddedSchema() + if err != nil { + t.Fatalf("loadEmbeddedSchema() error = %v", err) + } + commands, err := NewParser(api, mapping).GenerateSemanticCommands() + if err != nil { + t.Fatalf("GenerateSemanticCommands() error = %v", err) + } + + tests := []struct { + path []string + want []string + }{ + { + path: []string{"risk", "strategy", "create-account-abuse-ip"}, + want: []string{"--schema", "账号滥用 IP", "src_ip_dis_cnt", "effective_scope", "55"}, + }, + { + path: []string{"asset", "split", "create"}, + want: []string{"--schema", "谓词拆分规则", "origin", "query=1", "original_api_id"}, + }, + } + for _, tt := range tests { + t.Run(strings.Join(tt.path, " "), func(t *testing.T) { + cmd := findCommandPath(commands, tt.path...) + if cmd == nil { + t.Fatalf("command path %v not generated", tt.path) + } + if cmd.Flags().Lookup("schema") == nil { + t.Fatalf("schema flag not generated") + } + for _, want := range tt.want { + if !strings.Contains(cmd.Long, want) { + t.Fatalf("help missing %q:\n%s", want, cmd.Long) + } + } + }) + } +} + func TestEmbeddedAssetListCommandsHideInternalScopes(t *testing.T) { api, mapping, err := loadEmbeddedSchema() if err != nil { @@ -153,18 +430,28 @@ func TestEmbeddedAssetListCommandsHideInternalScopes(t *testing.T) { tests := []struct { name string path []string + wantOp string wantScope string wantPhrase string }{ + { + name: "app list", + path: []string{"asset", "app", "list"}, + wantOp: "Operation ID: FilterAPI_get", + wantScope: "scope=inventory:app", + wantPhrase: "without passing the internal scope", + }, { name: "site list", path: []string{"asset", "site", "list"}, + wantOp: "Operation ID: FilterAPI_get", wantScope: "scope=inventory:site", wantPhrase: "without passing the internal scope", }, { name: "api list", path: []string{"asset", "api", "list"}, + wantOp: "Operation ID: FilterAPI_get", wantScope: "scope=inventory:api", wantPhrase: "without passing the internal scope", }, @@ -175,6 +462,9 @@ func TestEmbeddedAssetListCommandsHideInternalScopes(t *testing.T) { if cmd == nil { t.Fatalf("command path %v not generated", tt.path) } + if !strings.Contains(cmd.Long, tt.wantOp) { + t.Fatalf("help missing operation %q:\n%s", tt.wantOp, cmd.Long) + } if !strings.Contains(cmd.Long, tt.wantScope) { t.Fatalf("help missing default scope %q:\n%s", tt.wantScope, cmd.Long) } diff --git a/products/apisec/types.go b/products/apisec/types.go index 20df32f..6c9efc0 100644 --- a/products/apisec/types.go +++ b/products/apisec/types.go @@ -81,11 +81,25 @@ type MappedCommand struct { Long string `yaml:"long,omitempty"` Examples []string `yaml:"examples,omitempty"` Query map[string]string `yaml:"query,omitempty"` + EnumHints map[string][]EnumHint `yaml:"enumHints,omitempty"` + BodyExample map[string]interface{} `yaml:"bodyExample,omitempty"` + Aliases []string `yaml:"aliases,omitempty"` + Rollback *RollbackHint `yaml:"rollback,omitempty"` RawHidden bool `yaml:"rawHidden,omitempty"` Flags map[string]MappedFlag `yaml:"flags,omitempty"` Metadata map[string]interface{} `yaml:"metadata,omitempty"` } +type EnumHint struct { + Value string `yaml:"value"` + Label string `yaml:"label"` +} + +type RollbackHint struct { + Description string `yaml:"description,omitempty"` + Command string `yaml:"command,omitempty"` +} + type MappedFlag struct { Name string `yaml:"name,omitempty"` Description string `yaml:"description,omitempty"` diff --git a/products/apisec/v26.05/cli-mapping.yaml b/products/apisec/v26.05/cli-mapping.yaml index 53591df..3b01ea8 100644 --- a/products/apisec/v26.05/cli-mapping.yaml +++ b/products/apisec/v26.05/cli-mapping.yaml @@ -1,10 +1,12 @@ commands: - path: [asset, app, list] - operationId: ApplicationAPI_get + operationId: FilterAPI_get short: List applications - long: List APISec applications. Operation help keeps the API operation ID for precise automation. + long: List APISec applications without passing the internal scope. The command sets scope=inventory:app by default; use --query count=100 --query offset=0 for pagination. + query: + scope: inventory:app examples: - - chaitin-cli apisec asset app list --output json + - chaitin-cli apisec asset app list --query count=100 --query offset=0 --output json - path: [asset, app, create] operationId: ApplicationAPI_post short: Create application @@ -95,6 +97,51 @@ commands: operationId: InterfaceSlimAPI_get short: List slim API asset records + - path: [asset, split, list] + operationId: SplitConfigAPI_get + short: List predicate split rules + long: List APISec predicate split rules. Use --page and --page-size; this endpoint does not use count/offset pagination. + aliases: + - 谓词拆分规则 + examples: + - chaitin-cli apisec asset split list --page 1 --page-size 10 --output json + - path: [asset, split, create] + operationId: SplitConfigAPI_post + short: Create predicate split rule + long: Create an APISec predicate split rule. + aliases: + - 谓词拆分规则 + enumHints: + origin: + - value: "1" + label: query + - value: "4" + label: body + bodyExample: + is_enabled: false + name: 按 user_id 拆分 + original_api_id: "" + origin: "1" + key: user_id + rollback: + description: Delete the created predicate split rule by UUID after creation. + command: chaitin-cli apisec raw filter-api delete --body '{"scope":"inventory:detect:split","id__in":[""]}' --yes + examples: + - chaitin-cli apisec asset split create --api-id a-xxx --origin query --key user_id --name "按 user_id 拆分" --disabled + - path: [asset, split, enable] + operationId: SplitConfigAPI_put + short: Enable predicate split rule + - path: [asset, split, disable] + operationId: SplitConfigAPI_put + short: Disable predicate split rule + - path: [asset, split, delete] + operationId: FilterAPI_delete + short: Delete predicate split rules + bodyExample: + scope: inventory:detect:split + id__in: + - "" + - path: [asset, visitor, query] operationId: UserSrcIpInfoQueryAPI_get short: Query visitor source IP information @@ -179,6 +226,32 @@ commands: - path: [risk, config, create-strategy] operationId: RiskStrategyAPI_post short: Create risk discovery strategy + enumHints: + effective_scope: + - value: "1" + label: api + - value: "2" + label: site + - value: "3" + label: app + - value: "4" + label: global + type: + - value: "55" + label: 账号滥用 IP + bodyExample: + effective_scope: "2" + uuids: + - "" + name: 账号滥用 IP + type: "55" + pattern: + detection_cycle: 2 + src_ip_dis_cnt: 10 + business_tag: [] + rollback: + description: Delete the created risk strategy after creation. + command: chaitin-cli apisec raw filter-api delete --body '{"scope":"risk:detect:strategy","id__in":[""]}' --yes - path: [risk, config, update-strategy] operationId: RiskStrategyAPI_put short: Update risk discovery strategy @@ -210,6 +283,52 @@ commands: operationId: RiskStrategyAuxiliaryInfoAPI_get short: Get risk strategy auxiliary info + - path: [risk, strategy, list] + operationId: FilterAPI_get + short: List risk strategies + long: List risk discovery strategies through APISec FilterAPI. The default query sets scope=risk:detect:strategy. + query: + scope: risk:detect:strategy + examples: + - chaitin-cli apisec risk strategy list --query count=20 --query offset=0 --output json + - path: [risk, strategy, create-account-abuse-ip] + operationId: RiskStrategyAPI_post + short: Create account abuse IP risk strategy + long: Create an account abuse IP risk strategy. This command can be called with --body/--body-file or the dedicated flags documented below. + aliases: + - 账号滥用 IP + enumHints: + effective_scope: + - value: "1" + label: api + - value: "2" + label: site + - value: "3" + label: app + - value: "4" + label: global + type: + - value: "55" + label: 账号滥用 IP + bodyExample: + effective_scope: "2" + uuids: + - "" + name: 账号滥用 IP + type: "55" + pattern: + detection_cycle: 2 + src_ip_dis_cnt: 10 + business_tag: [] + rollback: + description: Delete the created account abuse IP risk strategy after creation. + command: chaitin-cli apisec raw filter-api delete --body '{"scope":"risk:detect:strategy","id__in":[""]}' --yes + examples: + - chaitin-cli apisec risk strategy create-account-abuse-ip --scope site --uuid s-xxx --src-ip-dis-cnt 10 --detection-cycle 2 + - path: [risk, strategy, update] + operationId: RiskStrategyAPI_put + short: Update risk strategy + - path: [risk, event, list] operationId: FilterAPI_get short: List risk event groups