From 526354df1fbe808088ba733bbbfb34f3d30cd7fe Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:12:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20InboxController=E3=81=A7HTTP=20Signatur?= =?UTF-8?q?e=E3=81=AB=E9=96=A2=E9=80=A3=E3=81=99=E3=82=8BHTTP=E3=83=98?= =?UTF-8?q?=E3=83=83=E3=83=80=E3=83=BC=E3=81=AE=E6=A4=9C=E6=9F=BB=E3=82=92?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interfaces/api/inbox/InboxController.kt | 4 +- .../api/inbox/InboxControllerImpl.kt | 71 +++++++++++++------ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt index 58dad2e5..2cbbdbec 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt @@ -16,8 +16,8 @@ package dev.usbharu.hideout.activitypub.interfaces.api.inbox +import jakarta.servlet.http.HttpServletRequest import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.bind.annotation.RestController @@ -34,5 +34,5 @@ interface InboxController { consumes = ["application/json", "application/*+json"], method = [RequestMethod.POST] ) - suspend fun inbox(@RequestBody string: String): ResponseEntity + suspend fun inbox(httpServletRequest: HttpServletRequest): ResponseEntity } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt index 3f7c0c40..19bad740 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt @@ -17,31 +17,64 @@ package dev.usbharu.hideout.activitypub.interfaces.api.inbox import dev.usbharu.hideout.activitypub.service.common.APService +import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureHeaderChecker import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest +import jakarta.servlet.http.HttpServletRequest +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.slf4j.MDCContext +import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import org.springframework.http.HttpHeaders.WWW_AUTHENTICATE import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController -import org.springframework.web.context.request.RequestContextHolder -import org.springframework.web.context.request.ServletRequestAttributes import java.net.URL @RestController -class InboxControllerImpl(private val apService: APService) : InboxController { +class InboxControllerImpl( + private val apService: APService, + private val httpSignatureHeaderChecker: HttpSignatureHeaderChecker, +) : InboxController { @Suppress("TooGenericExceptionCaught") override suspend fun inbox( - @RequestBody string: String - ): ResponseEntity { - val request = (requireNotNull(RequestContextHolder.getRequestAttributes()) as ServletRequestAttributes).request + httpServletRequest: HttpServletRequest, + ): ResponseEntity { - val headersList = request.headerNames?.toList().orEmpty() + val headersList = httpServletRequest.headerNames?.toList().orEmpty() LOGGER.trace("Inbox Headers {}", headersList) - if (headersList.map { it.lowercase() }.contains("signature").not()) { + val body = withContext(Dispatchers.IO + MDCContext()) { + httpServletRequest.inputStream.readAllBytes()!! + } + + try { + httpSignatureHeaderChecker.checkDate(httpServletRequest.getHeader("date")!!) + } catch (e: NullPointerException) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Required date header") + } catch (e: IllegalArgumentException) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Request is too old.") + } + try { + httpSignatureHeaderChecker.checkHost(httpServletRequest.getHeader("host")!!) + } catch (e: NullPointerException) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Required host header") + } catch (e: IllegalArgumentException) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Wrong host for request") + } + try { + httpSignatureHeaderChecker.checkDigest(body, httpServletRequest.getHeader("digest")!!) + } catch (e: NullPointerException) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body("Required request body digest in digest header (sha256)") + } catch (e: IllegalArgumentException) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body("Wrong digest for request") + } + + if (httpServletRequest.getHeader("signature").orEmpty().isBlank()) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .header( WWW_AUTHENTICATE, @@ -51,33 +84,27 @@ class InboxControllerImpl(private val apService: APService) : InboxController { } val parseActivity = try { - apService.parseActivity(string) + apService.parseActivity(body.decodeToString()) } catch (e: Exception) { LOGGER.warn("FAILED Parse Activity", e) return ResponseEntity.accepted().build() } LOGGER.info("INBOX Processing Activity Type: {}", parseActivity) try { - val url = request.requestURL.toString() + val url = httpServletRequest.requestURL.toString() val headers = - headersList.associateWith { header -> request.getHeaders(header)?.toList().orEmpty() } - - val method = when (val method = request.method.lowercase()) { - "get" -> HttpMethod.GET - "post" -> HttpMethod.POST - else -> { - throw IllegalArgumentException("Unsupported method: $method") + headersList.associateWith { header -> + httpServletRequest.getHeaders(header)?.toList().orEmpty() } - } apService.processActivity( - string, + body.decodeToString(), parseActivity, HttpRequest( - URL(url + request.queryString.orEmpty()), + URL(url + httpServletRequest.queryString.orEmpty()), HttpHeaders(headers), - method + HttpMethod.GET ), headers )