93 lines
3.6 KiB
PHP
93 lines
3.6 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
use App\Jobs\RouteSupplierLeadJob;
|
|||
|
|
use App\Models\SupplierLead;
|
|||
|
|
use App\Models\SystemSetting;
|
|||
|
|
use App\Support\WebhookUrlGuard;
|
|||
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
|
|
use Illuminate\Support\Facades\Bus;
|
|||
|
|
|
|||
|
|
uses(DatabaseTransactions::class);
|
|||
|
|
|
|||
|
|
beforeEach(function () {
|
|||
|
|
SystemSetting::query()->where('key', 'supplier_webhook_secret')
|
|||
|
|
->update(['value' => 'test-secret-32chars-aaaaaaaaaaaaaa']);
|
|||
|
|
SystemSetting::query()->where('key', 'supplier_ip_allowlist')->update(['value' => '[]']);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// --- Часть А: DNS-rebind пиннинг (юнит на WebhookUrlGuard::safeDeliveryIp) ---
|
|||
|
|
|
|||
|
|
test('safeDeliveryIp блокирует приватный/служебный адрес и не отдаёт ip для пиннинга', function () {
|
|||
|
|
foreach ([
|
|||
|
|
'https://10.0.0.1/hook',
|
|||
|
|
'https://169.254.169.254/hook',
|
|||
|
|
'https://127.0.0.1/hook',
|
|||
|
|
'https://192.168.1.1/hook',
|
|||
|
|
] as $url) {
|
|||
|
|
$result = WebhookUrlGuard::safeDeliveryIp($url);
|
|||
|
|
expect($result['blockReason'])->not->toBeNull()
|
|||
|
|
->and($result['ip'])->toBeNull();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('safeDeliveryIp пропускает публичный IP и отдаёт его для пиннинга', function () {
|
|||
|
|
$result = WebhookUrlGuard::safeDeliveryIp('https://1.1.1.1/hook');
|
|||
|
|
|
|||
|
|
expect($result['blockReason'])->toBeNull()
|
|||
|
|
->and($result['ip'])->toBe('1.1.1.1');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// --- Часть Б: аддитивный HMAC для supplier-webhook ---
|
|||
|
|
|
|||
|
|
test('secretless /api/webhook/supplier принимает валидную HMAC-подпись → 202', function () {
|
|||
|
|
Bus::fake();
|
|||
|
|
$secret = 'test-secret-32chars-aaaaaaaaaaaaaa';
|
|||
|
|
$body = json_encode(['vid' => 55501, 'project' => 'B1_hmac.ru', 'phone' => '79991234567', 'time' => time()]);
|
|||
|
|
$sig = hash_hmac('sha256', $body, $secret);
|
|||
|
|
|
|||
|
|
$response = $this->call('POST', '/api/webhook/supplier', [], [], [], [
|
|||
|
|
'CONTENT_TYPE' => 'application/json',
|
|||
|
|
'HTTP_ACCEPT' => 'application/json',
|
|||
|
|
'HTTP_X_WEBHOOK_SIGNATURE' => $sig,
|
|||
|
|
], $body);
|
|||
|
|
|
|||
|
|
expect($response->getStatusCode())->toBe(202);
|
|||
|
|
expect(SupplierLead::where('vid', 55501)->exists())->toBeTrue();
|
|||
|
|
Bus::assertDispatched(RouteSupplierLeadJob::class);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('secretless /api/webhook/supplier без подписи → 404', function () {
|
|||
|
|
$body = json_encode(['vid' => 55502, 'project' => 'B1_hmac.ru', 'phone' => '79991234567', 'time' => time()]);
|
|||
|
|
|
|||
|
|
$response = $this->call('POST', '/api/webhook/supplier', [], [], [], [
|
|||
|
|
'CONTENT_TYPE' => 'application/json',
|
|||
|
|
'HTTP_ACCEPT' => 'application/json',
|
|||
|
|
], $body);
|
|||
|
|
|
|||
|
|
expect($response->getStatusCode())->toBe(404);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('secretless /api/webhook/supplier с неверной подписью → 404', function () {
|
|||
|
|
$body = json_encode(['vid' => 55503, 'project' => 'B1_hmac.ru', 'phone' => '79991234567', 'time' => time()]);
|
|||
|
|
|
|||
|
|
$response = $this->call('POST', '/api/webhook/supplier', [], [], [], [
|
|||
|
|
'CONTENT_TYPE' => 'application/json',
|
|||
|
|
'HTTP_ACCEPT' => 'application/json',
|
|||
|
|
'HTTP_X_WEBHOOK_SIGNATURE' => 'deadbeefdeadbeefdeadbeefdeadbeef',
|
|||
|
|
], $body);
|
|||
|
|
|
|||
|
|
expect($response->getStatusCode())->toBe(404);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('существующий {secret}-маршрут продолжает принимать по URL-секрету → 202', function () {
|
|||
|
|
Bus::fake();
|
|||
|
|
|
|||
|
|
$response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [
|
|||
|
|
'vid' => 55504, 'project' => 'B1_hmac.ru', 'phone' => '79991234567', 'time' => time(),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
expect($response->getStatusCode())->toBe(202);
|
|||
|
|
});
|