mirror of https://github.com/usbharu/Hideout.git
feat: InboxControllerでHTTP Signatureに関連するHTTPヘッダーの検査をするように
This commit is contained in:
parent
1bdb19db45
commit
526354df1f
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue