From c4c9b4872220ad40f8dc1f8929be4f976cf06f72 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:57:42 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20InboxJobProcessor=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tmp/AbstractActivityPubProcessor.kt | 4 +- .../service/tmp/ActivityPubProcessContext.kt | 14 +++ .../service/tmp/ActivityPubProcessor.kt | 4 +- .../service/tmp/InboxJobProcessor.kt | 94 +++++++++++++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessContext.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/InboxJobProcessor.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/AbstractActivityPubProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/AbstractActivityPubProcessor.kt index ff23a65e..6a3aba7e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/AbstractActivityPubProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/AbstractActivityPubProcessor.kt @@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory abstract class AbstractActivityPubProcessor(val transaction: Transaction) : ActivityPubProcessor { private val logger = LoggerFactory.getLogger(this::class.java) - override suspend fun process(activity: T) { + override suspend fun process(activity: ActivityPubProcessContext) { logger.info("START ActivityPub process") try { transaction.transaction { @@ -22,6 +22,6 @@ abstract class AbstractActivityPubProcessor(val transaction: Transac logger.info("SUCCESS ActivityPub process") } - abstract suspend fun internalProcess(activity: T) + abstract suspend fun internalProcess(activity: ActivityPubProcessContext) } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessContext.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessContext.kt new file mode 100644 index 00000000..6f45fd20 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessContext.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.activitypub.service.tmp + +import com.fasterxml.jackson.databind.JsonNode +import dev.usbharu.hideout.activitypub.domain.model.objects.Object +import dev.usbharu.httpsignature.common.HttpRequest +import dev.usbharu.httpsignature.verify.Signature + +data class ActivityPubProcessContext( + val activity: T, + val jsonNode: JsonNode, + val httpRequest: HttpRequest, + val signature: Signature?, + val isAuthorized: Boolean +) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessor.kt index 919bf836..350f1aea 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/ActivityPubProcessor.kt @@ -4,7 +4,9 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.Object import dev.usbharu.hideout.activitypub.service.common.ActivityType interface ActivityPubProcessor { - suspend fun process(activity: T) + suspend fun process(activity: ActivityPubProcessContext) fun isSupported(activityType: ActivityType): Boolean + + fun type(): Class } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/InboxJobProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/InboxJobProcessor.kt new file mode 100644 index 00000000..1489765c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/tmp/InboxJobProcessor.kt @@ -0,0 +1,94 @@ +package dev.usbharu.hideout.activitypub.service.tmp + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.activitypub.domain.model.objects.Object +import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.activitypub.service.objects.user.APUserService +import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException +import dev.usbharu.hideout.core.external.job.InboxJob +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.HttpSignatureVerifier +import dev.usbharu.httpsignature.verify.Signature +import dev.usbharu.httpsignature.verify.SignatureHeaderParser +import kjob.core.job.JobProps +import org.slf4j.LoggerFactory + +class InboxJobProcessor( + private val activityPubProcessorList: List>, + private val objectMapper: ObjectMapper, + private val signatureHeaderParser: SignatureHeaderParser, + private val signatureVerifier: HttpSignatureVerifier, + private val userQueryService: UserQueryService, + private val apUserService: APUserService +) { + suspend fun process(props: JobProps) { + + val type = ActivityType.valueOf(props[InboxJob.type]) + val jsonString = objectMapper.readTree(props[InboxJob.json]) + val httpRequestString = props[InboxJob.httpRequest] + val headersString = props[InboxJob.headers] + + logger.info("START Process inbox. type: {}", type) + logger.trace("type: {} \njson: \n{}", type, jsonString.toPrettyString()) + + val map = objectMapper.readValue>>(headersString) + + val httpRequest = + objectMapper.readValue(httpRequestString).copy(headers = HttpHeaders(map)) + + logger.trace("request: {}\nheaders: {}", httpRequest, map) + + val signature = parseSignatureHeader(httpRequest.headers) + + logger.debug("Has signature? {}", signature != null) + + val verify = signature?.let { verifyHttpSignature(httpRequest, it) } ?: false + + logger.debug("Is verifying success? {}", verify) + + val activityPubProcessor = activityPubProcessorList.firstOrNull { it.isSupported(type) } + + if (activityPubProcessor == null) { + logger.warn("ActivityType {} is not support.", type) + throw IllegalStateException("ActivityPubProcessor not found.") + } + + val value = objectMapper.treeToValue(jsonString, activityPubProcessor.type()) + activityPubProcessor.process(ActivityPubProcessContext(value, jsonString, httpRequest, signature, verify)) + + logger.info("SUCCESS Process inbox. type: {}", type) + } + + private suspend fun verifyHttpSignature(httpRequest: HttpRequest, signature: Signature): Boolean { + val user = try { + userQueryService.findByKeyId(signature.keyId) + } catch (_: FailedToGetResourcesException) { + apUserService.fetchPersonWithEntity(signature.keyId).second + } + + val verify = signatureVerifier.verify( + httpRequest, + PublicKey(RsaUtil.decodeRsaPublicKeyPem(user.publicKey), signature.keyId) + ) + + return verify.success + } + + private fun parseSignatureHeader(httpHeaders: HttpHeaders): Signature? { + return try { + signatureHeaderParser.parse(httpHeaders) + } catch (e: RuntimeException) { + logger.trace("FAILED parse signature header", e) + null + } + } + + companion object { + private val logger = LoggerFactory.getLogger(InboxJobProcessor::class.java) + } +}