From 47ff69ba6393bf325b465663e33fd45bc917ced0 Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Tue, 19 Dec 2023 18:20:14 +0100 Subject: [PATCH] Support GitHub-style (sha256 signature) git hooks --- .../ifi/access/controller/CourseController.kt | 12 ++++++--- .../uzh/ifi/access/service/CourseService.kt | 25 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/ch/uzh/ifi/access/controller/CourseController.kt b/src/main/kotlin/ch/uzh/ifi/access/controller/CourseController.kt index d224ca7..d7d4e98 100644 --- a/src/main/kotlin/ch/uzh/ifi/access/controller/CourseController.kt +++ b/src/main/kotlin/ch/uzh/ifi/access/controller/CourseController.kt @@ -45,10 +45,16 @@ class WebhooksController( private val logger = KotlinLogging.logger {} @PostMapping("/courses/{course}/update/gitlab") - fun updateCourse(@PathVariable("course") course: String, + fun hookGitlab(@PathVariable("course") course: String, @RequestHeader("X-Gitlab-Token") secret: String) { - logger.debug { "webhook triggered for $course"} - courseService.webhookUpdateCourse(course, secret) + logger.debug { "webhook (secret) triggered for $course"} + courseService.webhookUpdateWithSecret(course, secret) + } + @PostMapping("/courses/{course}/update/github") + fun hookGithub(@PathVariable("course") course: String, + @RequestHeader("X-Hub-Signature-256") signature: String) { + logger.debug { "webhook (signature) triggered for $course"} + courseService.webhookUpdateWithSignature(course, signature.substringAfter("sha256=")) } } diff --git a/src/main/kotlin/ch/uzh/ifi/access/service/CourseService.kt b/src/main/kotlin/ch/uzh/ifi/access/service/CourseService.kt index b28c7e1..637e111 100644 --- a/src/main/kotlin/ch/uzh/ifi/access/service/CourseService.kt +++ b/src/main/kotlin/ch/uzh/ifi/access/service/CourseService.kt @@ -35,6 +35,7 @@ import java.nio.charset.Charset import java.nio.file.Files import java.nio.file.NoSuchFileException import java.nio.file.Path +import java.security.MessageDigest import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.* @@ -43,6 +44,7 @@ import java.util.concurrent.TimeUnit import java.util.function.Consumer import java.util.stream.Collectors import java.util.stream.Stream +import kotlin.math.sign @Service class CourseServiceForCaching( @@ -554,10 +556,27 @@ fi } @Transactional - fun webhookUpdateCourse(courseSlug: String, secret: String): Course? { + fun webhookUpdateWithSecret(courseSlug: String, secret: String?): Course? { val existingCourse = getCourseBySlug(courseSlug) - if (existingCourse.webhookSecret != null && existingCourse.webhookSecret == secret) { - return updateCourse(courseSlug) + if (existingCourse.webhookSecret != null && secret != null) { + if (existingCourse.webhookSecret == secret) { + return updateCourse(courseSlug) + } + } + logger.debug { "Provided webhook secret does not match secret of course $courseSlug"} + throw ResponseStatusException(HttpStatus.FORBIDDEN) + } + + fun webhookUpdateWithSignature(courseSlug: String, signature: String?): Course? { + val existingCourse = getCourseBySlug(courseSlug) + if (existingCourse.webhookSecret != null && signature != null) { + val expected = MessageDigest.getInstance("SHA-256") + .digest(existingCourse.webhookSecret!!.toByteArray()) + .fold("") { str, it -> str + "%02x".format(it) } + logger.info{"Expected: ${expected} based on secret ${existingCourse.webhookSecret}, actual: ${signature}"} + if (expected == signature) { + return updateCourse(courseSlug) + } } logger.debug { "Provided webhook secret does not match secret of course $courseSlug"} throw ResponseStatusException(HttpStatus.FORBIDDEN)