chore: remove deprecated GitLab webhook code

GitLab webhook has been superseded by the GitHub webhook implementation.
Remove WebhookController (formerly GitLabWebhookController),
GitLabWebhookRequest DTO, and GitLabWebhookInterceptor.
This commit is contained in:
2026-06-01 15:37:06 +08:00
parent c30b5701e4
commit 8a9cf110af
3 changed files with 0 additions and 217 deletions
@@ -1,20 +0,0 @@
package com.onixbyte.deltaforceguide.controller;
import com.onixbyte.deltaforceguide.domain.dto.GitLabWebhookRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/webhooks")
public class WebhookController {
private static final Logger log = LoggerFactory.getLogger(WebhookController.class);
@PostMapping("/gitlab")
public void gitlabWebhook(
@RequestBody GitLabWebhookRequest request
) {
log.info("request={}", request);
}
}
@@ -1,45 +0,0 @@
package com.onixbyte.deltaforceguide.domain.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import java.time.OffsetDateTime;
import java.util.List;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonIgnoreProperties(ignoreUnknown = true)
public record GitLabWebhookRequest(
String objectKind,
String eventType,
GitLabWebhookObjectAttributes objectAttributes
) {
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonIgnoreProperties(ignoreUnknown = true)
public record GitLabWebhookLabel(
Long id,
String title,
@JsonProperty("color")
String colour,
Long projectId,
String createdAt,
String updatedAt,
Boolean template,
String description,
String type,
Long groupId
) {}
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonIgnoreProperties(ignoreUnknown = true)
public record GitLabWebhookObjectAttributes(
Long id,
String title,
String description,
List<GitLabWebhookLabel> labels
) {}
}
@@ -1,152 +0,0 @@
package com.onixbyte.deltaforceguide.interceptor;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.manager.WebhookManager;
import com.onixbyte.deltaforceguide.wrapper.RepeatedlyReadRequestWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
/**
* Verifies GitLab webhook requests by validating the {@code webhook-id},
* {@code webhook-timestamp}, and {@code webhook-signature} headers against
* a configured signing token using HMAC-SHA256.
*
* <p>Supports GitLab's v1 signature scheme. Verification is skipped when no
* signing token is configured.
*/
@Component
public class GitLabWebhookInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(GitLabWebhookInterceptor.class);
private static final String TOKEN_PREFIX = "whsec_";
private final WebhookManager webhookManager;
/**
* Creates a new interceptor with the given webhook configuration.
*
* @param webhookManager the webhook manager
*/
public GitLabWebhookInterceptor(WebhookManager webhookManager) {
this.webhookManager = webhookManager;
}
/**
* Validates the GitLab webhook signature headers on the incoming request.
* <p>
* Reads {@code webhook-id}, {@code webhook-timestamp}, and
* {@code webhook-signature} headers and verifies the signature against the
* configured signing token. If no token is configured, verification is skipped.
*
* @param request the incoming HTTP request (must be a {@link RepeatedlyReadRequestWrapper})
* @param response the HTTP response
* @param handler the chosen handler to execute
* @return {@code true} if the request is authentic
* @throws BizException with {@code 401} if headers are missing or the signature is invalid,
* with {@code 500} if verification fails unexpectedly
*/
@Override
public boolean preHandle(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler
) {
if (!(request instanceof RepeatedlyReadRequestWrapper req)) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Request body is not readable");
}
var webhookId = request.getHeader("webhook-id");
var webhookTimestamp = request.getHeader("webhook-timestamp");
var webhookSignature = request.getHeader("webhook-signature");
if (webhookId == null || webhookTimestamp == null || webhookSignature == null) {
log.warn("Missing webhook headers from ip={}", request.getRemoteAddr());
throw new BizException(HttpStatus.UNAUTHORIZED,
"Missing webhook verification headers");
}
var signingToken = webhookManager.getGitLabWebhookProperties().signingToken();
if (signingToken == null || signingToken.isBlank()) {
log.debug("No GitLab signing token configured, skipping signature verification");
return true;
}
var body = req.getBodyString();
var signedContent = "%s.%s.%s".formatted(webhookId, webhookTimestamp, body);
try {
var decodedKey = decodeSigningToken(signingToken);
var computedDigest = computeHmacSha256(decodedKey, signedContent);
var computedSignature = "v1,%s".formatted(computedDigest);
var signatures = webhookSignature.split(" ");
var matched = false;
for (var sig : signatures) {
if (MessageDigest.isEqual(computedSignature.getBytes(StandardCharsets.UTF_8),
sig.trim().getBytes(StandardCharsets.UTF_8))) {
matched = true;
break;
}
}
if (!matched) {
log.warn("Invalid webhook signature from ip={}", request.getRemoteAddr());
throw new BizException(HttpStatus.UNAUTHORIZED, "Invalid webhook signature");
}
} catch (BizException e) {
throw e;
} catch (Exception e) {
log.error("Failed to verify webhook signature", e);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Failed to verify webhook signature");
}
return true;
}
/**
* Decodes a GitLab-format signing token by stripping the {@code whsec_} prefix
* and Base64-decoding the remainder.
*
* @param token the prefixed signing token
* @return the raw key bytes
* @throws BizException if the token does not start with {@code whsec_}
*/
private byte[] decodeSigningToken(String token) {
if (!token.startsWith(TOKEN_PREFIX)) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Signing token must start with " + TOKEN_PREFIX);
}
var encoded = token.substring(TOKEN_PREFIX.length());
return Base64.getDecoder().decode(encoded);
}
/**
* Computes the Base64-encoded HMAC-SHA256 digest for the given data.
*
* @param key the secret key bytes
* @param data the content to sign
* @return Base64-encoded HMAC-SHA256 digest
* @throws Exception if the HMAC algorithm is unavailable
*/
private String computeHmacSha256(byte[] key, String data) throws Exception {
var mac = Mac.getInstance("HmacSHA256");
var secretKey = new SecretKeySpec(key, "HmacSHA256");
mac.init(secretKey);
var hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
}
}