diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt index 04f1d6f3..fc8acf7f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt @@ -1,16 +1,28 @@ package dev.usbharu.hideout.activitypub.interfaces.api.inbox 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.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 { @Suppress("TooGenericExceptionCaught") - override suspend fun inbox(@RequestBody string: String): ResponseEntity { + override suspend fun inbox( + @RequestBody string: String + ): ResponseEntity { + + val request = (requireNotNull(RequestContextHolder.getRequestAttributes()) as ServletRequestAttributes).request + val parseActivity = try { apService.parseActivity(string) } catch (e: Exception) { @@ -19,7 +31,29 @@ class InboxControllerImpl(private val apService: APService) : InboxController { } LOGGER.info("INBOX Processing Activity Type: {}", parseActivity) 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) { LOGGER.warn("FAILED Process Activity $parseActivity", e) return ResponseEntity(HttpStatus.ACCEPTED) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt index 2db01861..e6e10495 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt @@ -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.core.external.job.InboxJob import dev.usbharu.hideout.core.service.job.JobQueueParentService +import dev.usbharu.httpsignature.common.HttpRequest import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Qualifier @@ -15,7 +16,12 @@ import org.springframework.stereotype.Service interface APService { fun parseActivity(json: String): ActivityType - suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? + suspend fun processActivity( + json: String, + type: ActivityType, + httpRequest: HttpRequest, + map: Map> + ): ActivityPubResponse? } enum class ActivityType { @@ -215,11 +221,20 @@ class APServiceImpl( } @Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration") - override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse { + override suspend fun processActivity( + json: String, + type: ActivityType, + httpRequest: HttpRequest, + map: Map> + ): ActivityPubResponse { logger.debug("process activity: {}", type) jobQueueParentService.schedule(InboxJob) { props[it.json] = json 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 = "") } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ApJobServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ApJobServiceImpl.kt index 13d23f57..0020fbd0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ApJobServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ApJobServiceImpl.kt @@ -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.undo.APUndoServiceImpl 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.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.job.JobProps import org.slf4j.LoggerFactory @@ -31,7 +40,12 @@ class ApJobServiceImpl( private val APLikeServiceImpl: APLikeServiceImpl, private val APUndoServiceImpl: APUndoServiceImpl, 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 { @Suppress("REDUNDANT_ELSE_IN_WHEN") override suspend fun processActivity(job: JobContextWithProps, hideoutJob: HideoutJob) { @@ -41,6 +55,29 @@ class ApJobServiceImpl( // Springで作成されるプロキシの都合上パターンマッチングが壊れるので必須 when (hideoutJob) { is InboxJob -> { + val httpRequestString = (job.props as JobProps)[InboxJob.httpRequest] + println(httpRequestString) + val headerString = (job.props as JobProps)[InboxJob.headers] + + val readValue = objectMapper.readValue>>(headerString) + + val httpRequest = + objectMapper.readValue(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.type] val json = (job.props as JobProps)[InboxJob.json] val type = ActivityType.valueOf(typeString) @@ -60,6 +97,7 @@ class ApJobServiceImpl( } } } + is ReceiveFollowJob -> { apReceiveFollowJobService.receiveFollowJob( job.props as JobProps diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/HttpSignatureConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/HttpSignatureConfig.kt new file mode 100644 index 00000000..ee3bc409 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/HttpSignatureConfig.kt @@ -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) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/external/job/HideoutJob.kt b/src/main/kotlin/dev/usbharu/hideout/core/external/job/HideoutJob.kt index 94fa81da..238b809f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/external/job/HideoutJob.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/external/job/HideoutJob.kt @@ -41,4 +41,6 @@ object DeliverRemoveReactionJob : HideoutJob("DeliverRemoveReactionJob") { object InboxJob : HideoutJob("InboxJob") { val json = string("json") val type = string("type") + val httpRequest = string("http_request") + val headers = string("headers") }