Skip to content

Commit 47ff69b

Browse files
committed
Support GitHub-style (sha256 signature) git hooks
1 parent 150d0cb commit 47ff69b

File tree

2 files changed

+31
-6
lines changed

2 files changed

+31
-6
lines changed

src/main/kotlin/ch/uzh/ifi/access/controller/CourseController.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,16 @@ class WebhooksController(
4545
private val logger = KotlinLogging.logger {}
4646

4747
@PostMapping("/courses/{course}/update/gitlab")
48-
fun updateCourse(@PathVariable("course") course: String,
48+
fun hookGitlab(@PathVariable("course") course: String,
4949
@RequestHeader("X-Gitlab-Token") secret: String) {
50-
logger.debug { "webhook triggered for $course"}
51-
courseService.webhookUpdateCourse(course, secret)
50+
logger.debug { "webhook (secret) triggered for $course"}
51+
courseService.webhookUpdateWithSecret(course, secret)
52+
}
53+
@PostMapping("/courses/{course}/update/github")
54+
fun hookGithub(@PathVariable("course") course: String,
55+
@RequestHeader("X-Hub-Signature-256") signature: String) {
56+
logger.debug { "webhook (signature) triggered for $course"}
57+
courseService.webhookUpdateWithSignature(course, signature.substringAfter("sha256="))
5258
}
5359

5460
}

src/main/kotlin/ch/uzh/ifi/access/service/CourseService.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import java.nio.charset.Charset
3535
import java.nio.file.Files
3636
import java.nio.file.NoSuchFileException
3737
import java.nio.file.Path
38+
import java.security.MessageDigest
3839
import java.time.LocalDateTime
3940
import java.time.format.DateTimeFormatter
4041
import java.util.*
@@ -43,6 +44,7 @@ import java.util.concurrent.TimeUnit
4344
import java.util.function.Consumer
4445
import java.util.stream.Collectors
4546
import java.util.stream.Stream
47+
import kotlin.math.sign
4648

4749
@Service
4850
class CourseServiceForCaching(
@@ -554,10 +556,27 @@ fi
554556
}
555557

556558
@Transactional
557-
fun webhookUpdateCourse(courseSlug: String, secret: String): Course? {
559+
fun webhookUpdateWithSecret(courseSlug: String, secret: String?): Course? {
558560
val existingCourse = getCourseBySlug(courseSlug)
559-
if (existingCourse.webhookSecret != null && existingCourse.webhookSecret == secret) {
560-
return updateCourse(courseSlug)
561+
if (existingCourse.webhookSecret != null && secret != null) {
562+
if (existingCourse.webhookSecret == secret) {
563+
return updateCourse(courseSlug)
564+
}
565+
}
566+
logger.debug { "Provided webhook secret does not match secret of course $courseSlug"}
567+
throw ResponseStatusException(HttpStatus.FORBIDDEN)
568+
}
569+
570+
fun webhookUpdateWithSignature(courseSlug: String, signature: String?): Course? {
571+
val existingCourse = getCourseBySlug(courseSlug)
572+
if (existingCourse.webhookSecret != null && signature != null) {
573+
val expected = MessageDigest.getInstance("SHA-256")
574+
.digest(existingCourse.webhookSecret!!.toByteArray())
575+
.fold("") { str, it -> str + "%02x".format(it) }
576+
logger.info{"Expected: ${expected} based on secret ${existingCourse.webhookSecret}, actual: ${signature}"}
577+
if (expected == signature) {
578+
return updateCourse(courseSlug)
579+
}
561580
}
562581
logger.debug { "Provided webhook secret does not match secret of course $courseSlug"}
563582
throw ResponseStatusException(HttpStatus.FORBIDDEN)

0 commit comments

Comments
 (0)