| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- <?php
- /** Real local HTTP integration, using an unprivileged temporary fixture user. */
- require dirname(__DIR__) . '/vendor/autoload.php';
- \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
- \support\App::loadAllConfig(['route']);
- use app\miniapi\model\MiniUser;
- use app\miniapi\model\MiniSession;
- use app\miniapi\service\AuthService;
- use support\Redis;
- if (config('mini_api.environment') !== 'local' || !config('mini_protection.enabled')) {
- throw new RuntimeException('Run only against the local enabled protection service');
- }
- $checks = 0;
- function check(bool $ok, string $label): void
- {
- global $checks;
- $checks++;
- if (!$ok) {
- throw new RuntimeException($label);
- }
- }
- function http(string $path, string $token, array $headers = []): array
- {
- $responseHeaders = [];
- $curl = curl_init('http://127.0.0.1:8787/api/mini/v1' . $path);
- curl_setopt_array($curl, [
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_TIMEOUT => 5,
- CURLOPT_HTTPHEADER => array_merge(['Authorization: Bearer ' . $token, 'Origin: http://127.0.0.1:8787'], $headers),
- CURLOPT_HEADERFUNCTION => static function ($handle, $line) use (&$responseHeaders) {
- if (str_contains($line, ':')) {
- [$name, $value] = explode(':', $line, 2);
- $responseHeaders[strtolower($name)] = trim($value);
- }
- return strlen($line);
- },
- ]);
- $raw = curl_exec($curl);
- $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
- curl_close($curl);
- return [$status, json_decode((string) $raw, true), $responseHeaders];
- }
- $auth = new AuthService();
- $identity = ['appid' => 'protection-http-test', 'openid' => bin2hex(random_bytes(16))];
- $one = $auth->issue($identity);
- $two = $auth->issue($identity);
- $redis = Redis::connection(config('mini_protection.connection'));
- $accountHash = hash('sha256', $one['user_id']);
- $endpointKey = config('mini_protection.prefix') . 'account_endpoint:' . $accountHash . ':' . hash('sha256', 'GET /api/mini/v1/session');
- try {
- check($one['identities'] === [], 'Fixture has no business permissions');
- [$status] = http('/me', $one['access_token']);
- check($status === 200, 'Normal authenticated read works');
- $limit = config('mini_protection.limits.account_endpoint')[0];
- // Arrange only this disposable account near its endpoint threshold; avoid load bursts.
- $redis->setex($endpointKey, 30, $limit - 1);
- [$status] = http('/session', $one['access_token']);
- check($status === 200, 'Last allowed request succeeds');
- [$status, $body, $headers] = http('/session?keyword=changed', $two['access_token']);
- check($status === 429 && $body['code'] === 42902, 'New token and query cannot evade account endpoint quota');
- check((int) ($headers['retry-after'] ?? 0) > 0, 'HTTP Retry-After returned');
- check(str_contains($headers['access-control-expose-headers'] ?? '', 'Retry-After'), 'H5 can read retry header');
- check(isset($body['request_id']) && !str_contains(json_encode($body), $two['access_token']), 'Correlated response contains no token');
- $redis->expire($endpointKey, 0);
- [$status] = http('/session', $two['access_token']);
- check($status === 200, 'Request recovers after quota expiry');
- [$status, $body] = http('/packages?page=101&page_size=100', $one['access_token']);
- check($status === 422 && $body['code'] === 42201, 'Deep pagination rejected over HTTP');
- [$status] = http('/enterprises/49/home', $one['access_token']);
- check($status === 403, 'Existing resource permissions remain enforced');
- $auth->logout($two['access_token']);
- [$status] = http('/session', $two['access_token']);
- check($status === 401, 'Revoked session stays invalid');
- } finally {
- // Only exact keys for this generated fixture account may be removed.
- foreach (['GET /api/mini/v1/session', 'GET /api/mini/v1/me', 'GET /api/mini/v1/enterprises/{id}/home'] as $endpoint) {
- $key = config('mini_protection.prefix') . 'account_endpoint:' . $accountHash . ':' . hash('sha256', $endpoint);
- $redis->del($key, $key . ':notice');
- }
- foreach (['account_requests', 'account_list_rows'] as $rule) {
- $key = config('mini_protection.prefix') . $rule . ':' . $accountHash;
- $redis->del($key, $key . ':notice');
- }
- (new MiniUser())->getConnection()->transaction(function () use ($one, $identity) {
- MiniSession::query()->where('user_id', $one['user_id'])->delete();
- MiniUser::query()->where('id', $one['user_id'])->where($identity)->delete();
- });
- }
- echo "PASS: $checks real HTTP protection checks; temporary account removed.\n";
|