wpmind/modules/api-gateway/includes/Pipeline/AuthMiddleware.php
LinuxJoy 36b69d300d security: API Gateway 全量代码审计修复 (59 issues)
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>
2026-02-09 01:14:12 +08:00

171 lines
No EOL
4 KiB
PHP

<?php
/**
* Auth Middleware
*
* Pipeline stage that authenticates API gateway requests.
*
* @package WPMind\Modules\ApiGateway\Pipeline
* @since 1.0.0
*/
declare(strict_types=1);
namespace WPMind\Modules\ApiGateway\Pipeline;
use WPMind\Modules\ApiGateway\Auth\ApiKeyManager;
/**
* Class AuthMiddleware
*
* Authenticates requests via Bearer API key (for API endpoints)
* or Cookie+Nonce / Application Password (for management endpoints).
*/
final class AuthMiddleware implements GatewayStageInterface {
/**
* Operation prefixes that require Bearer API key authentication.
*
* @var array<int, string>
*/
private const API_OPERATION_PREFIXES = [
'chat.',
'embeddings',
'responses',
'models',
'model_detail',
'status',
];
/**
* {@inheritDoc}
*/
public function process( GatewayRequestContext $context ): void {
if ( $this->is_api_operation( $context->operation() ) ) {
$this->authenticate_api_request( $context );
return;
}
$this->authenticate_management_request( $context );
}
/**
* Authenticate an API endpoint request via Bearer API key.
*
* @param GatewayRequestContext $context Request context.
*/
private function authenticate_api_request( GatewayRequestContext $context ): void {
$auth_header = $context->rest_request()->get_header( 'authorization' );
if ( empty( $auth_header ) ) {
$context->set_error(
new \WP_Error(
'missing_auth_header',
'Missing Authorization header.',
[ 'status' => 401 ]
)
);
return;
}
$client_ip = $this->get_client_ip();
$result = ApiKeyManager::authenticate_bearer_header( $auth_header, $client_ip );
if ( is_wp_error( $result ) ) {
$context->set_error( $result );
return;
}
$context->set_client_ip( $client_ip );
$context->set_auth_result( $result );
}
/**
* Authenticate a management endpoint request via Cookie+Nonce or Application Password.
*
* @param GatewayRequestContext $context Request context.
*/
private function authenticate_management_request( GatewayRequestContext $context ): void {
if ( ! is_user_logged_in() ) {
$context->set_error(
new \WP_Error(
'not_authenticated',
'Authentication required.',
[ 'status' => 401 ]
)
);
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
$context->set_error(
new \WP_Error(
'forbidden',
'Insufficient permissions.',
[ 'status' => 403 ]
)
);
}
}
/**
* Check if the operation is an API endpoint (requires Bearer key).
*
* @param string $operation Operation identifier.
* @return bool
*/
private function is_api_operation( string $operation ): bool {
foreach ( self::API_OPERATION_PREFIXES as $prefix ) {
if ( str_starts_with( $operation, $prefix ) ) {
return true;
}
}
return false;
}
/**
* Determine the client IP address from request headers.
*
* Only trusts proxy headers (X-Forwarded-For, X-Real-IP) when
* REMOTE_ADDR matches a configured trusted proxy. Otherwise
* falls back to REMOTE_ADDR to prevent IP spoofing.
*
* @return string Client IP address.
*/
private function get_client_ip(): string {
$remote_addr = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
if ( $remote_addr === '' ) {
return '127.0.0.1';
}
$trusted_proxies = (array) apply_filters( 'wpmind_gateway_trusted_proxies', [ '127.0.0.1', '::1' ] );
if ( in_array( $remote_addr, $trusted_proxies, true ) ) {
$proxy_headers = [ 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP' ];
foreach ( $proxy_headers as $header ) {
$value = sanitize_text_field( wp_unslash( $_SERVER[ $header ] ?? '' ) );
if ( $value === '' ) {
continue;
}
if ( $header === 'HTTP_X_FORWARDED_FOR' ) {
$parts = explode( ',', $value );
$value = trim( $parts[0] );
}
if ( filter_var( $value, FILTER_VALIDATE_IP ) !== false ) {
return $value;
}
}
}
if ( filter_var( $remote_addr, FILTER_VALIDATE_IP ) !== false ) {
return $remote_addr;
}
return '127.0.0.1';
}
}