Codex 审计发现 124 个问题,修复 59 个 P0-P2 级别问题: P0 (CRITICAL): - TOCTOU 竞态: TransientRateStore/SseConcurrencyGuard 改用 wp_cache_add 原子锁 - REST 路由: __return_true → check_bearer_present 轻量鉴权 - Schema 验证: messages/tools/tool_choice 添加完整 validate_callback - SQL 列名不匹配: upsert 查询对齐 SchemaManager 定义 - SQL 表名插值: 统一使用 %i 占位符 P1 (HIGH): - IP hash 加盐 (HMAC) + IP 解析统一 (context 共享) - Header 注入防护 (X-Request-Id/响应头 \r\n 过滤) - 数值参数范围校验 (temperature/top_p/penalties/max_tokens) - insert_key 错误处理 + revoke_key 审计日志 - Pipeline finally 保护 + RateLimiter rollback 双 store - wp_unslash 补全 + strtotime 返回值检查 - RedisRateStore $rid 冒号验证 - UpstreamStreamClient catch \Throwable P2 (MEDIUM): - AuditLogRepository DRY (提取 build_where_clause) - Token 估算 CJK 优化 (mb_strlen) - ResponseTransformMiddleware null 检查 - 嵌入响应 usage 数据修复 - SchemaManager autoload 优化 25 files changed, ~530 insertions, ~190 deletions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
226 lines
6.1 KiB
PHP
226 lines
6.1 KiB
PHP
<?php
|
|
/**
|
|
* REST Controller
|
|
*
|
|
* Registers all REST API routes for the API Gateway and delegates
|
|
* request handling to the middleware pipeline.
|
|
*
|
|
* @package WPMind\Modules\ApiGateway
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace WPMind\Modules\ApiGateway;
|
|
|
|
use WPMind\Modules\ApiGateway\Pipeline\GatewayPipeline;
|
|
use WPMind\Modules\ApiGateway\Pipeline\AuthMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\BudgetMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\QuotaMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\RequestTransformMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\RouteMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\ResponseTransformMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\ErrorMiddleware;
|
|
use WPMind\Modules\ApiGateway\Pipeline\LogMiddleware;
|
|
|
|
/**
|
|
* Class RestController
|
|
*
|
|
* Registers OpenAI-compatible REST endpoints under the mind/v1 namespace
|
|
* and routes each request through the 8-stage gateway pipeline.
|
|
*/
|
|
final class RestController {
|
|
|
|
/**
|
|
* REST API namespace.
|
|
*
|
|
* @var string
|
|
*/
|
|
private const NAMESPACE = 'mind/v1';
|
|
|
|
/**
|
|
* Lazy-initialized gateway pipeline instance.
|
|
*
|
|
* @var GatewayPipeline|null
|
|
*/
|
|
private ?GatewayPipeline $pipeline = null;
|
|
|
|
/**
|
|
* Register all REST API routes for the gateway.
|
|
*/
|
|
public function register_routes(): void {
|
|
// POST /wp-json/mind/v1/chat/completions
|
|
register_rest_route(
|
|
self::NAMESPACE,
|
|
'/chat/completions',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'handle_chat_completions' ],
|
|
'permission_callback' => [ $this, 'check_bearer_present' ],
|
|
'args' => GatewayRequestSchema::chat_completions(),
|
|
]
|
|
);
|
|
|
|
// POST /wp-json/mind/v1/embeddings
|
|
register_rest_route(
|
|
self::NAMESPACE,
|
|
'/embeddings',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'handle_embeddings' ],
|
|
'permission_callback' => [ $this, 'check_bearer_present' ],
|
|
'args' => GatewayRequestSchema::embeddings(),
|
|
]
|
|
);
|
|
|
|
// POST /wp-json/mind/v1/responses
|
|
register_rest_route(
|
|
self::NAMESPACE,
|
|
'/responses',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [ $this, 'handle_responses' ],
|
|
'permission_callback' => [ $this, 'check_bearer_present' ],
|
|
'args' => GatewayRequestSchema::responses(),
|
|
]
|
|
);
|
|
|
|
// GET /wp-json/mind/v1/models
|
|
register_rest_route(
|
|
self::NAMESPACE,
|
|
'/models',
|
|
[
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'handle_models' ],
|
|
'permission_callback' => [ $this, 'check_bearer_present' ],
|
|
'args' => GatewayRequestSchema::models(),
|
|
]
|
|
);
|
|
|
|
// GET /wp-json/mind/v1/models/<model_id>
|
|
register_rest_route(
|
|
self::NAMESPACE,
|
|
'/models/(?P<model_id>[a-zA-Z0-9_.-]+)',
|
|
[
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'handle_model_detail' ],
|
|
'permission_callback' => [ $this, 'check_bearer_present' ],
|
|
'args' => [
|
|
'model_id' => [
|
|
'type' => 'string',
|
|
'required' => true,
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
// GET /wp-json/mind/v1/status
|
|
register_rest_route(
|
|
self::NAMESPACE,
|
|
'/status',
|
|
[
|
|
'methods' => 'GET',
|
|
'callback' => [ $this, 'handle_status' ],
|
|
'permission_callback' => [ $this, 'check_bearer_present' ],
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle POST /chat/completions requests.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_chat_completions( \WP_REST_Request $request ): \WP_REST_Response {
|
|
return $this->get_pipeline()->handle( 'chat.completions', $request );
|
|
}
|
|
|
|
/**
|
|
* Handle POST /embeddings requests.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_embeddings( \WP_REST_Request $request ): \WP_REST_Response {
|
|
return $this->get_pipeline()->handle( 'embeddings', $request );
|
|
}
|
|
|
|
/**
|
|
* Handle POST /responses requests.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_responses( \WP_REST_Request $request ): \WP_REST_Response {
|
|
return $this->get_pipeline()->handle( 'responses', $request );
|
|
}
|
|
|
|
/**
|
|
* Handle GET /models requests.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_models( \WP_REST_Request $request ): \WP_REST_Response {
|
|
return $this->get_pipeline()->handle( 'models', $request );
|
|
}
|
|
|
|
/**
|
|
* Handle GET /models/<model_id> requests.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_model_detail( \WP_REST_Request $request ): \WP_REST_Response {
|
|
return $this->get_pipeline()->handle( 'model_detail', $request );
|
|
}
|
|
|
|
/**
|
|
* Handle GET /status requests.
|
|
*
|
|
* Goes through the full pipeline so auth, error, and log stages apply.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_status( \WP_REST_Request $request ): \WP_REST_Response {
|
|
return $this->get_pipeline()->handle( 'status', $request );
|
|
}
|
|
|
|
/**
|
|
* Check that a Bearer token is present in the Authorization header.
|
|
*
|
|
* @param \WP_REST_Request $request REST request.
|
|
* @return bool
|
|
*/
|
|
public function check_bearer_present( \WP_REST_Request $request ): bool {
|
|
$auth = $request->get_header( 'authorization' );
|
|
return ! empty( $auth ) && str_starts_with( strtolower( $auth ), 'bearer ' );
|
|
}
|
|
|
|
/**
|
|
* Lazy-initialize and return the gateway pipeline.
|
|
*
|
|
* Creates the pipeline with all 8 middleware stages on first call.
|
|
*
|
|
* @return GatewayPipeline
|
|
*/
|
|
private function get_pipeline(): GatewayPipeline {
|
|
if ( ! isset( $this->pipeline ) ) {
|
|
$this->pipeline = new GatewayPipeline(
|
|
new AuthMiddleware(),
|
|
new BudgetMiddleware(),
|
|
new QuotaMiddleware(),
|
|
new RequestTransformMiddleware(),
|
|
new RouteMiddleware(),
|
|
new ResponseTransformMiddleware(),
|
|
new ErrorMiddleware(),
|
|
new LogMiddleware()
|
|
);
|
|
}
|
|
|
|
return $this->pipeline;
|
|
}
|
|
}
|