feat: InboxControllerでHTTP Signatureに関連するHTTPヘッダーの検査をするように

This commit is contained in:
usbharu 2024-02-20 13:12:33 +09:00
parent 1bdb19db45
commit 526354df1f
2 changed files with 51 additions and 24 deletions

View File

@ -16,8 +16,8 @@
package dev.usbharu.hideout.activitypub.interfaces.api.inbox package dev.usbharu.hideout.activitypub.interfaces.api.inbox
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.ResponseEntity 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.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@ -34,5 +34,5 @@ interface InboxController {
consumes = ["application/json", "application/*+json"], consumes = ["application/json", "application/*+json"],
method = [RequestMethod.POST] method = [RequestMethod.POST]
) )
suspend fun inbox(@RequestBody string: String): ResponseEntity<Unit> suspend fun inbox(httpServletRequest: HttpServletRequest): ResponseEntity<String>
} }

View File

@ -17,31 +17,64 @@
package dev.usbharu.hideout.activitypub.interfaces.api.inbox package dev.usbharu.hideout.activitypub.interfaces.api.inbox
import dev.usbharu.hideout.activitypub.service.common.APService 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.HttpHeaders
import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpMethod
import dev.usbharu.httpsignature.common.HttpRequest 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.slf4j.LoggerFactory
import org.springframework.http.HttpHeaders.WWW_AUTHENTICATE import org.springframework.http.HttpHeaders.WWW_AUTHENTICATE
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController 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 import java.net.URL
@RestController @RestController
class InboxControllerImpl(private val apService: APService) : InboxController { class InboxControllerImpl(
private val apService: APService,
private val httpSignatureHeaderChecker: HttpSignatureHeaderChecker,
) : InboxController {
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
override suspend fun inbox( override suspend fun inbox(
@RequestBody string: String httpServletRequest: HttpServletRequest,
): ResponseEntity<Unit> { ): ResponseEntity<String> {
val request = (requireNotNull(RequestContextHolder.getRequestAttributes()) as ServletRequestAttributes).request
val headersList = request.headerNames?.toList().orEmpty() val headersList = httpServletRequest.headerNames?.toList().orEmpty()
LOGGER.trace("Inbox Headers {}", headersList) 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) return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.header( .header(
WWW_AUTHENTICATE, WWW_AUTHENTICATE,
@ -51,33 +84,27 @@ class InboxControllerImpl(private val apService: APService) : InboxController {
} }
val parseActivity = try { val parseActivity = try {
apService.parseActivity(string) apService.parseActivity(body.decodeToString())
} catch (e: Exception) { } catch (e: Exception) {
LOGGER.warn("FAILED Parse Activity", e) LOGGER.warn("FAILED Parse Activity", e)
return ResponseEntity.accepted().build() return ResponseEntity.accepted().build()
} }
LOGGER.info("INBOX Processing Activity Type: {}", parseActivity) LOGGER.info("INBOX Processing Activity Type: {}", parseActivity)
try { try {
val url = request.requestURL.toString() val url = httpServletRequest.requestURL.toString()
val headers = val headers =
headersList.associateWith { header -> request.getHeaders(header)?.toList().orEmpty() } headersList.associateWith { header ->
httpServletRequest.getHeaders(header)?.toList().orEmpty()
val method = when (val method = request.method.lowercase()) {
"get" -> HttpMethod.GET
"post" -> HttpMethod.POST
else -> {
throw IllegalArgumentException("Unsupported method: $method")
} }
}
apService.processActivity( apService.processActivity(
string, body.decodeToString(),
parseActivity, parseActivity,
HttpRequest( HttpRequest(
URL(url + request.queryString.orEmpty()), URL(url + httpServletRequest.queryString.orEmpty()),
HttpHeaders(headers), HttpHeaders(headers),
method HttpMethod.GET
), ),
headers headers
) )