Merge remote-tracking branch 'origin/feature/register-account' into feature/register-account

This commit is contained in:
usbharu 2024-05-16 15:53:33 +09:00
commit 21ced0670a
13 changed files with 310 additions and 82 deletions

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.activitypub.domain
import dev.usbharu.hideout.activitypub.domain.model.StringOrObject
object Constant {
val context = listOf(
StringOrObject("https://www.w3.org/ns/activitystreams"),
StringOrObject("https://w3id.org/security/v1"),
StringOrObject(
mapOf(
"manuallyApprovesFollowers" to "as:manuallyApprovesFollowers",
"sensitive" to "as:sensitive",
"Hashtag" to "as:Hashtag",
"quoteUrl" to "as:quoteUrl",
"toot" to "http://joinmastodon.org/ns#",
"Emoji" to "toot:Emoji",
"featured" to "toot:featured",
"discoverable" to "toot:discoverable",
"schema" to "http://schema.org#",
"PropertyValue" to "schema:PropertyValue",
"value" to "schema:value",
)
)
)
}

View File

@ -31,18 +31,18 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
open class JsonLd { open class JsonLd {
@JsonProperty("@context") @JsonProperty("@context")
@JsonDeserialize(contentUsing = ContextDeserializer::class) @JsonDeserialize(contentUsing = StringOrObjectDeserializer::class)
@JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class) @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class)
@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonInclude(JsonInclude.Include.NON_EMPTY)
var context: List<String> = emptyList() var context: List<StringOrObject> = emptyList()
set(value) { set(value) {
field = value.filterNotNull().filter { it.isNotBlank() } field = value.filterNot { it.isEmpty() }
} }
@JsonCreator @JsonCreator
constructor(context: List<String?>?) { constructor(context: List<StringOrObject?>?) {
if (context != null) { if (context != null) {
this.context = context.filterNotNull().filter { it.isNotBlank() } this.context = context.filterNotNull().filterNot { it.isEmpty() }
} else { } else {
this.context = emptyList() this.context = emptyList()
} }
@ -76,24 +76,24 @@ class ContextDeserializer : JsonDeserializer<String>() {
} }
} }
class ContextSerializer : JsonSerializer<List<String>>() { class ContextSerializer : JsonSerializer<List<StringOrObject>>() {
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun isEmpty(value: List<String>?): Boolean = value.isNullOrEmpty() override fun isEmpty(value: List<StringOrObject>?): Boolean = value.isNullOrEmpty()
override fun isEmpty(provider: SerializerProvider?, value: List<String>?): Boolean = value.isNullOrEmpty() override fun isEmpty(provider: SerializerProvider?, value: List<StringOrObject>?): Boolean = value.isNullOrEmpty()
override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider) { override fun serialize(value: List<StringOrObject>?, gen: JsonGenerator?, serializers: SerializerProvider) {
if (value.isNullOrEmpty()) { if (value.isNullOrEmpty()) {
serializers.defaultSerializeNull(gen) serializers.defaultSerializeNull(gen)
return return
} }
if (value.size == 1) { if (value.size == 1) {
gen?.writeString(value[0]) serializers.findValueSerializer(StringOrObject::class.java).serialize(value[0], gen, serializers)
} else { } else {
gen?.writeStartArray() gen?.writeStartArray()
value.forEach { value.forEach {
gen?.writeString(it) serializers.findValueSerializer(StringOrObject::class.java).serialize(it, gen, serializers)
} }
gen?.writeEndArray() gen?.writeEndArray()
} }

View File

@ -0,0 +1,82 @@
package dev.usbharu.hideout.activitypub.domain.model
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.*
open class StringOrObject {
var contextString: String? = null
var contextObject: Map<String, String>? = null
@JsonCreator
protected constructor()
constructor(string: String) : this() {
contextString = string
}
constructor(contextObject: Map<String, String>) : this() {
this.contextObject = contextObject
}
fun isEmpty(): Boolean = contextString.isNullOrEmpty() and contextObject.isNullOrEmpty()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StringOrObject
if (contextString != other.contextString) return false
if (contextObject != other.contextObject) return false
return true
}
override fun hashCode(): Int {
var result = contextString?.hashCode() ?: 0
result = 31 * result + (contextObject?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "StringOrObject(contextString=$contextString, contextObject=$contextObject)"
}
}
class StringOrObjectDeserializer : JsonDeserializer<StringOrObject>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): StringOrObject {
val readTree: JsonNode = p?.codec?.readTree(p) ?: return StringOrObject("")
return if (readTree.isValueNode) {
StringOrObject(readTree.textValue())
} else if (readTree.isObject) {
val map: Map<String, String> = ctxt.readTreeAsValue(
readTree,
ctxt.typeFactory.constructType(object : TypeReference<Map<String, String>>() {})
)
StringOrObject(map)
} else {
StringOrObject("")
}
}
}
class StringORObjectSerializer : JsonSerializer<StringOrObject>() {
override fun serialize(value: StringOrObject?, gen: JsonGenerator?, serializers: SerializerProvider) {
if (value == null) {
serializers.defaultSerializeNull(gen)
return
}
if (value.contextString != null) {
gen?.writeString(value.contextString)
} else {
serializers.defaultSerializeValue(value.contextObject, gen)
}
}
}

View File

@ -16,6 +16,7 @@
package dev.usbharu.hideout.activitypub.interfaces.api.actor package dev.usbharu.hideout.activitypub.interfaces.api.actor
import dev.usbharu.hideout.activitypub.domain.Constant
import dev.usbharu.hideout.activitypub.domain.model.Person import dev.usbharu.hideout.activitypub.domain.model.Person
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
@ -31,7 +32,7 @@ class UserAPControllerImpl(private val apUserService: APUserService) : UserAPCon
} catch (_: UserNotFoundException) { } catch (_: UserNotFoundException) {
return ResponseEntity.notFound().build() return ResponseEntity.notFound().build()
} }
person.context += listOf("https://www.w3.org/ns/activitystreams") person.context += Constant.context
return ResponseEntity(person, HttpStatus.OK) return ResponseEntity(person, HttpStatus.OK)
} }
} }

View File

@ -17,6 +17,8 @@
package dev.usbharu.hideout.activitypub.service.common package dev.usbharu.hideout.activitypub.service.common
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.activitypub.domain.Constant
import dev.usbharu.hideout.activitypub.domain.model.StringOrObject
import dev.usbharu.hideout.activitypub.domain.model.objects.Object import dev.usbharu.hideout.activitypub.domain.model.objects.Object
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.util.Base64Util import dev.usbharu.hideout.util.Base64Util
@ -74,7 +76,7 @@ class APRequestServiceImpl(
date: String, date: String,
u: URL, u: URL,
signer: Actor, signer: Actor,
url: String url: String,
): HttpResponse { ): HttpResponse {
val headers = headers { val headers = headers {
append("Accept", Activity) append("Accept", Activity)
@ -117,7 +119,7 @@ class APRequestServiceImpl(
url: String, url: String,
body: T?, body: T?,
signer: Actor?, signer: Actor?,
responseClass: Class<R> responseClass: Class<R>,
): R { ): R {
val bodyAsText = apPost(url, body, signer) val bodyAsText = apPost(url, body, signer)
return objectMapper.readValue(bodyAsText, responseClass) return objectMapper.readValue(bodyAsText, responseClass)
@ -167,7 +169,7 @@ class APRequestServiceImpl(
url: String, url: String,
date: String?, date: String?,
digest: String, digest: String,
requestBody: String? requestBody: String?,
) = httpClient.post(url) { ) = httpClient.post(url) {
accept(Activity) accept(Activity)
header("Date", date) header("Date", date)
@ -183,7 +185,7 @@ class APRequestServiceImpl(
u: URL, u: URL,
digest: String, digest: String,
signer: Actor, signer: Actor,
requestBody: String? requestBody: String?,
): HttpResponse { ): HttpResponse {
val headers = headers { val headers = headers {
append("Accept", Activity) append("Accept", Activity)
@ -218,10 +220,10 @@ class APRequestServiceImpl(
} }
private fun <T : Object> addContextIfNotNull(body: T?) = if (body != null) { private fun <T : Object> addContextIfNotNull(body: T?) = if (body != null) {
val mutableListOf = mutableListOf<String>() val context = mutableListOf<StringOrObject>()
mutableListOf.add("https://www.w3.org/ns/activitystreams") context.addAll(Constant.context)
mutableListOf.addAll(body.context) context.addAll(body.context)
body.context = mutableListOf body.context = context
objectMapper.writeValueAsString(body) objectMapper.writeValueAsString(body)
} else { } else {
null null

View File

@ -22,7 +22,10 @@ import com.fasterxml.jackson.annotation.Nulls
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import dev.usbharu.hideout.activitypub.domain.model.StringORObjectSerializer
import dev.usbharu.hideout.activitypub.domain.model.StringOrObject
import dev.usbharu.hideout.core.infrastructure.httpsignature.HttpRequestMixIn import dev.usbharu.hideout.core.infrastructure.httpsignature.HttpRequestMixIn
import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.HttpRequest
import dev.usbharu.httpsignature.sign.HttpSignatureSigner import dev.usbharu.httpsignature.sign.HttpSignatureSigner
@ -39,7 +42,11 @@ class ActivityPubConfig {
@Bean @Bean
@Qualifier("activitypub") @Qualifier("activitypub")
fun objectMapper(): ObjectMapper { fun objectMapper(): ObjectMapper {
val module = SimpleModule().addSerializer(StringOrObject::class.java, StringORObjectSerializer())
val objectMapper = jacksonObjectMapper() val objectMapper = jacksonObjectMapper()
.registerModules(module)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY) .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) .setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP))
@ -49,6 +56,7 @@ class ActivityPubConfig {
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true) .configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true)
.addMixIn(HttpRequest::class.java, HttpRequestMixIn::class.java) .addMixIn(HttpRequest::class.java, HttpRequestMixIn::class.java)
return objectMapper return objectMapper
} }

View File

@ -17,11 +17,14 @@
package dev.usbharu.hideout.application.config package dev.usbharu.hideout.application.config
import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.module.SimpleModule
import com.nimbusds.jose.jwk.JWKSet import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jose.jwk.source.ImmutableJWKSet import com.nimbusds.jose.jwk.source.ImmutableJWKSet
import com.nimbusds.jose.jwk.source.JWKSource import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.proc.SecurityContext import com.nimbusds.jose.proc.SecurityContext
import dev.usbharu.hideout.activitypub.domain.model.StringORObjectSerializer
import dev.usbharu.hideout.activitypub.domain.model.StringOrObject
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.application.infrastructure.springframework.RoleHierarchyAuthorizationManagerFactory import dev.usbharu.hideout.application.infrastructure.springframework.RoleHierarchyAuthorizationManagerFactory
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
@ -295,13 +298,16 @@ class SecurityConfig {
@Primary @Primary
fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer { fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer {
return Jackson2ObjectMapperBuilderCustomizer { return Jackson2ObjectMapperBuilderCustomizer {
it.serializationInclusion(JsonInclude.Include.ALWAYS).serializers() it.serializationInclusion(JsonInclude.Include.ALWAYS)
.modulesToInstall(SimpleModule().addSerializer(StringOrObject::class.java, StringORObjectSerializer()))
.serializers()
} }
} }
@Bean @Bean
fun mappingJackson2HttpMessageConverter(): MappingJackson2HttpMessageConverter { fun mappingJackson2HttpMessageConverter(): MappingJackson2HttpMessageConverter {
val builder = Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL) val builder = Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL)
builder.modulesToInstall(SimpleModule().addSerializer(StringOrObject::class.java, StringORObjectSerializer()))
return MappingJackson2HttpMessageConverter(builder.build()) return MappingJackson2HttpMessageConverter(builder.build())
} }

View File

@ -17,6 +17,7 @@
package dev.usbharu.hideout.activitypub.domain.model package dev.usbharu.hideout.activitypub.domain.model
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.Constant
import dev.usbharu.hideout.application.config.ActivityPubConfig import dev.usbharu.hideout.application.config.ActivityPubConfig
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
@ -37,14 +38,7 @@ class DeleteSerializeTest {
"discoverable" : "toot:discoverable", "discoverable" : "toot:discoverable",
"schema" : "http://schema.org#", "schema" : "http://schema.org#",
"PropertyValue" : "schema:PropertyValue", "PropertyValue" : "schema:PropertyValue",
"value" : "schema:value", "value" : "schema:value"
"misskey" : "https://misskey-hub.net/ns#",
"_misskey_content" : "misskey:_misskey_content",
"_misskey_quote" : "misskey:_misskey_quote",
"_misskey_reaction" : "misskey:_misskey_reaction",
"_misskey_votes" : "misskey:_misskey_votes",
"isCat" : "misskey:isCat",
"vcard" : "http://www.w3.org/2006/vcard/ns#"
} ], } ],
"type" : "Delete", "type" : "Delete",
"actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6", "actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6",
@ -69,7 +63,7 @@ class DeleteSerializeTest {
), ),
published = "2023-11-02T15:30:34.160Z", published = "2023-11-02T15:30:34.160Z",
) )
expected.context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", "") expected.context = Constant.context
assertEquals(expected, readValue) assertEquals(expected, readValue)
} }

View File

@ -31,7 +31,7 @@ class JsonLdSerializeTest {
val readValue = objectMapper.readValue<JsonLd>(json) val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(JsonLd(listOf("https://example.com")), readValue) assertEquals(JsonLd(listOf(StringOrObject("https://example.com"))), readValue)
} }
@Test @Test
@ -43,7 +43,14 @@ class JsonLdSerializeTest {
val readValue = objectMapper.readValue<JsonLd>(json) val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue) assertEquals(
JsonLd(
listOf(
StringOrObject("https://example.com"),
StringOrObject("https://www.w3.org/ns/activitystreams")
)
), readValue
)
} }
@Test @Test
@ -67,7 +74,14 @@ class JsonLdSerializeTest {
val readValue = objectMapper.readValue<JsonLd>(json) val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue) assertEquals(
JsonLd(
listOf(
StringOrObject("https://example.com"),
StringOrObject("https://www.w3.org/ns/activitystreams")
)
), readValue
)
} }
@Test @Test
@ -79,7 +93,7 @@ class JsonLdSerializeTest {
val readValue = objectMapper.readValue<JsonLd>(json) val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(JsonLd(emptyList()), readValue) assertEquals(JsonLd(listOf(StringOrObject(mapOf("hoge" to "fuga")))), readValue)
} }
@Test @Test
@ -91,7 +105,15 @@ class JsonLdSerializeTest {
val readValue = objectMapper.readValue<JsonLd>(json) val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue) assertEquals(
JsonLd(
listOf(
StringOrObject("https://example.com"),
StringOrObject(mapOf("hoge" to "fuga")),
StringOrObject("https://www.w3.org/ns/activitystreams")
)
), readValue
)
} }
@Test @Test
@ -130,7 +152,7 @@ class JsonLdSerializeTest {
@Test @Test
fun contextが文字列のとき文字列としてシリアライズされる() { fun contextが文字列のとき文字列としてシリアライズされる() {
val jsonLd = JsonLd(listOf("https://example.com")) val jsonLd = JsonLd(listOf(StringOrObject("https://example.com")))
val objectMapper = ActivityPubConfig().objectMapper() val objectMapper = ActivityPubConfig().objectMapper()
@ -141,7 +163,12 @@ class JsonLdSerializeTest {
@Test @Test
fun contextが文字列の配列のとき配列としてシリアライズされる() { fun contextが文字列の配列のとき配列としてシリアライズされる() {
val jsonLd = JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")) val jsonLd = JsonLd(
listOf(
StringOrObject("https://example.com"),
StringOrObject("https://www.w3.org/ns/activitystreams")
)
)
val objectMapper = ActivityPubConfig().objectMapper() val objectMapper = ActivityPubConfig().objectMapper()
@ -149,4 +176,74 @@ class JsonLdSerializeTest {
assertEquals("""{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""", actual) assertEquals("""{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""", actual)
} }
@Test
fun contextがオブジェクトのときシリアライズできる() {
val jsonLd = JsonLd(
listOf(
StringOrObject(mapOf("hoge" to "fuga"))
)
)
val objectMapper = ActivityPubConfig().objectMapper()
val actual = objectMapper.writeValueAsString(jsonLd)
assertEquals("""{"@context":{"hoge":"fuga"}}""", actual)
}
@Test
fun contextが複数のオブジェクトのときシリアライズできる() {
val jsonLd = JsonLd(
listOf(
StringOrObject(mapOf("hoge" to "fuga")),
StringOrObject(mapOf("foo" to "bar"))
)
)
val objectMapper = ActivityPubConfig().objectMapper()
val actual = objectMapper.writeValueAsString(jsonLd)
assertEquals("""{"@context":[{"hoge":"fuga"},{"foo":"bar"}]}""", actual)
}
@Test
fun contextが複数のオブジェクトのときデシリアライズできる() {
//language=JSON
val json = """{"@context":["https://example.com",{"hoge": "fuga"},{"foo": "bar"}]}"""
val objectMapper = ActivityPubConfig().objectMapper()
val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(
JsonLd(
listOf(
StringOrObject("https://example.com"),
StringOrObject(mapOf("hoge" to "fuga")),
StringOrObject(mapOf("foo" to "bar"))
)
), readValue
)
}
@Test
fun contextがオブジェクトのときデシリアライズできる() {
//language=JSON
val json = """{"@context":{"hoge": "fuga"}}"""
val objectMapper = ActivityPubConfig().objectMapper()
val readValue = objectMapper.readValue<JsonLd>(json)
assertEquals(
JsonLd(
listOf(
StringOrObject(mapOf("hoge" to "fuga"))
)
), readValue
)
}
} }

View File

@ -17,6 +17,7 @@
package dev.usbharu.hideout.activitypub.domain.model package dev.usbharu.hideout.activitypub.domain.model
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.Constant
import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public
import dev.usbharu.hideout.application.config.ActivityPubConfig import dev.usbharu.hideout.application.config.ActivityPubConfig
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -108,15 +109,7 @@ class NoteSerializeTest {
"discoverable": "toot:discoverable", "discoverable": "toot:discoverable",
"schema": "http://schema.org#", "schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue", "PropertyValue": "schema:PropertyValue",
"value": "schema:value", "value": "schema:value"
"misskey": "https://misskey-hub.net/ns#",
"_misskey_content": "misskey:_misskey_content",
"_misskey_quote": "misskey:_misskey_quote",
"_misskey_reaction": "misskey:_misskey_reaction",
"_misskey_votes": "misskey:_misskey_votes",
"_misskey_summary": "misskey:_misskey_summary",
"isCat": "misskey:isCat",
"vcard": "http://www.w3.org/2006/vcard/ns#"
} }
], ],
"id": "https://misskey.usbharu.dev/notes/9nj1omt1rn", "id": "https://misskey.usbharu.dev/notes/9nj1omt1rn",
@ -182,10 +175,7 @@ class NoteSerializeTest {
) )
) )
expected.context = listOf( expected.context = Constant.context
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
)
val note = objectMapper.readValue<Note>(json) val note = objectMapper.readValue<Note>(json)

View File

@ -17,6 +17,7 @@
package dev.usbharu.hideout.activitypub.domain.model package dev.usbharu.hideout.activitypub.domain.model
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.Constant
import dev.usbharu.hideout.application.config.ActivityPubConfig import dev.usbharu.hideout.application.config.ActivityPubConfig
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
@ -38,15 +39,7 @@ class RejectTest {
"discoverable" : "toot:discoverable", "discoverable" : "toot:discoverable",
"schema" : "http://schema.org#", "schema" : "http://schema.org#",
"PropertyValue" : "schema:PropertyValue", "PropertyValue" : "schema:PropertyValue",
"value" : "schema:value", "value" : "schema:value"
"misskey" : "https://misskey-hub.net/ns#",
"_misskey_content" : "misskey:_misskey_content",
"_misskey_quote" : "misskey:_misskey_quote",
"_misskey_reaction" : "misskey:_misskey_reaction",
"_misskey_votes" : "misskey:_misskey_votes",
"_misskey_summary" : "misskey:_misskey_summary",
"isCat" : "misskey:isCat",
"vcard" : "http://www.w3.org/2006/vcard/ns#"
} ], } ],
"type" : "Reject", "type" : "Reject",
"actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6", "actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6",
@ -72,7 +65,9 @@ class RejectTest {
actor = "https://test-hideout.usbharu.dev/users/test-user2", actor = "https://test-hideout.usbharu.dev/users/test-user2",
id = "https://misskey.usbharu.dev/follows/9mxh6mawru/97ws8y3rj6" id = "https://misskey.usbharu.dev/follows/9mxh6mawru/97ws8y3rj6"
) )
).apply { context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1") } ).apply {
context = Constant.context
}
assertThat(reject).isEqualTo(expected) assertThat(reject).isEqualTo(expected)
} }
@ -88,7 +83,12 @@ class RejectTest {
apObject = "https://misskey.usbharu.dev/users/97ws8y3rj6", apObject = "https://misskey.usbharu.dev/users/97ws8y3rj6",
actor = "https://test-hideout.usbharu.dev/users/test-user2" actor = "https://test-hideout.usbharu.dev/users/test-user2"
) )
).apply { context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1") } ).apply {
context = listOf(
StringOrObject("https://www.w3.org/ns/activitystreams"),
StringOrObject("https://w3id.org/security/v1")
)
}
val objectMapper = ActivityPubConfig().objectMapper() val objectMapper = ActivityPubConfig().objectMapper()

View File

@ -33,6 +33,7 @@ import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post import org.springframework.test.web.servlet.post
@ -51,7 +52,10 @@ class ActorAPControllerImplTest {
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(userAPControllerImpl).build() mockMvc = MockMvcBuilders
.standaloneSetup(userAPControllerImpl)
.setMessageConverters(MappingJackson2HttpMessageConverter(ActivityPubConfig().objectMapper()))
.build()
} }
@Test @Test
@ -85,6 +89,7 @@ class ActorAPControllerImplTest {
mockMvc mockMvc
.get("/users/hoge") .get("/users/hoge")
.asyncDispatch() .asyncDispatch()
.andDo { print() }
.andExpect { status { isOk() } } .andExpect { status { isOk() } }
.andExpect { content { this.json(objectMapper.writeValueAsString(person)) } } .andExpect { content { this.json(objectMapper.writeValueAsString(person)) } }
} }

View File

@ -17,7 +17,10 @@
package dev.usbharu.hideout.activitypub.service.common package dev.usbharu.hideout.activitypub.service.common
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.Constant
import dev.usbharu.hideout.activitypub.domain.model.Follow import dev.usbharu.hideout.activitypub.domain.model.Follow
import dev.usbharu.hideout.activitypub.domain.model.StringOrObject
import dev.usbharu.hideout.application.config.ActivityPubConfig
import dev.usbharu.hideout.util.Base64Util import dev.usbharu.hideout.util.Base64Util
import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpHeaders
import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpMethod
@ -35,7 +38,6 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq import org.mockito.kotlin.eq
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import utils.JsonObjectMapper.objectMapper
import utils.UserBuilder import utils.UserBuilder
import java.net.URL import java.net.URL
import java.security.MessageDigest import java.security.MessageDigest
@ -57,7 +59,7 @@ class APRequestServiceImplTest {
} }
respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""") respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""")
}), }),
objectMapper, ActivityPubConfig().objectMapper(),
mock(), mock(),
dateTimeFormatter dateTimeFormatter
) )
@ -82,7 +84,7 @@ class APRequestServiceImplTest {
} }
respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""") respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""")
}), }),
objectMapper, ActivityPubConfig().objectMapper(),
mock(), mock(),
dateTimeFormatter dateTimeFormatter
) )
@ -122,7 +124,7 @@ class APRequestServiceImplTest {
} }
respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""") respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""")
}), }),
objectMapper, ActivityPubConfig().objectMapper(),
httpSignatureSigner, httpSignatureSigner,
dateTimeFormatter dateTimeFormatter
) )
@ -171,12 +173,12 @@ class APRequestServiceImplTest {
fun `apPost bodyがnullでないときcontextにactivitystreamのURLを追加する`() = runTest { fun `apPost bodyがnullでないときcontextにactivitystreamのURLを追加する`() = runTest {
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
val readValue = objectMapper.readValue<Follow>(it.body.toByteArray()) val readValue = ActivityPubConfig().objectMapper().readValue<Follow>(it.body.toByteArray())
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams") assertThat(readValue.context).containsAll(Constant.context)
respondOk("{}") respondOk("{}")
}), objectMapper, mock(), dateTimeFormatter) }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter)
val body = Follow( val body = Follow(
apObject = "https://example.com", apObject = "https://example.com",
@ -193,7 +195,7 @@ class APRequestServiceImplTest {
assertEquals(0, it.body.toByteArray().size) assertEquals(0, it.body.toByteArray().size)
respondOk("{}") respondOk("{}")
}), objectMapper, mock(), dateTimeFormatter) }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter)
apRequestServiceImpl.apPost("https://example.com", null, null) apRequestServiceImpl.apPost("https://example.com", null, null)
} }
@ -203,9 +205,9 @@ class APRequestServiceImplTest {
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
val src = it.body.toByteArray() val src = it.body.toByteArray()
val readValue = objectMapper.readValue<Follow>(src) val readValue = ActivityPubConfig().objectMapper().readValue<Follow>(src)
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams") assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams"))
val map = it.headers.toMap() val map = it.headers.toMap()
assertThat(map).containsKey("Date") assertThat(map).containsKey("Date")
@ -222,7 +224,7 @@ class APRequestServiceImplTest {
assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last()) assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last())
respondOk("{}") respondOk("{}")
}), objectMapper, mock(), dateTimeFormatter) }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter)
val body = Follow( val body = Follow(
apObject = "https://example.com", apObject = "https://example.com",
@ -236,9 +238,9 @@ class APRequestServiceImplTest {
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
val src = it.body.toByteArray() val src = it.body.toByteArray()
val readValue = objectMapper.readValue<Follow>(src) val readValue = ActivityPubConfig().objectMapper().readValue<Follow>(src)
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams") assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams"))
val map = it.headers.toMap() val map = it.headers.toMap()
assertThat(map).containsKey("Date") assertThat(map).containsKey("Date")
@ -252,7 +254,7 @@ class APRequestServiceImplTest {
assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last()) assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last())
respondOk("{}") respondOk("{}")
}), objectMapper, mock(), dateTimeFormatter) }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter)
val body = Follow( val body = Follow(
apObject = "https://example.com", apObject = "https://example.com",
@ -277,9 +279,9 @@ class APRequestServiceImplTest {
} }
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
val src = it.body.toByteArray() val src = it.body.toByteArray()
val readValue = objectMapper.readValue<Follow>(src) val readValue = ActivityPubConfig().objectMapper().readValue<Follow>(src)
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams") assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams"))
val map = it.headers.toMap() val map = it.headers.toMap()
assertThat(map).containsKey("Date") assertThat(map).containsKey("Date")
@ -293,7 +295,7 @@ class APRequestServiceImplTest {
assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last()) assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last())
respondOk("{}") respondOk("{}")
}), objectMapper, httpSignatureSigner, dateTimeFormatter) }), ActivityPubConfig().objectMapper(), httpSignatureSigner, dateTimeFormatter)
val body = Follow( val body = Follow(
apObject = "https://example.com", apObject = "https://example.com",
@ -338,12 +340,12 @@ class APRequestServiceImplTest {
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
val src = it.body.toByteArray() val src = it.body.toByteArray()
val readValue = objectMapper.readValue<Follow>(src) val readValue = ActivityPubConfig().objectMapper().readValue<Follow>(src)
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams") assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams"))
respondOk(src.decodeToString()) respondOk(src.decodeToString())
}), objectMapper, mock(), dateTimeFormatter) }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter)
val body = Follow( val body = Follow(
apObject = "https://example.com", apObject = "https://example.com",