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
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<Unit>
suspend fun inbox(httpServletRequest: HttpServletRequest): ResponseEntity<String>
}

View File

@ -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<Unit> {
val request = (requireNotNull(RequestContextHolder.getRequestAttributes()) as ServletRequestAttributes).request
httpServletRequest: HttpServletRequest,
): ResponseEntity<String> {
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
)