feat(webhook): GitLab + Gitea/Forgejo adapters#71
Open
russellromney wants to merge 3 commits into
Open
Conversation
Adds the two remaining phase-1 webhook adapters behind the existing
`WebhookProvider` trait — no engine or handler changes. `/webhooks/{provider}`
now serves GitHub, GitLab, and Gitea/Forgejo.
- GitLab (`rust/src/webhook/gitlab.rs`): authenticates with the shared token in
`X-Gitlab-Token` (constant-time equality, not a body HMAC); acts on
`X-Gitlab-Event: Push Hook`; routes by `project.path_with_namespace`
(subgroup-safe); visibility from `project.visibility_level` (<20 ⇒ private).
- Gitea/Forgejo (`rust/src/webhook/gitea.rs`): bare-hex HMAC-SHA256 in
`X-Gitea-Signature` (no `sha256=` prefix); push / delete / ping. Its dedicated
`delete` event carries a short branch name, normalized back to
`refs/heads/<branch>` so the handler stays uniform.
- Shared `is_zero_sha` helper lifted into `webhook/mod.rs`; `provider_for` now
returns adapters for GitHub/GitLab/Gitea (Bitbucket/Generic still None → 501).
- Tests: per-adapter verify (valid/wrong/missing/tampered) + parse
(push/delete/ping/non-event); server-level gitlab push enqueues, gitlab bad
token → 401, gitea push enqueues, gitea branch-delete cleans up the ref,
provider-without-adapter → 501.
- Docs: README + WEBHOOKS.md updated (all three providers supported; adapter
quirks documented).
fmt + clippy -D warnings + full release suite green (lib 228 passed, 53 test
binaries, 0 failures).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sts + docs) From two independent adversarial reviews of the GitLab/Gitea adapters (protocol-accuracy review confirmed zero format defects vs the real docs): - Allowlist now matches a **natural key** (`RepoId::natural_key()`): `owner/repo` for GitHub, provider-prefixed unescaped for others (`gitlab/group/sub/proj`) — previously it matched the slash-*escaped* storage key, so the documented `owner/repo` form silently never matched GitLab/Gitea repos (medium footgun: operators would fall back to allow-all). - Reject all-whitespace webhook secrets in `parse_secret` (a blank GitLab token is a guessable credential); secret kept verbatim otherwise. - `validate_repo_path`: reject `..` path segments and ASCII control chars for non-github providers (defense in depth; escaping already blocked traversal). - Clarify `CanonicalEvent.private` is informational (the warm path resolves credentials from the broker regardless of visibility). - Tests: gitea bad-signature → 401, gitlab branch-delete-through-handler cleanup, gitlab allowlist via natural key, `natural_key()` unit test, `validate_repo_path` traversal/control rejection. - Docs: allowlist natural-key format; per-provider setup notes (GitLab requires the secret-token scheme not signing-token; Gitea webhooks must enable the Delete event for branch-delete cleanup). fmt + clippy -D warnings + full release suite green (lib 233 passed, 53 test binaries, 0 failures). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e controls) Third-round adversarial review (no regressions found) left three low-severity debts; closing them so nothing's left half-done: - Allowlist naming/docs: `WebhookConfig::allows` param renamed `storage_key` → `repo_key`; the `with_allowlist`/`allows`/`parse_allowlist` docs now say "natural key" (they receive the unescaped form, not the storage key). - GitHub allowlist asymmetry: the github default now ALSO accepts the explicit `github/owner/repo` form (not just bare `owner/repo`), so an operator generalizing from the `gitlab/...` examples isn't silently bitten. New helper `webhook_repo_allowed` + test exercising both forms. - `validate_repo_path`: reject ALL Unicode control chars (`char::is_control`), not just ASCII — catches the C1 range (e.g. U+0085 NEL). fmt + clippy -D warnings + full release suite green (lib 234 passed, 53 test binaries, 0 failures). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the two remaining phase-1 webhook adapters behind the existing
WebhookProvidertrait — no engine or handler changes./webhooks/{provider}now serves GitHub, GitLab, and Gitea/Forgejo.Adding a provider is exactly what the design promised: implement one trait (
verify+parse) and add a match arm. Everything downstream —CanonicalEvent, the allowlist, branch policy, delete cleanup,trigger_buildcoalescing — is already generic.Adapters
rust/src/webhook/gitlab.rs) — authenticates with the shared token echoed inX-Gitlab-Token(constant-time equality; GitLab uses no body HMAC, so the raw body is unused there). Acts onX-Gitlab-Event: Push Hook. Routes byproject.path_with_namespace(subgroup-safe). Visibility fromproject.visibility_level(< 20⇒ non-public).rust/src/webhook/gitea.rs) — bare-hex HMAC-SHA256 inX-Gitea-Signature(nosha256=prefix). Handlespush/delete/ping. Gitea's dedicateddeleteevent carries a short branch name, which the adapter normalizes back torefs/heads/<branch>so the handler stays uniform. Covers Codeberg too.Shared
is_zero_shalifted intowebhook/mod.rs;provider_forreturns adapters for GitHub/GitLab/Gitea — Bitbucket/Generic stillNone→501.Tests
Per-adapter unit tests (verify valid/wrong/missing/tampered; parse push/delete/ping/non-event, subgroup paths, visibility, short-ref normalization) plus server-level integration: gitlab push enqueues, gitlab bad token → 401, gitea push enqueues, gitea branch-delete cleans up the ref, provider-without-adapter → 501.
cargo fmt --check+cargo clippy --all-targets -- -D warnings+cargo test --release --all-targets --lockedall green (lib 228 passed, 53 test binaries, 0 failures).Follow-ups
A Bitbucket adapter (same pattern); repo-lifecycle events and tag/release pre-warm.
🤖 Generated with Claude Code