diff --git a/application/common/library/WebhookHandler.php b/application/common/library/WebhookHandler.php index 5dbc225..9e4d8a0 100644 --- a/application/common/library/WebhookHandler.php +++ b/application/common/library/WebhookHandler.php @@ -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; + } } diff --git a/tests/WebhookTest.php b/tests/WebhookTest.php index 8736161..7fbeb4f 100644 --- a/tests/WebhookTest.php +++ b/tests/WebhookTest.php @@ -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 请求被拒绝 */