feat: 支持 HMAC-SHA256 签名校验并优化测试用例
这个提交包含在:
@@ -20,9 +20,8 @@ class WebhookHandler
|
||||
|
||||
public function handle(array $server, array $get, string $rawBody): array
|
||||
{
|
||||
// 1. 验证 Token (支持 Header 与 URL 参数)
|
||||
$token = $server['HTTP_X_GITEA_TOKEN'] ?? $server['HTTP_X_GIT_TOKEN'] ?? $server['HTTP_X_GITLAB_TOKEN'] ?? ($get['token'] ?? '');
|
||||
if ($token === '' || !hash_equals($this->secret, $token)) {
|
||||
// 1. 验证权限 (支持 Header 明文 Token、URL Token 以及 HMAC-SHA256 签名)
|
||||
if (!$this->verifyAuth($server, $get, $rawBody)) {
|
||||
return ['code' => 403, 'data' => ['status' => 'error', 'message' => 'Invalid token']];
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ class WebhookHandler
|
||||
}
|
||||
|
||||
// 3. 处理 Ping / Test 事件
|
||||
$event = strtolower($server['HTTP_X_GITEA_EVENT'] ?? $server['HTTP_X_GIT_EVENT'] ?? '');
|
||||
$event = strtolower($server['HTTP_X_GITEA_EVENT'] ?? $server['HTTP_X_GIT_EVENT'] ?? $server['HTTP_X_GITHUB_EVENT'] ?? '');
|
||||
if ($event === 'ping') {
|
||||
return ['code' => 200, 'data' => ['status' => 'success', 'message' => 'pong']];
|
||||
}
|
||||
@@ -69,4 +68,27 @@ class WebhookHandler
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function verifyAuth(array $server, array $get, string $rawBody): bool
|
||||
{
|
||||
// A. 明文 Token 校验
|
||||
$token = $server['HTTP_X_GITEA_TOKEN'] ?? $server['HTTP_X_GIT_TOKEN'] ?? $server['HTTP_X_GITLAB_TOKEN'] ?? ($get['token'] ?? '');
|
||||
if ($token !== '' && hash_equals($this->secret, $token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// B. HMAC-SHA256 签名校验 (Gitea / GitHub)
|
||||
$signature = $server['HTTP_X_GITEA_SIGNATURE'] ?? $server['HTTP_X_HUB_SIGNATURE_256'] ?? '';
|
||||
if ($signature !== '') {
|
||||
if (strpos($signature, 'sha256=') === 0) {
|
||||
$signature = substr($signature, 7);
|
||||
}
|
||||
$expected = hash_hmac('sha256', $rawBody, $this->secret);
|
||||
if (hash_equals($expected, $signature)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ class WebhookTest
|
||||
$this->testInvalidTokenRejection();
|
||||
$this->testUrlTokenAllowed();
|
||||
$this->testHeaderTokenAllowed();
|
||||
$this->testHmacSha256SignatureAllowed();
|
||||
$this->testHubSignature256Allowed();
|
||||
$this->testNonPostMethodRejection();
|
||||
$this->testPingEventSuccess();
|
||||
$this->testIgnoredBranchSkipped();
|
||||
@@ -75,6 +77,34 @@ class WebhookTest
|
||||
$this->assert($res['code'] === 200, "合法 Header Token 应返回 HTTP 200");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试通过 Gitea HMAC-SHA256 签名校验
|
||||
*/
|
||||
public function testHmacSha256SignatureAllowed()
|
||||
{
|
||||
$handler = new \app\common\library\WebhookHandler('test_secret_123');
|
||||
$body = json_encode(['ref' => 'refs/heads/master']);
|
||||
$sig = hash_hmac('sha256', $body, 'test_secret_123');
|
||||
$server = ['REQUEST_METHOD' => 'POST', 'HTTP_X_GITEA_SIGNATURE' => $sig];
|
||||
$res = $handler->handle($server, [], $body);
|
||||
|
||||
$this->assert($res['code'] === 200, "合法 HMAC-SHA256 签名应返回 HTTP 200");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试通过 GitHub/Gitea sha256= 前缀签名校验
|
||||
*/
|
||||
public function testHubSignature256Allowed()
|
||||
{
|
||||
$handler = new \app\common\library\WebhookHandler('test_secret_123');
|
||||
$body = json_encode(['ref' => 'refs/heads/master']);
|
||||
$sig = 'sha256=' . hash_hmac('sha256', $body, 'test_secret_123');
|
||||
$server = ['REQUEST_METHOD' => 'POST', 'HTTP_X_HUB_SIGNATURE_256' => $sig];
|
||||
$res = $handler->handle($server, [], $body);
|
||||
|
||||
$this->assert($res['code'] === 200, "合法 X-Hub-Signature-256 签名应返回 HTTP 200");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试非 POST 请求被拒绝
|
||||
*/
|
||||
|
||||
在新工单中引用
屏蔽一个用户