From 35ba6a2022605adc2e41588dc6fdd39ac0b14706 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:08:01 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E3=82=B8=E3=83=A7=E3=83=96=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E7=AD=89=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/JobQueueRunner.kt | 49 +++++++++++++++++++ .../hideout/config/ActivityPubConfig.kt | 23 +++++++++ .../hideout/config/HttpClientConfig.kt | 28 ++++++++++- .../usbharu/hideout/config/SecurityConfig.kt | 5 +- .../usbharu/hideout/config/SpringConfig.kt | 3 +- .../hideout/controller/InboxController.kt | 2 +- .../hideout/controller/InboxControllerImpl.kt | 5 +- .../hideout/controller/UserAPController.kt | 2 +- .../controller/UserAPControllerImpl.kt | 6 ++- .../wellknown/WebFingerController.kt | 5 +- .../hideout/domain/model/job/HideoutJob.kt | 5 ++ .../usbharu/hideout/plugins/ActivityPub.kt | 22 ++++++--- .../hideout/service/ap/APNoteService.kt | 22 ++++++--- .../hideout/service/ap/APReactionService.kt | 12 +++-- .../service/ap/APReceiveFollowService.kt | 15 +++--- .../hideout/service/ap/APSendFollowService.kt | 10 +++- .../usbharu/hideout/service/ap/APService.kt | 41 ++++++++++------ .../hideout/service/ap/APUserService.kt | 18 ++++--- .../api/mastodon/InstanceApiService.kt | 6 +-- .../auth/HttpSignatureVerifyService.kt | 8 ++- .../service/auth/UserDetailsServiceImpl.kt | 3 +- .../service/job/JobQueueWorkerService.kt | 4 +- .../service/job/KJobJobQueueWorkerService.kt | 10 ++-- .../hideout/service/user/UserServiceImpl.kt | 17 ++++--- src/main/resources/logback.xml | 4 +- 25 files changed, 239 insertions(+), 86 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/JobQueueRunner.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/config/ActivityPubConfig.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/JobQueueRunner.kt b/src/main/kotlin/dev/usbharu/hideout/JobQueueRunner.kt new file mode 100644 index 00000000..c8d97610 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/JobQueueRunner.kt @@ -0,0 +1,49 @@ +package dev.usbharu.hideout + +import dev.usbharu.hideout.domain.model.job.HideoutJob +import dev.usbharu.hideout.service.ap.APService +import dev.usbharu.hideout.service.job.JobQueueParentService +import dev.usbharu.hideout.service.job.JobQueueWorkerService +import org.slf4j.LoggerFactory +import org.springframework.boot.ApplicationArguments +import org.springframework.boot.ApplicationRunner +import org.springframework.stereotype.Component + +@Component +class JobQueueRunner(private val jobQueueParentService: JobQueueParentService, private val jobs: List) : + ApplicationRunner { + override fun run(args: ApplicationArguments?) { + LOGGER.info("Init job queue. ${jobs.size}") + jobQueueParentService.init(jobs) + } + + companion object { + val LOGGER = LoggerFactory.getLogger(JobQueueRunner::class.java) + } +} + +@Component +class JobQueueWorkerRunner( + private val jobQueueWorkerService: JobQueueWorkerService, + private val jobs: List, + private val apService: APService +) : ApplicationRunner { + override fun run(args: ApplicationArguments?) { + LOGGER.info("Init job queue worker.") + jobQueueWorkerService.init(jobs.map { + it to { + execute { + LOGGER.debug("excute job ${it.name}") + apService.processActivity( + job = this, + hideoutJob = it + ) + } + } + }) + } + + companion object { + val LOGGER = LoggerFactory.getLogger(JobQueueWorkerRunner::class.java) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/config/ActivityPubConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/ActivityPubConfig.kt new file mode 100644 index 00000000..5e6c3016 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/config/ActivityPubConfig.kt @@ -0,0 +1,23 @@ +package dev.usbharu.hideout.config + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class ActivityPubConfig { + + @Bean + @Qualifier("activitypub") + fun objectMapper(): ObjectMapper { + val objectMapper = jacksonObjectMapper() + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + return objectMapper + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt index 85483577..e5d5c56e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt @@ -1,12 +1,38 @@ package dev.usbharu.hideout.config +import dev.usbharu.hideout.plugins.KtorKeyMap +import dev.usbharu.hideout.plugins.httpSignaturePlugin +import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import io.ktor.client.* import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.logging.* import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import tech.barbero.http.message.signing.KeyMap @Configuration class HttpClientConfig { @Bean - fun httpClient(): HttpClient = HttpClient(CIO) + fun httpClient(keyMap: KeyMap): HttpClient = HttpClient(CIO).config { + install(httpSignaturePlugin) { + this.keyMap = keyMap + } + install(Logging) { + logger = Logger.DEFAULT + level = LogLevel.ALL + } + expectSuccess = true + } + + @Bean + fun keyMap( + userQueryService: UserQueryService, + transaction: Transaction, + applicationConfig: ApplicationConfig + ): KtorKeyMap { + return KtorKeyMap( + userQueryService, transaction, applicationConfig + ) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt index f98420b3..62dba22a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt @@ -33,7 +33,7 @@ import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import java.util.* -@EnableWebSecurity(debug = true) +@EnableWebSecurity(debug = false) @Configuration class SecurityConfig { @@ -66,6 +66,7 @@ class SecurityConfig { it.requestMatchers(PathRequest.toH2Console()).permitAll() it.requestMatchers( builder.pattern("/inbox"), + builder.pattern("/users/*/inbox"), builder.pattern("/api/v1/apps"), builder.pattern("/api/v1/instance/**"), builder.pattern("/.well-known/**"), @@ -85,6 +86,8 @@ class SecurityConfig { .formLogin(Customizer.withDefaults()) .csrf { it.ignoringRequestMatchers(builder.pattern("/api/**")) + it.ignoringRequestMatchers(builder.pattern("/users/*/inbox")) + it.ignoringRequestMatchers(builder.pattern("/inbox")) it.ignoringRequestMatchers(PathRequest.toH2Console()) } .headers { diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt index 58898d02..63825da0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import java.net.URL @Configuration class SpringConfig { @@ -28,7 +29,7 @@ class SpringConfig { @ConfigurationProperties("hideout") data class ApplicationConfig( - val url: String + val url: URL ) @ConfigurationProperties("hideout.database") diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt index 0cba64e3..15d4c77c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt @@ -15,7 +15,7 @@ interface InboxController { produces = ["application/activity+json", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""], method = [RequestMethod.GET, RequestMethod.POST] ) - suspend fun inbox(@RequestBody string: String): ResponseEntity { + fun inbox(@RequestBody string: String): ResponseEntity { return ResponseEntity(HttpStatus.ACCEPTED) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt index fb47a3f0..d65560d0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.controller import dev.usbharu.hideout.service.ap.APService +import kotlinx.coroutines.runBlocking import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestBody @@ -8,9 +9,9 @@ import org.springframework.web.bind.annotation.RestController @RestController class InboxControllerImpl(private val apService: APService) : InboxController { - override suspend fun inbox(@RequestBody string: String): ResponseEntity { + override fun inbox(@RequestBody string: String): ResponseEntity = runBlocking { val parseActivity = apService.parseActivity(string) apService.processActivity(string, parseActivity) - return ResponseEntity(HttpStatus.ACCEPTED) + ResponseEntity(HttpStatus.ACCEPTED) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt index 5390fff2..2c1da4ec 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt @@ -9,5 +9,5 @@ import org.springframework.web.bind.annotation.RestController @RestController interface UserAPController { @GetMapping("/users/{username}") - suspend fun userAp(@PathVariable("username") username: String): ResponseEntity + fun userAp(@PathVariable("username") username: String): ResponseEntity } diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt index 90fb6155..b18a30cf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt @@ -2,14 +2,16 @@ package dev.usbharu.hideout.controller import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.service.ap.APUserService +import kotlinx.coroutines.runBlocking import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RestController @RestController class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController { - override suspend fun userAp(username: String): ResponseEntity { + override fun userAp(username: String): ResponseEntity = runBlocking { val person = apUserService.getPersonByName(username) - return ResponseEntity(person, HttpStatus.OK) + person.context += listOf("https://www.w3.org/ns/activitystreams") + ResponseEntity(person, HttpStatus.OK) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt index 3ea7fffd..070b82fa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt @@ -10,7 +10,6 @@ import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam -import java.net.URL @Controller class WebFingerController( @@ -21,14 +20,14 @@ class WebFingerController( fun webfinger(@RequestParam("resource") resource: String): ResponseEntity = runBlocking { val acct = AcctUtil.parse(resource.replace("acct:", "")) val user = - webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: URL(applicationConfig.url).host) + webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host) val webFinger = WebFinger( "acct:${user.name}@${user.domain}", listOf( WebFinger.Link( "self", "application/activity+json", - applicationConfig.url + "/users/" + user.id + user.url ) ) ) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt index 63f2bfd2..efc9e844 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt @@ -1,21 +1,25 @@ package dev.usbharu.hideout.domain.model.job import kjob.core.Job +import org.springframework.stereotype.Component sealed class HideoutJob(name: String = "") : Job(name) +@Component object ReceiveFollowJob : HideoutJob("ReceiveFollowJob") { val actor = string("actor") val follow = string("follow") val targetActor = string("targetActor") } +@Component object DeliverPostJob : HideoutJob("DeliverPostJob") { val post = string("post") val actor = string("actor") val inbox = string("inbox") } +@Component object DeliverReactionJob : HideoutJob("DeliverReactionJob") { val reaction = string("reaction") val postUrl = string("postUrl") @@ -24,6 +28,7 @@ object DeliverReactionJob : HideoutJob("DeliverReactionJob") { val id = string("id") } +@Component object DeliverRemoveReactionJob : HideoutJob("DeliverRemoveReactionJob") { val id = string("id") val inbox = string("inbox") diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt index a3e61051..dcdea469 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.plugins -import dev.usbharu.hideout.config.Config +import com.fasterxml.jackson.databind.ObjectMapper +import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.domain.model.ap.JsonLd import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.core.Transaction @@ -26,13 +27,18 @@ import java.text.SimpleDateFormat import java.util.* import javax.crypto.SecretKey -suspend fun HttpClient.postAp(urlString: String, username: String, jsonLd: JsonLd): HttpResponse { +suspend fun HttpClient.postAp( + urlString: String, + username: String, + jsonLd: JsonLd, + objectMapper: ObjectMapper +): HttpResponse { jsonLd.context += "https://www.w3.org/ns/activitystreams" return this.post(urlString) { header("Accept", ContentType.Application.Activity) header("Content-Type", ContentType.Application.Activity) header("Signature", "keyId=\"$username\",algorithm=\"rsa-sha256\",headers=\"(request-target) digest date\"") - val text = Config.configData.objectMapper.writeValueAsString(jsonLd) + val text = objectMapper.writeValueAsString(jsonLd) setBody(text) } } @@ -157,7 +163,11 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo } } -class KtorKeyMap(private val userQueryService: UserQueryService, private val transaction: Transaction) : KeyMap { +class KtorKeyMap( + private val userQueryService: UserQueryService, + private val transaction: Transaction, + private val applicationConfig: ApplicationConfig +) : KeyMap { override fun getPublicKey(keyId: String?): PublicKey = runBlocking { val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") .substringAfterLast("/") @@ -165,7 +175,7 @@ class KtorKeyMap(private val userQueryService: UserQueryService, private val tra transaction.transaction { userQueryService.findByNameAndDomain( username, - Config.configData.domain + applicationConfig.url.host ).run { publicKey .replace("-----BEGIN PUBLIC KEY-----", "") @@ -185,7 +195,7 @@ class KtorKeyMap(private val userQueryService: UserQueryService, private val tra transaction.transaction { userQueryService.findByNameAndDomain( username, - Config.configData.domain + applicationConfig.url.host ).privateKey?.run { replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index f569ef7e..3aece244 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -1,7 +1,8 @@ package dev.usbharu.hideout.service.ap +import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.domain.model.hideout.entity.Post @@ -20,6 +21,7 @@ import io.ktor.client.* import io.ktor.client.statement.* import kjob.core.job.JobProps import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service import java.time.Instant @@ -34,14 +36,17 @@ interface APNoteService { } @Service -class APNoteServiceImpl( +class APNoteServiceImpl private constructor( private val httpClient: HttpClient, private val jobQueueParentService: JobQueueParentService, private val postRepository: PostRepository, private val apUserService: APUserService, private val userQueryService: UserQueryService, private val followerQueryService: FollowerQueryService, - private val postQueryService: PostQueryService + private val postQueryService: PostQueryService, + @Qualifier("activitypub") private val objectMapper: ObjectMapper, + private val applicationConfig: ApplicationConfig + ) : APNoteService { private val logger = LoggerFactory.getLogger(this::class.java) @@ -49,7 +54,7 @@ class APNoteServiceImpl( override suspend fun createNote(post: Post) { val followers = followerQueryService.findFollowersById(post.userId) val userEntity = userQueryService.findById(post.userId) - val note = Config.configData.objectMapper.writeValueAsString(post) + val note = objectMapper.writeValueAsString(post) followers.forEach { followerEntity -> jobQueueParentService.schedule(DeliverPostJob) { props[DeliverPostJob.actor] = userEntity.url @@ -61,7 +66,7 @@ class APNoteServiceImpl( override suspend fun createNoteJob(props: JobProps) { val actor = props[DeliverPostJob.actor] - val postEntity = Config.configData.objectMapper.readValue(props[DeliverPostJob.post]) + val postEntity = objectMapper.readValue(props[DeliverPostJob.post]) val note = Note( name = "Note", id = postEntity.url, @@ -79,8 +84,9 @@ class APNoteServiceImpl( name = "Create Note", `object` = note, actor = note.attributedTo, - id = "${Config.configData.url}/create/note/${postEntity.id}" - ) + id = "${applicationConfig.url}/create/note/${postEntity.id}" + ), + objectMapper ) } @@ -95,7 +101,7 @@ class APNoteServiceImpl( url, targetActor?.let { "$targetActor#pubkey" } ) - val note = Config.configData.objectMapper.readValue(response.bodyAsText()) + val note = objectMapper.readValue(response.bodyAsText()) return note(note, targetActor, url) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt index ce201308..fc1aea04 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.service.ap +import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ap.Like @@ -14,6 +15,7 @@ import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import kjob.core.job.JobProps +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service import java.time.Instant @@ -31,8 +33,10 @@ class APReactionServiceImpl( private val httpClient: HttpClient, private val userQueryService: UserQueryService, private val followerQueryService: FollowerQueryService, - private val postQueryService: PostQueryService -) : APReactionService { + private val postQueryService: PostQueryService, + @Qualifier("activitypub") private val objectMapper: ObjectMapper, + + ) : APReactionService { override suspend fun reaction(like: Reaction) { val followers = followerQueryService.findFollowersById(like.userId) val user = userQueryService.findById(like.userId) @@ -79,7 +83,7 @@ class APReactionServiceImpl( `object` = postUrl, id = "${Config.configData.url}/like/note/$id", content = content - ) + ), objectMapper ) } @@ -96,7 +100,7 @@ class APReactionServiceImpl( `object` = like, id = "${Config.configData.url}/undo/note/${like.id}", published = Instant.now() - ) + ), objectMapper ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt index 867ff717..261c5fd1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt @@ -1,7 +1,7 @@ package dev.usbharu.hideout.service.ap +import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ap.Accept @@ -15,9 +15,10 @@ import dev.usbharu.hideout.service.user.UserService import io.ktor.client.* import io.ktor.http.* import kjob.core.job.JobProps +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service -@Service + interface APReceiveFollowService { suspend fun receiveFollow(follow: Follow): ActivityPubResponse suspend fun receiveFollowJob(props: JobProps) @@ -30,24 +31,26 @@ class APReceiveFollowServiceImpl( private val userService: UserService, private val httpClient: HttpClient, private val userQueryService: UserQueryService, - private val transaction: Transaction + private val transaction: Transaction, + @Qualifier("activitypub") private val objectMapper: ObjectMapper ) : APReceiveFollowService { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { // TODO: Verify HTTP Signature jobQueueParentService.schedule(ReceiveFollowJob) { props[ReceiveFollowJob.actor] = follow.actor - props[ReceiveFollowJob.follow] = Config.configData.objectMapper.writeValueAsString(follow) + props[ReceiveFollowJob.follow] = objectMapper.writeValueAsString(follow) props[ReceiveFollowJob.targetActor] = follow.`object` } return ActivityPubStringResponse(HttpStatusCode.OK, "{}", ContentType.Application.Json) } override suspend fun receiveFollowJob(props: JobProps) { +// throw Exception() transaction.transaction { val actor = props[ReceiveFollowJob.actor] val targetActor = props[ReceiveFollowJob.targetActor] val person = apUserService.fetchPerson(actor, targetActor) - val follow = Config.configData.objectMapper.readValue(props[ReceiveFollowJob.follow]) + val follow = objectMapper.readValue(props[ReceiveFollowJob.follow]) httpClient.postAp( urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), username = "$targetActor#pubkey", @@ -55,7 +58,7 @@ class APReceiveFollowServiceImpl( name = "Follow", `object` = follow, actor = targetActor - ) + ), objectMapper ) val targetEntity = userQueryService.findByUrl(targetActor) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt index ef8e526b..b2069234 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt @@ -1,9 +1,11 @@ package dev.usbharu.hideout.service.ap +import com.fasterxml.jackson.databind.ObjectMapper import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto import dev.usbharu.hideout.plugins.postAp import io.ktor.client.* +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service @Service @@ -12,7 +14,10 @@ interface APSendFollowService { } @Service -class APSendFollowServiceImpl(private val httpClient: HttpClient) : APSendFollowService { +class APSendFollowServiceImpl( + private val httpClient: HttpClient, + @Qualifier("activitypub") private val objectMapper: ObjectMapper, +) : APSendFollowService { override suspend fun sendFollow(sendFollowDto: SendFollowDto) { val follow = Follow( name = "Follow", @@ -22,7 +27,8 @@ class APSendFollowServiceImpl(private val httpClient: HttpClient) : APSendFollow httpClient.postAp( urlString = sendFollowDto.followTargetUserId.inbox, username = sendFollowDto.userId.url, - jsonLd = follow + jsonLd = follow, + objectMapper ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt index 29b51e15..aa2d9781 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt @@ -1,8 +1,8 @@ package dev.usbharu.hideout.service.ap import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.job.* @@ -11,6 +11,7 @@ import kjob.core.dsl.JobContextWithProps import kjob.core.job.JobProps import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service @Service @@ -181,12 +182,13 @@ class APServiceImpl( private val apAcceptService: APAcceptService, private val apCreateService: APCreateService, private val apLikeService: APLikeService, - private val apReactionService: APReactionService + private val apReactionService: APReactionService, + @Qualifier("activitypub") private val objectMapper: ObjectMapper ) : APService { val logger: Logger = LoggerFactory.getLogger(this::class.java) override fun parseActivity(json: String): ActivityType { - val readTree = Config.configData.objectMapper.readTree(json) + val readTree = objectMapper.readTree(json) logger.trace("readTree: {}", readTree) if (readTree.isObject.not()) { throw JsonParseException("Json is not object.") @@ -204,17 +206,17 @@ class APServiceImpl( override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse { logger.debug("proccess activity: {}", type) return when (type) { - ActivityType.Accept -> apAcceptService.receiveAccept(Config.configData.objectMapper.readValue(json)) + ActivityType.Accept -> apAcceptService.receiveAccept(objectMapper.readValue(json)) ActivityType.Follow -> apReceiveFollowService.receiveFollow( - Config.configData.objectMapper.readValue( + objectMapper.readValue( json, Follow::class.java ) ) - ActivityType.Create -> apCreateService.receiveCreate(Config.configData.objectMapper.readValue(json)) - ActivityType.Like -> apLikeService.receiveLike(Config.configData.objectMapper.readValue(json)) - ActivityType.Undo -> apUndoService.receiveUndo(Config.configData.objectMapper.readValue(json)) + ActivityType.Create -> apCreateService.receiveCreate(objectMapper.readValue(json)) + ActivityType.Like -> apLikeService.receiveLike(objectMapper.readValue(json)) + ActivityType.Undo -> apUndoService.receiveUndo(objectMapper.readValue(json)) else -> { throw IllegalArgumentException("$type is not supported.") @@ -224,16 +226,25 @@ class APServiceImpl( override suspend fun processActivity(job: JobContextWithProps, hideoutJob: HideoutJob) { logger.debug("processActivity: ${hideoutJob.name}") - when (hideoutJob) { - ReceiveFollowJob -> apReceiveFollowService.receiveFollowJob( - job.props as JobProps - ) - DeliverPostJob -> apNoteService.createNoteJob(job.props as JobProps) - DeliverReactionJob -> apReactionService.reactionJob(job.props as JobProps) - DeliverRemoveReactionJob -> apReactionService.removeReactionJob( +// println(apReceiveFollowService::class.java) +// apReceiveFollowService.receiveFollowJob(job.props as JobProps) + when { + hideoutJob is ReceiveFollowJob -> { + apReceiveFollowService.receiveFollowJob( + job.props as JobProps + ) + } + + hideoutJob is DeliverPostJob -> apNoteService.createNoteJob(job.props as JobProps) + hideoutJob is DeliverReactionJob -> apReactionService.reactionJob(job.props as JobProps) + hideoutJob is DeliverRemoveReactionJob -> apReactionService.removeReactionJob( job.props as JobProps ) + + else -> { + throw IllegalStateException("WTF") + } } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt index 4266db9d..abb7423f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt @@ -1,7 +1,8 @@ package dev.usbharu.hideout.service.ap +import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.domain.model.ap.Image import dev.usbharu.hideout.domain.model.ap.Key import dev.usbharu.hideout.domain.model.ap.Person @@ -18,6 +19,7 @@ import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service @Service @@ -41,16 +43,18 @@ class APUserServiceImpl( private val userService: UserService, private val httpClient: HttpClient, private val userQueryService: UserQueryService, - private val transaction: Transaction + private val transaction: Transaction, + private val applicationConfig: ApplicationConfig, + @Qualifier("activitypub") private val objectMapper: ObjectMapper ) : APUserService { override suspend fun getPersonByName(name: String): Person { val userEntity = transaction.transaction { - userQueryService.findByNameAndDomain(name, Config.configData.domain) + userQueryService.findByNameAndDomain(name, applicationConfig.url.host) } // TODO: JOINで書き直し - val userUrl = "${Config.configData.url}/users/$name" + val userUrl = "${applicationConfig.url}/users/$name" return Person( type = emptyList(), name = userEntity.name, @@ -73,7 +77,7 @@ class APUserServiceImpl( owner = userUrl, publicKeyPem = userEntity.publicKey ), - endpoints = mapOf("sharedInbox" to "${Config.configData.url}/inbox") + endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox") ) } @@ -105,7 +109,7 @@ class APUserServiceImpl( owner = url, publicKeyPem = userEntity.publicKey ), - endpoints = mapOf("sharedInbox" to "${Config.configData.url}/inbox") + endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox") ) to userEntity } catch (ignore: FailedToGetResourcesException) { val httpResponse = if (targetActor != null) { @@ -115,7 +119,7 @@ class APUserServiceImpl( accept(ContentType.Application.Activity) } } - val person = Config.configData.objectMapper.readValue(httpResponse.bodyAsText()) + val person = objectMapper.readValue(httpResponse.bodyAsText()) person to userService.createRemoteUser( RemoteUserCreateDto( diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/InstanceApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/InstanceApiService.kt index 92bff65b..e83ae237 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/InstanceApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/InstanceApiService.kt @@ -3,7 +3,6 @@ package dev.usbharu.hideout.service.api.mastodon import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.domain.mastodon.model.generated.* import org.springframework.stereotype.Service -import java.net.URL @Service interface InstanceApiService { @@ -14,15 +13,14 @@ interface InstanceApiService { class InstanceApiServiceImpl(private val applicationConfig: ApplicationConfig) : InstanceApiService { override suspend fun v1Instance(): V1Instance { val url = applicationConfig.url - val url1 = URL(url) return V1Instance( - uri = url1.host, + uri = url.host, title = "Hideout Server", shortDescription = "Hideout test server", description = "This server is operated for testing of Hideout. We are not responsible for any events that occur when associating with this server", email = "i@usbharu.dev", version = "0.0.1", - urls = V1InstanceUrls("wss://${url1.host}"), + urls = V1InstanceUrls("wss://${url.host}"), stats = V1InstanceStats(1, 0, 0), thumbnail = null, languages = listOf("ja-JP"), diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt index fd4a26c3..f0009080 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.service.auth +import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.plugins.KtorKeyMap import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.core.Transaction @@ -15,10 +16,13 @@ interface HttpSignatureVerifyService { @Service class HttpSignatureVerifyServiceImpl( private val userQueryService: UserQueryService, - private val transaction: Transaction + private val transaction: Transaction, + private val applicationConfig: ApplicationConfig ) : HttpSignatureVerifyService { override fun verify(headers: Headers): Boolean { - val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction)).build() + val build = + SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction, applicationConfig)) + .build() return true // build.verify(object : HttpMessage { // override fun headerValues(name: String?): MutableList { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt index adb9c0ab..e6a8f6b3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt @@ -9,7 +9,6 @@ import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Service -import java.net.URL @Service class UserDetailsServiceImpl( @@ -23,7 +22,7 @@ class UserDetailsServiceImpl( throw UsernameNotFoundException("$username not found") } transaction.transaction { - val findById = userQueryService.findByNameAndDomain(username, URL(applicationConfig.url).host) + val findById = userQueryService.findByNameAndDomain(username, applicationConfig.url.host) UserDetailsImpl( findById.id, findById.name, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt index 80413c79..f8bfd523 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt @@ -1,6 +1,6 @@ package dev.usbharu.hideout.service.job -import kjob.core.Job +import dev.usbharu.hideout.domain.model.job.HideoutJob import kjob.core.dsl.KJobFunctions import org.springframework.stereotype.Service import kjob.core.dsl.JobContextWithProps as JCWP @@ -8,5 +8,5 @@ import kjob.core.dsl.JobRegisterContext as JRC @Service interface JobQueueWorkerService { - fun init(defines: List>.(Job) -> KJobFunctions>>>) + fun init(defines: List>.(HideoutJob) -> KJobFunctions>>>) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt index 368aae99..d4714df7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt @@ -1,13 +1,13 @@ package dev.usbharu.hideout.service.job +import dev.usbharu.hideout.domain.model.job.HideoutJob import dev.usbharu.kjob.exposed.ExposedKJob -import kjob.core.Job +import kjob.core.dsl.JobContextWithProps +import kjob.core.dsl.JobRegisterContext import kjob.core.dsl.KJobFunctions import kjob.core.kjob import org.jetbrains.exposed.sql.Database import org.springframework.stereotype.Service -import kjob.core.dsl.JobContextWithProps as JCWP -import kjob.core.dsl.JobRegisterContext as JRC @Service class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService { @@ -21,9 +21,7 @@ class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorker }.start() } - override fun init( - defines: List>.(Job) -> KJobFunctions>>> - ) { + override fun init(defines: List>.(HideoutJob) -> KJobFunctions>>>) { defines.forEach { job -> kjob.register(job.first, job.second) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt index d3611586..76fb4c1e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt @@ -1,6 +1,6 @@ package dev.usbharu.hideout.service.user -import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto @@ -19,12 +19,13 @@ class UserServiceImpl( private val userAuthService: UserAuthService, private val apSendFollowService: APSendFollowService, private val userQueryService: UserQueryService, - private val followerQueryService: FollowerQueryService + private val followerQueryService: FollowerQueryService, + private val applicationConfig: ApplicationConfig ) : UserService { override suspend fun usernameAlreadyUse(username: String): Boolean { - val findByNameAndDomain = userQueryService.findByNameAndDomain(username, Config.configData.domain) + val findByNameAndDomain = userQueryService.findByNameAndDomain(username, applicationConfig.url.host) return findByNameAndDomain != null } @@ -35,13 +36,13 @@ class UserServiceImpl( val userEntity = User.of( id = nextId, name = user.name, - domain = Config.configData.domain, + domain = applicationConfig.url.host, screenName = user.screenName, description = user.description, password = hashedPassword, - inbox = "${Config.configData.url}/users/${user.name}/inbox", - outbox = "${Config.configData.url}/users/${user.name}/outbox", - url = "${Config.configData.url}/users/${user.name}", + inbox = "${applicationConfig.url}/users/${user.name}/inbox", + outbox = "${applicationConfig.url}/users/${user.name}/outbox", + url = "${applicationConfig.url}/users/${user.name}", publicKey = keyPair.public.toPem(), privateKey = keyPair.private.toPem(), createdAt = Instant.now() @@ -70,7 +71,7 @@ class UserServiceImpl( override suspend fun followRequest(id: Long, followerId: Long): Boolean { val user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") val follower = userRepository.findById(followerId) ?: throw UserNotFoundException("$followerId was not found.") - return if (user.domain == Config.configData.domain) { + return if (user.domain == applicationConfig.url.host) { follow(id, followerId) true } else { diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 25579496..1736f642 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,7 +4,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + @@ -12,5 +12,5 @@ - +