mirror of https://github.com/usbharu/Hideout.git
feat: ジョブキューでHTTP Signatureの検証を行うように
This commit is contained in:
parent
65b37cbb5e
commit
986d16f442
|
@ -1,16 +1,28 @@
|
||||||
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.httpsignature.common.HttpHeaders
|
||||||
|
import dev.usbharu.httpsignature.common.HttpMethod
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
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.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
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class InboxControllerImpl(private val apService: APService) : InboxController {
|
class InboxControllerImpl(private val apService: APService) : InboxController {
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
override suspend fun inbox(@RequestBody string: String): ResponseEntity<Unit> {
|
override suspend fun inbox(
|
||||||
|
@RequestBody string: String
|
||||||
|
): ResponseEntity<Unit> {
|
||||||
|
|
||||||
|
val request = (requireNotNull(RequestContextHolder.getRequestAttributes()) as ServletRequestAttributes).request
|
||||||
|
|
||||||
val parseActivity = try {
|
val parseActivity = try {
|
||||||
apService.parseActivity(string)
|
apService.parseActivity(string)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -19,7 +31,29 @@ class InboxControllerImpl(private val apService: APService) : InboxController {
|
||||||
}
|
}
|
||||||
LOGGER.info("INBOX Processing Activity Type: {}", parseActivity)
|
LOGGER.info("INBOX Processing Activity Type: {}", parseActivity)
|
||||||
try {
|
try {
|
||||||
apService.processActivity(string, parseActivity)
|
val url = request.requestURL.toString()
|
||||||
|
|
||||||
|
val headersList = request.headerNames?.toList().orEmpty()
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println(headers)
|
||||||
|
|
||||||
|
apService.processActivity(
|
||||||
|
string, parseActivity, HttpRequest(
|
||||||
|
URL(url + request.queryString.orEmpty()),
|
||||||
|
HttpHeaders(headers),
|
||||||
|
method
|
||||||
|
), headers
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LOGGER.warn("FAILED Process Activity $parseActivity", e)
|
LOGGER.warn("FAILED Process Activity $parseActivity", e)
|
||||||
return ResponseEntity(HttpStatus.ACCEPTED)
|
return ResponseEntity(HttpStatus.ACCEPTED)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import dev.usbharu.hideout.activitypub.interfaces.api.common.ActivityPubResponse
|
||||||
import dev.usbharu.hideout.activitypub.interfaces.api.common.ActivityPubStringResponse
|
import dev.usbharu.hideout.activitypub.interfaces.api.common.ActivityPubStringResponse
|
||||||
import dev.usbharu.hideout.core.external.job.InboxJob
|
import dev.usbharu.hideout.core.external.job.InboxJob
|
||||||
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
@ -15,7 +16,12 @@ import org.springframework.stereotype.Service
|
||||||
interface APService {
|
interface APService {
|
||||||
fun parseActivity(json: String): ActivityType
|
fun parseActivity(json: String): ActivityType
|
||||||
|
|
||||||
suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse?
|
suspend fun processActivity(
|
||||||
|
json: String,
|
||||||
|
type: ActivityType,
|
||||||
|
httpRequest: HttpRequest,
|
||||||
|
map: Map<String, List<String>>
|
||||||
|
): ActivityPubResponse?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ActivityType {
|
enum class ActivityType {
|
||||||
|
@ -215,11 +221,20 @@ class APServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
||||||
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
|
override suspend fun processActivity(
|
||||||
|
json: String,
|
||||||
|
type: ActivityType,
|
||||||
|
httpRequest: HttpRequest,
|
||||||
|
map: Map<String, List<String>>
|
||||||
|
): ActivityPubResponse {
|
||||||
logger.debug("process activity: {}", type)
|
logger.debug("process activity: {}", type)
|
||||||
jobQueueParentService.schedule(InboxJob) {
|
jobQueueParentService.schedule(InboxJob) {
|
||||||
props[it.json] = json
|
props[it.json] = json
|
||||||
props[it.type] = type.name
|
props[it.type] = type.name
|
||||||
|
val writeValueAsString = objectMapper.writeValueAsString(httpRequest)
|
||||||
|
println(writeValueAsString)
|
||||||
|
props[it.httpRequest] = writeValueAsString
|
||||||
|
props[it.headers] = objectMapper.writeValueAsString(map)
|
||||||
}
|
}
|
||||||
return ActivityPubStringResponse(message = "")
|
return ActivityPubStringResponse(message = "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,17 @@ import dev.usbharu.hideout.activitypub.service.activity.like.APLikeServiceImpl
|
||||||
import dev.usbharu.hideout.activitypub.service.activity.like.ApReactionJobService
|
import dev.usbharu.hideout.activitypub.service.activity.like.ApReactionJobService
|
||||||
import dev.usbharu.hideout.activitypub.service.activity.undo.APUndoServiceImpl
|
import dev.usbharu.hideout.activitypub.service.activity.undo.APUndoServiceImpl
|
||||||
import dev.usbharu.hideout.activitypub.service.objects.note.ApNoteJobService
|
import dev.usbharu.hideout.activitypub.service.objects.note.ApNoteJobService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||||
import dev.usbharu.hideout.core.external.job.*
|
import dev.usbharu.hideout.core.external.job.*
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.util.RsaUtil
|
||||||
|
import dev.usbharu.httpsignature.common.HttpHeaders
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
|
import dev.usbharu.httpsignature.common.PublicKey
|
||||||
|
import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser
|
||||||
|
import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier
|
||||||
import kjob.core.dsl.JobContextWithProps
|
import kjob.core.dsl.JobContextWithProps
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
@ -31,7 +40,12 @@ class ApJobServiceImpl(
|
||||||
private val APLikeServiceImpl: APLikeServiceImpl,
|
private val APLikeServiceImpl: APLikeServiceImpl,
|
||||||
private val APUndoServiceImpl: APUndoServiceImpl,
|
private val APUndoServiceImpl: APUndoServiceImpl,
|
||||||
private val APReceiveDeleteServiceImpl: APReceiveDeleteServiceImpl,
|
private val APReceiveDeleteServiceImpl: APReceiveDeleteServiceImpl,
|
||||||
@Qualifier("activitypub") private val objectMapper: ObjectMapper
|
@Qualifier("activitypub") private val objectMapper: ObjectMapper,
|
||||||
|
private val httpSignatureVerifier: RsaSha256HttpSignatureVerifier,
|
||||||
|
private val signatureHeaderParser: DefaultSignatureHeaderParser,
|
||||||
|
private val apUserService: APUserService,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val transaction: Transaction
|
||||||
) : ApJobService {
|
) : ApJobService {
|
||||||
@Suppress("REDUNDANT_ELSE_IN_WHEN")
|
@Suppress("REDUNDANT_ELSE_IN_WHEN")
|
||||||
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
||||||
|
@ -41,6 +55,29 @@ class ApJobServiceImpl(
|
||||||
// Springで作成されるプロキシの都合上パターンマッチングが壊れるので必須
|
// Springで作成されるプロキシの都合上パターンマッチングが壊れるので必須
|
||||||
when (hideoutJob) {
|
when (hideoutJob) {
|
||||||
is InboxJob -> {
|
is InboxJob -> {
|
||||||
|
val httpRequestString = (job.props as JobProps<InboxJob>)[InboxJob.httpRequest]
|
||||||
|
println(httpRequestString)
|
||||||
|
val headerString = (job.props as JobProps<InboxJob>)[InboxJob.headers]
|
||||||
|
|
||||||
|
val readValue = objectMapper.readValue<Map<String, List<String>>>(headerString)
|
||||||
|
|
||||||
|
val httpRequest =
|
||||||
|
objectMapper.readValue<HttpRequest>(httpRequestString).copy(headers = HttpHeaders(readValue))
|
||||||
|
val signature = signatureHeaderParser.parse(httpRequest.headers)
|
||||||
|
|
||||||
|
val publicKey = transaction.transaction {
|
||||||
|
try {
|
||||||
|
userQueryService.findByKeyId(signature.keyId)
|
||||||
|
} catch (e: FailedToGetResourcesException) {
|
||||||
|
apUserService.fetchPersonWithEntity(signature.keyId).second
|
||||||
|
}.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSignatureVerifier.verify(
|
||||||
|
httpRequest,
|
||||||
|
PublicKey(RsaUtil.decodeRsaPublicKeyPem(publicKey), signature.keyId)
|
||||||
|
)
|
||||||
|
|
||||||
val typeString = (job.props as JobProps<InboxJob>)[InboxJob.type]
|
val typeString = (job.props as JobProps<InboxJob>)[InboxJob.type]
|
||||||
val json = (job.props as JobProps<InboxJob>)[InboxJob.json]
|
val json = (job.props as JobProps<InboxJob>)[InboxJob.json]
|
||||||
val type = ActivityType.valueOf(typeString)
|
val type = ActivityType.valueOf(typeString)
|
||||||
|
@ -60,6 +97,7 @@ class ApJobServiceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReceiveFollowJob -> {
|
is ReceiveFollowJob -> {
|
||||||
apReceiveFollowJobService.receiveFollowJob(
|
apReceiveFollowJobService.receiveFollowJob(
|
||||||
job.props as JobProps<ReceiveFollowJob>
|
job.props as JobProps<ReceiveFollowJob>
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package dev.usbharu.hideout.application.config
|
||||||
|
|
||||||
|
import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner
|
||||||
|
import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser
|
||||||
|
import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier
|
||||||
|
import dev.usbharu.httpsignature.verify.SignatureHeaderParser
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class HttpSignatureConfig {
|
||||||
|
@Bean
|
||||||
|
fun defaultSignatureHeaderParser(): DefaultSignatureHeaderParser = DefaultSignatureHeaderParser()
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun rsaSha256HttpSignatureVerifier(
|
||||||
|
signatureHeaderParser: SignatureHeaderParser,
|
||||||
|
signatureSigner: RsaSha256HttpSignatureSigner
|
||||||
|
): RsaSha256HttpSignatureVerifier = RsaSha256HttpSignatureVerifier(signatureHeaderParser, signatureSigner)
|
||||||
|
}
|
|
@ -41,4 +41,6 @@ object DeliverRemoveReactionJob : HideoutJob("DeliverRemoveReactionJob") {
|
||||||
object InboxJob : HideoutJob("InboxJob") {
|
object InboxJob : HideoutJob("InboxJob") {
|
||||||
val json = string("json")
|
val json = string("json")
|
||||||
val type = string("type")
|
val type = string("type")
|
||||||
|
val httpRequest = string("http_request")
|
||||||
|
val headers = string("headers")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue