mirror of https://github.com/usbharu/Hideout.git
wip
This commit is contained in:
parent
d5cb840270
commit
71eeb47169
|
@ -17,7 +17,6 @@
|
||||||
package mastodon.apps
|
package mastodon.apps
|
||||||
|
|
||||||
import dev.usbharu.hideout.SpringApplication
|
import dev.usbharu.hideout.SpringApplication
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient
|
|
||||||
import dev.usbharu.owl.producer.api.OwlProducer
|
import dev.usbharu.owl.producer.api.OwlProducer
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
@ -30,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
|
||||||
import org.springframework.security.test.context.support.WithAnonymousUser
|
import org.springframework.security.test.context.support.WithAnonymousUser
|
||||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers
|
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
|
|
||||||
import software.amazon.awssdk.regions.Region
|
|
||||||
import software.amazon.awssdk.services.s3.S3Client
|
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class AwsConfig {
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty("hideout.storage.type", havingValue = "s3")
|
|
||||||
fun s3Client(awsConfig: S3StorageConfig): S3Client {
|
|
||||||
return S3Client.builder()
|
|
||||||
.endpointOverride(URI.create(awsConfig.endpoint))
|
|
||||||
.region(Region.of(awsConfig.region))
|
|
||||||
.credentialsProvider { AwsBasicCredentials.create(awsConfig.accessKey, awsConfig.secretKey) }
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package dev.usbharu.hideout.application.config
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.security")
|
|
||||||
data class CaptchaConfig(
|
|
||||||
val reCaptchaSiteKey: String?,
|
|
||||||
val reCaptchaSecretKey: String?
|
|
||||||
)
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.client.plugins.*
|
|
||||||
import io.ktor.client.plugins.cache.*
|
|
||||||
import io.ktor.client.plugins.logging.*
|
|
||||||
import org.springframework.boot.info.BuildProperties
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class HttpClientConfig {
|
|
||||||
@Bean
|
|
||||||
fun httpClient(buildProperties: BuildProperties, applicationConfig: ApplicationConfig): HttpClient =
|
|
||||||
HttpClient(CIO).config {
|
|
||||||
install(Logging) {
|
|
||||||
logger = Logger.DEFAULT
|
|
||||||
level = LogLevel.ALL
|
|
||||||
}
|
|
||||||
install(HttpCache) {
|
|
||||||
}
|
|
||||||
expectSuccess = true
|
|
||||||
install(UserAgent) {
|
|
||||||
agent = "Hideout/${buildProperties.version} (${applicationConfig.url})"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.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)
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import jakarta.servlet.Filter
|
|
||||||
import jakarta.servlet.FilterChain
|
|
||||||
import jakarta.servlet.ServletRequest
|
|
||||||
import jakarta.servlet.ServletResponse
|
|
||||||
import org.slf4j.MDC
|
|
||||||
import org.springframework.boot.autoconfigure.security.SecurityProperties
|
|
||||||
import org.springframework.core.annotation.Order
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Order(SecurityProperties.DEFAULT_FILTER_ORDER - 1)
|
|
||||||
class MdcXrequestIdFilter : Filter {
|
|
||||||
override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain) {
|
|
||||||
val uuid = UUID.randomUUID()
|
|
||||||
try {
|
|
||||||
MDC.put(KEY, uuid.toString())
|
|
||||||
chain.doFilter(request, response)
|
|
||||||
} finally {
|
|
||||||
MDC.remove(KEY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val KEY = "x-request-id"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.media")
|
|
||||||
data class MediaConfig(
|
|
||||||
val remoteMediaFileSizeLimit: Long = 3000000L
|
|
||||||
)
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter
|
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class MvcConfigurer(private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor) : WebMvcConfigurer {
|
|
||||||
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
|
|
||||||
resolvers.add(jsonOrFormModelMethodProcessor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class JsonOrFormModelMethodProcessorConfig {
|
|
||||||
@Bean
|
|
||||||
fun jsonOrFormModelMethodProcessor(converter: List<HttpMessageConverter<*>>): JsonOrFormModelMethodProcessor {
|
|
||||||
return JsonOrFormModelMethodProcessor(
|
|
||||||
ServletModelAttributeMethodProcessor(true),
|
|
||||||
RequestResponseBodyMethodProcessor(
|
|
||||||
converter
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import dev.usbharu.owl.broker.ModuleContext
|
|
||||||
import dev.usbharu.owl.common.property.*
|
|
||||||
import dev.usbharu.owl.common.retry.RetryPolicyFactory
|
|
||||||
import dev.usbharu.owl.producer.api.OWL
|
|
||||||
import dev.usbharu.owl.producer.api.OwlProducer
|
|
||||||
import dev.usbharu.owl.producer.defaultimpl.DEFAULT
|
|
||||||
import dev.usbharu.owl.producer.embedded.EMBEDDED
|
|
||||||
import dev.usbharu.owl.producer.embedded.EMBEDDED_GRPC
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class OwlConfig(private val producerConfig: ProducerConfig) {
|
|
||||||
@Bean
|
|
||||||
fun producer(
|
|
||||||
@Autowired(required = false) retryPolicyFactory: RetryPolicyFactory? = null,
|
|
||||||
@Qualifier("activitypub") objectMapper: ObjectMapper,
|
|
||||||
): OwlProducer {
|
|
||||||
return when (producerConfig.mode) {
|
|
||||||
ProducerMode.EMBEDDED -> {
|
|
||||||
OWL(EMBEDDED) {
|
|
||||||
if (retryPolicyFactory != null) {
|
|
||||||
this.retryPolicyFactory = retryPolicyFactory
|
|
||||||
}
|
|
||||||
if (producerConfig.port != null) {
|
|
||||||
this.port = producerConfig.port.toString()
|
|
||||||
}
|
|
||||||
val moduleContext = ServiceLoader.load(ModuleContext::class.java).firstOrNull()
|
|
||||||
if (moduleContext != null) {
|
|
||||||
this.moduleContext = moduleContext
|
|
||||||
}
|
|
||||||
this.propertySerializerFactory = CustomPropertySerializerFactory(
|
|
||||||
setOf(
|
|
||||||
IntegerPropertySerializer(),
|
|
||||||
StringPropertyValueSerializer(),
|
|
||||||
DoublePropertySerializer(),
|
|
||||||
BooleanPropertySerializer(),
|
|
||||||
LongPropertySerializer(),
|
|
||||||
FloatPropertySerializer(),
|
|
||||||
ObjectPropertySerializer(objectMapper),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProducerMode.GRPC -> {
|
|
||||||
OWL(EMBEDDED_GRPC) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProducerMode.EMBEDDED_GRPC -> {
|
|
||||||
OWL(DEFAULT) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.owl.producer")
|
|
||||||
data class ProducerConfig(val mode: ProducerMode = ProducerMode.EMBEDDED, val port: Int? = null)
|
|
||||||
|
|
||||||
enum class ProducerMode {
|
|
||||||
GRPC,
|
|
||||||
EMBEDDED,
|
|
||||||
EMBEDDED_GRPC
|
|
||||||
}
|
|
|
@ -1,319 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import com.nimbusds.jose.jwk.JWKSet
|
|
||||||
import com.nimbusds.jose.jwk.RSAKey
|
|
||||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet
|
|
||||||
import com.nimbusds.jose.jwk.source.JWKSource
|
|
||||||
import com.nimbusds.jose.proc.SecurityContext
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsImpl
|
|
||||||
import dev.usbharu.hideout.util.RsaUtil
|
|
||||||
import jakarta.annotation.PostConstruct
|
|
||||||
import jakarta.servlet.*
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.core.annotation.Order
|
|
||||||
import org.springframework.http.HttpMethod.GET
|
|
||||||
import org.springframework.http.HttpMethod.POST
|
|
||||||
import org.springframework.http.HttpStatus
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
|
||||||
import org.springframework.security.config.annotation.web.invoke
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy
|
|
||||||
import org.springframework.security.core.Authentication
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType
|
|
||||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer
|
|
||||||
import org.springframework.security.web.FilterChainProxy
|
|
||||||
import org.springframework.security.web.SecurityFilterChain
|
|
||||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint
|
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
|
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider
|
|
||||||
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer
|
|
||||||
import org.springframework.security.web.debug.DebugFilter
|
|
||||||
import org.springframework.security.web.firewall.HttpFirewall
|
|
||||||
import org.springframework.security.web.firewall.RequestRejectedHandler
|
|
||||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
|
||||||
import org.springframework.web.filter.CompositeFilter
|
|
||||||
import java.io.IOException
|
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.security.interfaces.RSAPrivateKey
|
|
||||||
import java.security.interfaces.RSAPublicKey
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@EnableWebSecurity(debug = false)
|
|
||||||
@Configuration
|
|
||||||
@Suppress("FunctionMaxLength", "TooManyFunctions", "LongMethod")
|
|
||||||
class SecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? =
|
|
||||||
authenticationConfiguration.authenticationManager
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(1)
|
|
||||||
fun httpSignatureFilterChain(
|
|
||||||
http: HttpSecurity,
|
|
||||||
): SecurityFilterChain {
|
|
||||||
http {
|
|
||||||
securityMatcher("/users/*/posts/*")
|
|
||||||
authorizeHttpRequests {
|
|
||||||
authorize(anyRequest, permitAll)
|
|
||||||
}
|
|
||||||
exceptionHandling {
|
|
||||||
authenticationEntryPoint = HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)
|
|
||||||
defaultAuthenticationEntryPointFor(
|
|
||||||
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
|
||||||
AnyRequestMatcher.INSTANCE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
sessionManagement {
|
|
||||||
sessionCreationPolicy = SessionCreationPolicy.STATELESS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return http.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(2)
|
|
||||||
fun oauth2SecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
|
||||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
|
|
||||||
http {
|
|
||||||
exceptionHandling {
|
|
||||||
authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/login")
|
|
||||||
}
|
|
||||||
oauth2ResourceServer {
|
|
||||||
jwt {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return http.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(5)
|
|
||||||
fun defaultSecurityFilterChain(
|
|
||||||
http: HttpSecurity,
|
|
||||||
): SecurityFilterChain {
|
|
||||||
http {
|
|
||||||
authorizeHttpRequests {
|
|
||||||
authorize("/error", permitAll)
|
|
||||||
authorize("/login", permitAll)
|
|
||||||
authorize(GET, "/.well-known/**", permitAll)
|
|
||||||
authorize(GET, "/nodeinfo/2.0", permitAll)
|
|
||||||
|
|
||||||
authorize(POST, "/inbox", permitAll)
|
|
||||||
authorize(POST, "/users/*/inbox", permitAll)
|
|
||||||
authorize(GET, "/users/*", permitAll)
|
|
||||||
authorize(GET, "/users/*/posts/*", permitAll)
|
|
||||||
|
|
||||||
authorize("/dev/usbharu/hideout/core/service/auth/sign_up", hasRole("ANONYMOUS"))
|
|
||||||
authorize(GET, "/files/*", permitAll)
|
|
||||||
authorize(GET, "/users/*/icon.jpg", permitAll)
|
|
||||||
authorize(GET, "/users/*/header.jpg", permitAll)
|
|
||||||
|
|
||||||
authorize(anyRequest, authenticated)
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth2ResourceServer {
|
|
||||||
jwt { }
|
|
||||||
}
|
|
||||||
|
|
||||||
formLogin {
|
|
||||||
}
|
|
||||||
|
|
||||||
csrf {
|
|
||||||
ignoringRequestMatchers("/users/*/inbox", "/inbox", "/api/v1/apps")
|
|
||||||
}
|
|
||||||
|
|
||||||
headers {
|
|
||||||
frameOptions {
|
|
||||||
sameOrigin = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return http.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "false", matchIfMissing = true)
|
|
||||||
fun genJwkSource(): JWKSource<SecurityContext> {
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
|
||||||
keyPairGenerator.initialize(2048)
|
|
||||||
val generateKeyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
val rsaPublicKey = generateKeyPair.public as RSAPublicKey
|
|
||||||
val rsaPrivateKey = generateKeyPair.private as RSAPrivateKey
|
|
||||||
val rsaKey = RSAKey.Builder(rsaPublicKey).privateKey(rsaPrivateKey).keyID(UUID.randomUUID().toString()).build()
|
|
||||||
|
|
||||||
val jwkSet = JWKSet(rsaKey)
|
|
||||||
return ImmutableJWKSet(jwkSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "")
|
|
||||||
fun loadJwkSource(jwkConfig: JwkConfig): JWKSource<SecurityContext> {
|
|
||||||
val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey))
|
|
||||||
.privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey)).keyID(jwkConfig.keyId).build()
|
|
||||||
return ImmutableJWKSet(JWKSet(rsaKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun jwtDecoder(jwkSource: JWKSource<SecurityContext>): JwtDecoder =
|
|
||||||
OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource)
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun authorizationServerSettings(): AuthorizationServerSettings {
|
|
||||||
return AuthorizationServerSettings.builder().authorizationEndpoint("/oauth/authorize")
|
|
||||||
.tokenEndpoint("/oauth/token").tokenRevocationEndpoint("/oauth/revoke").build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun jwtTokenCustomizer(): OAuth2TokenCustomizer<JwtEncodingContext> {
|
|
||||||
return OAuth2TokenCustomizer { context: JwtEncodingContext ->
|
|
||||||
|
|
||||||
if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType &&
|
|
||||||
context.authorization?.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE
|
|
||||||
) {
|
|
||||||
val userDetailsImpl = context.getPrincipal<Authentication>().principal as UserDetailsImpl
|
|
||||||
context.claims.claim("uid", userDetailsImpl.id.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Spring Security 3.2.1 に存在する EnableWebSecurity(debug = true)にすると発生するエラーに対処するためのコード
|
|
||||||
// trueにしないときはコメントアウト
|
|
||||||
|
|
||||||
// @Bean
|
|
||||||
fun beanDefinitionRegistryPostProcessor(): BeanDefinitionRegistryPostProcessor {
|
|
||||||
return BeanDefinitionRegistryPostProcessor { registry: BeanDefinitionRegistry ->
|
|
||||||
registry.getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME).beanClassName =
|
|
||||||
CompositeFilterChainProxy::class.java.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("ExpressionBodySyntax")
|
|
||||||
internal class CompositeFilterChainProxy(filters: List<Filter?>) : FilterChainProxy() {
|
|
||||||
private val doFilterDelegate: Filter
|
|
||||||
|
|
||||||
private val springSecurityFilterChain: FilterChainProxy
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.doFilterDelegate = createDoFilterDelegate(filters)
|
|
||||||
this.springSecurityFilterChain = findFilterChainProxy(filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterPropertiesSet() {
|
|
||||||
springSecurityFilterChain.afterPropertiesSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, ServletException::class)
|
|
||||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
|
||||||
doFilterDelegate.doFilter(request, response, chain)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilters(url: String): List<Filter> {
|
|
||||||
return springSecurityFilterChain.getFilters(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilterChains(): List<SecurityFilterChain> {
|
|
||||||
return springSecurityFilterChain.filterChains
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSecurityContextHolderStrategy(securityContextHolderStrategy: SecurityContextHolderStrategy) {
|
|
||||||
springSecurityFilterChain.setSecurityContextHolderStrategy(securityContextHolderStrategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFilterChainValidator(filterChainValidator: FilterChainValidator) {
|
|
||||||
springSecurityFilterChain.setFilterChainValidator(filterChainValidator)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFilterChainDecorator(filterChainDecorator: FilterChainDecorator) {
|
|
||||||
springSecurityFilterChain.setFilterChainDecorator(filterChainDecorator)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFirewall(firewall: HttpFirewall) {
|
|
||||||
springSecurityFilterChain.setFirewall(firewall)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setRequestRejectedHandler(requestRejectedHandler: RequestRejectedHandler) {
|
|
||||||
springSecurityFilterChain.setRequestRejectedHandler(requestRejectedHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun createDoFilterDelegate(filters: List<Filter?>): Filter {
|
|
||||||
val delegate: CompositeFilter = CompositeFilter()
|
|
||||||
delegate.setFilters(filters)
|
|
||||||
return delegate
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findFilterChainProxy(filters: List<Filter?>): FilterChainProxy {
|
|
||||||
for (filter in filters) {
|
|
||||||
if (filter is FilterChainProxy) {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
if (filter is DebugFilter) {
|
|
||||||
return filter.filterChainProxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw IllegalStateException("Couldn't find FilterChainProxy in $filters")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.security.jwt")
|
|
||||||
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "")
|
|
||||||
data class JwkConfig(
|
|
||||||
val keyId: String,
|
|
||||||
val publicKey: String,
|
|
||||||
val privateKey: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class PostSecurityConfig(
|
|
||||||
val auth: AuthenticationManagerBuilder,
|
|
||||||
val daoAuthenticationProvider: DaoAuthenticationProvider,
|
|
||||||
val httpSignatureAuthenticationProvider: PreAuthenticatedAuthenticationProvider,
|
|
||||||
) {
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
fun config() {
|
|
||||||
auth.authenticationProvider(daoAuthenticationProvider)
|
|
||||||
auth.authenticationProvider(httpSignatureAuthenticationProvider)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.config
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.web.filter.CommonsRequestLoggingFilter
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class SpringConfig {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
lateinit var config: ApplicationConfig
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun requestLoggingFilter(): CommonsRequestLoggingFilter {
|
|
||||||
val loggingFilter = CommonsRequestLoggingFilter()
|
|
||||||
loggingFilter.setIncludeHeaders(true)
|
|
||||||
loggingFilter.setIncludeClientInfo(true)
|
|
||||||
loggingFilter.setIncludeQueryString(true)
|
|
||||||
loggingFilter.setIncludePayload(true)
|
|
||||||
loggingFilter.setMaxPayloadLength(64000)
|
|
||||||
return loggingFilter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout")
|
|
||||||
data class ApplicationConfig(
|
|
||||||
val url: URL,
|
|
||||||
val private: Boolean = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.storage.s3")
|
|
||||||
@ConditionalOnProperty("hideout.storage.type", havingValue = "s3")
|
|
||||||
data class S3StorageConfig(
|
|
||||||
val endpoint: String,
|
|
||||||
val publicUrl: String,
|
|
||||||
val bucket: String,
|
|
||||||
val region: String,
|
|
||||||
val accessKey: String,
|
|
||||||
val secretKey: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* メディアの保存にローカルファイルシステムを使用する際のコンフィグ
|
|
||||||
*
|
|
||||||
* @property path フォゾンする場所へのパス。 /から始めると絶対パスとなります。
|
|
||||||
* @property publicUrl 公開用URL 省略可能 指定するとHideoutがファイルを配信しなくなります。
|
|
||||||
*/
|
|
||||||
@ConfigurationProperties("hideout.storage.local")
|
|
||||||
@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true)
|
|
||||||
data class LocalStorageConfig(
|
|
||||||
val path: String = "files",
|
|
||||||
val publicUrl: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.character-limit")
|
|
||||||
data class CharacterLimit(
|
|
||||||
val general: General = General(),
|
|
||||||
val post: Post = Post(),
|
|
||||||
val account: Account = Account(),
|
|
||||||
val instance: Instance = Instance()
|
|
||||||
) {
|
|
||||||
|
|
||||||
data class General(
|
|
||||||
val url: Int = 1000,
|
|
||||||
val domain: Int = 1000,
|
|
||||||
val publicKey: Int = 10000,
|
|
||||||
val privateKey: Int = 10000
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Post(
|
|
||||||
val text: Int = 3000,
|
|
||||||
val overview: Int = 3000
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Account(
|
|
||||||
val id: Int = 300,
|
|
||||||
val name: Int = 300,
|
|
||||||
val description: Int = 10000
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Instance(
|
|
||||||
val name: Int = 600,
|
|
||||||
val description: Int = 10000
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.external
|
|
||||||
|
|
||||||
import dev.usbharu.owl.common.task.TaskDefinition
|
|
||||||
import dev.usbharu.owl.producer.api.OwlProducer
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.springframework.beans.factory.DisposableBean
|
|
||||||
import org.springframework.boot.ApplicationArguments
|
|
||||||
import org.springframework.boot.ApplicationRunner
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class OwlProducerRunner(private val owlProducer: OwlProducer, private val taskDefinitions: List<TaskDefinition<*>>) :
|
|
||||||
ApplicationRunner, DisposableBean {
|
|
||||||
override fun run(args: ApplicationArguments?) {
|
|
||||||
runBlocking {
|
|
||||||
owlProducer.start()
|
|
||||||
taskDefinitions.forEach { taskDefinition -> owlProducer.registerTask(taskDefinition) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun destroy() {
|
|
||||||
runBlocking {
|
|
||||||
owlProducer.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.infrastructure.exposed
|
|
||||||
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
|
|
||||||
fun <S : Any> Query.withPagination(page: Page, exp: ExpressionWithColumnType<S>): PaginationList<ResultRow, S> {
|
|
||||||
page.limit?.let { limit(it) }
|
|
||||||
val resultRows = if (page.minId != null) {
|
|
||||||
page.maxId?.let { it: Long -> andWhere { exp.less(it) } }
|
|
||||||
andWhere { exp.greater(page.minId!!) }
|
|
||||||
reversed()
|
|
||||||
} else {
|
|
||||||
page.maxId?.let { andWhere { exp.less(it) } }
|
|
||||||
page.sinceId?.let { andWhere { exp.greater(it) } }
|
|
||||||
orderBy(exp, SortOrder.DESC)
|
|
||||||
toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
return PaginationList(resultRows, resultRows.firstOrNull()?.getOrNull(exp), resultRows.lastOrNull()?.getOrNull(exp))
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.infrastructure.exposed
|
|
||||||
|
|
||||||
sealed class Page {
|
|
||||||
abstract val maxId: Long?
|
|
||||||
abstract val sinceId: Long?
|
|
||||||
abstract val minId: Long?
|
|
||||||
abstract val limit: Int?
|
|
||||||
|
|
||||||
data class PageByMaxId(
|
|
||||||
override val maxId: Long?,
|
|
||||||
override val sinceId: Long?,
|
|
||||||
override val limit: Int?
|
|
||||||
) : Page() {
|
|
||||||
override val minId: Long? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
data class PageByMinId(
|
|
||||||
override val maxId: Long?,
|
|
||||||
override val minId: Long?,
|
|
||||||
override val limit: Int?
|
|
||||||
) : Page() {
|
|
||||||
override val sinceId: Long? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Suppress("FunctionMinLength")
|
|
||||||
fun of(
|
|
||||||
maxId: Long? = null,
|
|
||||||
sinceId: Long? = null,
|
|
||||||
minId: Long? = null,
|
|
||||||
limit: Int? = null
|
|
||||||
): Page =
|
|
||||||
if (minId != null) {
|
|
||||||
PageByMinId(
|
|
||||||
maxId,
|
|
||||||
minId,
|
|
||||||
limit
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
PageByMaxId(
|
|
||||||
maxId,
|
|
||||||
sinceId,
|
|
||||||
limit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.infrastructure.exposed
|
|
||||||
|
|
||||||
class PaginationList<T, ID>(list: List<T>, val next: ID?, val prev: ID?) : List<T> by list
|
|
||||||
|
|
||||||
fun <T, ID> PaginationList<T, ID>.toHttpHeader(
|
|
||||||
nextBlock: (string: String) -> String,
|
|
||||||
prevBlock: (string: String) -> String
|
|
||||||
): String? {
|
|
||||||
val mutableListOf = mutableListOf<String>()
|
|
||||||
if (next != null) {
|
|
||||||
mutableListOf.add("<${nextBlock(this.next.toString())}>; rel=\"next\"")
|
|
||||||
}
|
|
||||||
if (prev != null) {
|
|
||||||
mutableListOf.add("<${prevBlock(this.prev.toString())}>; rel=\"prev\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutableListOf.isEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return mutableListOf.joinToString(", ")
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.application.infrastructure.springframework
|
|
||||||
|
|
||||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
|
|
||||||
import org.springframework.security.authorization.AuthorityAuthorizationManager
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager
|
|
||||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class RoleHierarchyAuthorizationManagerFactory(private val roleHierarchy: RoleHierarchy) {
|
|
||||||
fun hasScope(role: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
val hasAuthority = AuthorityAuthorizationManager.hasAuthority<RequestAuthorizationContext>("SCOPE_$role")
|
|
||||||
hasAuthority.setRoleHierarchy(roleHierarchy)
|
|
||||||
return hasAuthority
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@
|
||||||
package dev.usbharu.hideout.core.application.actor
|
package dev.usbharu.hideout.core.application.actor
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
|
||||||
|
@ -26,6 +25,7 @@ import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
||||||
import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorDomainService
|
import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorDomainService
|
||||||
import dev.usbharu.hideout.core.domain.service.userdetail.UserDetailDomainService
|
import dev.usbharu.hideout.core.domain.service.userdetail.UserDetailDomainService
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import dev.usbharu.hideout.core.infrastructure.factory.ActorFactoryImpl
|
import dev.usbharu.hideout.core.infrastructure.factory.ActorFactoryImpl
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.config
|
package dev.usbharu.hideout.core.config
|
||||||
|
|
||||||
import org.owasp.html.HtmlPolicyBuilder
|
import org.owasp.html.HtmlPolicyBuilder
|
||||||
import org.owasp.html.PolicyFactory
|
import org.owasp.html.PolicyFactory
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.service.post
|
package dev.usbharu.hideout.core.domain.service.post
|
||||||
|
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
@ -24,11 +24,6 @@ import org.jsoup.select.Elements
|
||||||
import org.owasp.html.PolicyFactory
|
import org.owasp.html.PolicyFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
|
||||||
interface PostContentFormatter {
|
|
||||||
fun format(content: String): FormattedPostContent
|
|
||||||
}
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : PostContentFormatter {
|
class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : PostContentFormatter {
|
||||||
override fun format(content: String): FormattedPostContent {
|
override fun format(content: String): FormattedPostContent {
|
||||||
|
@ -102,8 +97,3 @@ class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : Po
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FormattedPostContent(
|
|
||||||
val html: String,
|
|
||||||
val content: String,
|
|
||||||
)
|
|
|
@ -14,10 +14,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.infrastructure.springframework.security
|
package dev.usbharu.hideout.core.domain.service.post
|
||||||
|
|
||||||
interface LoginUserContextHolder {
|
|
||||||
fun getLoginUserId(): Long
|
|
||||||
|
|
||||||
fun getLoginUserIdOrNull(): Long?
|
interface PostContentFormatter {
|
||||||
|
fun format(content: String): FormattedPostContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class FormattedPostContent(
|
||||||
|
val html: String,
|
||||||
|
val content: String,
|
||||||
|
)
|
|
@ -14,11 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.service.id
|
package dev.usbharu.hideout.core.domain.shared.id
|
||||||
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
@Service
|
|
||||||
interface IdGenerateService {
|
interface IdGenerateService {
|
||||||
suspend fun generateId(): Long
|
suspend fun generateId(): Long
|
||||||
}
|
}
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
|
|
||||||
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.*
|
import dev.usbharu.hideout.core.domain.model.actor.*
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
|
|
|
@ -14,30 +14,29 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.slf4j.MDCContext
|
import kotlinx.coroutines.slf4j.MDCContext
|
||||||
import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger
|
import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger
|
||||||
import org.jetbrains.exposed.sql.addLogger
|
import org.jetbrains.exposed.sql.addLogger
|
||||||
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
|
||||||
@Service
|
@Component
|
||||||
class ExposedTransaction : Transaction {
|
class ExposedTransaction : Transaction {
|
||||||
override suspend fun <T> transaction(block: suspend () -> T): T {
|
override suspend fun <T> transaction(block: suspend () -> T): T {
|
||||||
return transaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
|
return newSuspendedTransaction(
|
||||||
|
transactionIsolation = Connection.TRANSACTION_READ_COMMITTED,
|
||||||
|
context = MDCContext()
|
||||||
|
) {
|
||||||
debug = true
|
debug = true
|
||||||
warnLongQueriesDuration = 1000
|
warnLongQueriesDuration = 1000
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
runBlocking(MDCContext()) {
|
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T {
|
override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T {
|
||||||
return newSuspendedTransaction(MDCContext(), transactionIsolation = transactionLevel) {
|
return newSuspendedTransaction(MDCContext(), transactionIsolation = transactionLevel) {
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import org.jetbrains.exposed.sql.Query
|
import org.jetbrains.exposed.sql.Query
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.*
|
import dev.usbharu.hideout.core.domain.model.actor.*
|
||||||
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
|
||||||
import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository
|
import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposed.QueryMapper
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.factory
|
package dev.usbharu.hideout.core.infrastructure.factory
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.*
|
import dev.usbharu.hideout.core.domain.model.actor.*
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.factory
|
package dev.usbharu.hideout.core.infrastructure.factory
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostContent
|
import dev.usbharu.hideout.core.domain.model.post.PostContent
|
||||||
import dev.usbharu.hideout.core.service.post.PostContentFormatter
|
import dev.usbharu.hideout.core.domain.service.post.PostContentFormatter
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.factory
|
package dev.usbharu.hideout.core.infrastructure.factory
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorName
|
import dev.usbharu.hideout.core.domain.model.actor.ActorName
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
|
@ -25,6 +24,7 @@ import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostOverview
|
import dev.usbharu.hideout.core.domain.model.post.PostOverview
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.core.infrastructure.httpsignature
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
|
||||||
import dev.usbharu.httpsignature.common.HttpHeaders
|
|
||||||
import dev.usbharu.httpsignature.common.HttpMethod
|
|
||||||
import dev.usbharu.httpsignature.common.HttpRequest
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
@JsonDeserialize(using = HttpRequestDeserializer::class)
|
|
||||||
@JsonSubTypes
|
|
||||||
@Suppress("UnnecessaryAbstractClass")
|
|
||||||
abstract class HttpRequestMixIn
|
|
||||||
|
|
||||||
class HttpRequestDeserializer : JsonDeserializer<HttpRequest>() {
|
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): HttpRequest {
|
|
||||||
val readTree: JsonNode = p.codec.readTree(p)
|
|
||||||
|
|
||||||
return HttpRequest(
|
|
||||||
URL(readTree["url"].textValue()),
|
|
||||||
HttpHeaders(emptyMap()),
|
|
||||||
HttpMethod.valueOf(readTree["method"].textValue())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,15 +14,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.service.id
|
package dev.usbharu.hideout.core.infrastructure.other
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
|
open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
|
||||||
var lastTimeStamp: Long = -1
|
var lastTimeStamp: Long = -1
|
||||||
var sequenceId: Int = 0
|
var sequenceId: Int = 0
|
||||||
val mutex = Mutex()
|
val mutex = Mutex()
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.application.service.id
|
package dev.usbharu.hideout.core.infrastructure.other
|
||||||
|
|
||||||
import org.springframework.context.annotation.Primary
|
import org.springframework.context.annotation.Primary
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
|
@ -1,97 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.core.infrastructure.springframework.oauth2
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent as AuthorizationConsent
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class ExposedOAuth2AuthorizationConsentService(
|
|
||||||
private val registeredClientRepository: RegisteredClientRepository,
|
|
||||||
private val transaction: Transaction,
|
|
||||||
) :
|
|
||||||
OAuth2AuthorizationConsentService {
|
|
||||||
|
|
||||||
override fun save(authorizationConsent: AuthorizationConsent?): Unit = runBlocking {
|
|
||||||
requireNotNull(authorizationConsent)
|
|
||||||
transaction.transaction {
|
|
||||||
val singleOrNull =
|
|
||||||
OAuth2AuthorizationConsent.selectAll().where {
|
|
||||||
OAuth2AuthorizationConsent.registeredClientId
|
|
||||||
.eq(authorizationConsent.registeredClientId)
|
|
||||||
.and(OAuth2AuthorizationConsent.principalName.eq(authorizationConsent.principalName))
|
|
||||||
}
|
|
||||||
.singleOrNull()
|
|
||||||
if (singleOrNull == null) {
|
|
||||||
OAuth2AuthorizationConsent.insert {
|
|
||||||
it[registeredClientId] = authorizationConsent.registeredClientId
|
|
||||||
it[principalName] = authorizationConsent.principalName
|
|
||||||
it[authorities] = authorizationConsent.authorities.joinToString(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(authorizationConsent: AuthorizationConsent?) {
|
|
||||||
if (authorizationConsent == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
OAuth2AuthorizationConsent.deleteWhere {
|
|
||||||
registeredClientId eq authorizationConsent.registeredClientId and (principalName eq principalName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findById(registeredClientId: String?, principalName: String?): AuthorizationConsent? = runBlocking {
|
|
||||||
requireNotNull(registeredClientId)
|
|
||||||
requireNotNull(principalName)
|
|
||||||
transaction.transaction {
|
|
||||||
OAuth2AuthorizationConsent.selectAll().where {
|
|
||||||
(OAuth2AuthorizationConsent.registeredClientId eq registeredClientId)
|
|
||||||
.and(OAuth2AuthorizationConsent.principalName eq principalName)
|
|
||||||
}
|
|
||||||
.singleOrNull()?.toAuthorizationConsent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ResultRow.toAuthorizationConsent(): AuthorizationConsent {
|
|
||||||
val registeredClientId = this[OAuth2AuthorizationConsent.registeredClientId]
|
|
||||||
registeredClientRepository.findById(registeredClientId)
|
|
||||||
|
|
||||||
val principalName = this[OAuth2AuthorizationConsent.principalName]
|
|
||||||
val builder = AuthorizationConsent.withId(registeredClientId, principalName)
|
|
||||||
|
|
||||||
this[OAuth2AuthorizationConsent.authorities].split(",").forEach {
|
|
||||||
builder.authority(SimpleGrantedAuthority(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object OAuth2AuthorizationConsent : Table("oauth2_authorization_consent") {
|
|
||||||
val registeredClientId: Column<String> = varchar("registered_client_id", 100)
|
|
||||||
val principalName: Column<String> = varchar("principal_name", 200)
|
|
||||||
val authorities: Column<String> = varchar("authorities", 1000)
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(registeredClientId, principalName)
|
|
||||||
}
|
|
|
@ -1,393 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.core.infrastructure.springframework.oauth2
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
||||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
|
||||||
import org.springframework.security.jackson2.CoreJackson2Module
|
|
||||||
import org.springframework.security.jackson2.SecurityJackson2Modules
|
|
||||||
import org.springframework.security.oauth2.core.*
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
|
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken
|
|
||||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
|
||||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class ExposedOAuth2AuthorizationService(
|
|
||||||
private val registeredClientRepository: RegisteredClientRepository,
|
|
||||||
private val transaction: Transaction,
|
|
||||||
) :
|
|
||||||
OAuth2AuthorizationService {
|
|
||||||
|
|
||||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
|
||||||
override fun save(authorization: OAuth2Authorization?): Unit = runBlocking {
|
|
||||||
requireNotNull(authorization)
|
|
||||||
transaction.transaction {
|
|
||||||
val singleOrNull = Authorization.selectAll().where { Authorization.id eq authorization.id }.singleOrNull()
|
|
||||||
if (singleOrNull == null) {
|
|
||||||
val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java)
|
|
||||||
val accessToken = authorization.getToken(OAuth2AccessToken::class.java)
|
|
||||||
val refreshToken = authorization.getToken(OAuth2RefreshToken::class.java)
|
|
||||||
val oidcIdToken = authorization.getToken(OidcIdToken::class.java)
|
|
||||||
val userCode = authorization.getToken(OAuth2UserCode::class.java)
|
|
||||||
val deviceCode = authorization.getToken(OAuth2DeviceCode::class.java)
|
|
||||||
Authorization.insert {
|
|
||||||
it[id] = authorization.id
|
|
||||||
it[registeredClientId] = authorization.registeredClientId
|
|
||||||
it[principalName] = authorization.principalName
|
|
||||||
it[authorizationGrantType] = authorization.authorizationGrantType.value
|
|
||||||
it[authorizedScopes] =
|
|
||||||
authorization.authorizedScopes.joinToString(",").takeIf { s -> s.isNotEmpty() }
|
|
||||||
it[attributes] = mapToJson(authorization.attributes)
|
|
||||||
it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE)
|
|
||||||
it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue
|
|
||||||
it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt
|
|
||||||
it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt
|
|
||||||
it[authorizationCodeMetadata] =
|
|
||||||
authorizationCodeToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[accessTokenValue] = accessToken?.token?.tokenValue
|
|
||||||
it[accessTokenIssuedAt] = accessToken?.token?.issuedAt
|
|
||||||
it[accessTokenExpiresAt] = accessToken?.token?.expiresAt
|
|
||||||
it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[accessTokenType] = accessToken?.token?.tokenType?.value
|
|
||||||
it[accessTokenScopes] =
|
|
||||||
accessToken?.run { token.scopes.joinToString(",").takeIf { s -> s.isNotEmpty() } }
|
|
||||||
it[refreshTokenValue] = refreshToken?.token?.tokenValue
|
|
||||||
it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt
|
|
||||||
it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt
|
|
||||||
it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue
|
|
||||||
it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt
|
|
||||||
it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt
|
|
||||||
it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[userCodeValue] = userCode?.token?.tokenValue
|
|
||||||
it[userCodeIssuedAt] = userCode?.token?.issuedAt
|
|
||||||
it[userCodeExpiresAt] = userCode?.token?.expiresAt
|
|
||||||
it[userCodeMetadata] = userCode?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[deviceCodeValue] = deviceCode?.token?.tokenValue
|
|
||||||
it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt
|
|
||||||
it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt
|
|
||||||
it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java)
|
|
||||||
val accessToken = authorization.getToken(OAuth2AccessToken::class.java)
|
|
||||||
val refreshToken = authorization.getToken(OAuth2RefreshToken::class.java)
|
|
||||||
val oidcIdToken = authorization.getToken(OidcIdToken::class.java)
|
|
||||||
val userCode = authorization.getToken(OAuth2UserCode::class.java)
|
|
||||||
val deviceCode = authorization.getToken(OAuth2DeviceCode::class.java)
|
|
||||||
Authorization.update({ Authorization.id eq authorization.id }) {
|
|
||||||
it[registeredClientId] = authorization.registeredClientId
|
|
||||||
it[principalName] = authorization.principalName
|
|
||||||
it[authorizationGrantType] = authorization.authorizationGrantType.value
|
|
||||||
it[authorizedScopes] =
|
|
||||||
authorization.authorizedScopes.joinToString(",").takeIf { s -> s.isNotEmpty() }
|
|
||||||
it[attributes] = mapToJson(authorization.attributes)
|
|
||||||
it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE)
|
|
||||||
it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue
|
|
||||||
it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt
|
|
||||||
it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt
|
|
||||||
it[authorizationCodeMetadata] =
|
|
||||||
authorizationCodeToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[accessTokenValue] = accessToken?.token?.tokenValue
|
|
||||||
it[accessTokenIssuedAt] = accessToken?.token?.issuedAt
|
|
||||||
it[accessTokenExpiresAt] = accessToken?.token?.expiresAt
|
|
||||||
it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[accessTokenType] = accessToken?.run { token.tokenType.value }
|
|
||||||
it[accessTokenScopes] =
|
|
||||||
accessToken?.run { token.scopes.joinToString(",").takeIf { s -> s.isNotEmpty() } }
|
|
||||||
it[refreshTokenValue] = refreshToken?.token?.tokenValue
|
|
||||||
it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt
|
|
||||||
it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt
|
|
||||||
it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue
|
|
||||||
it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt
|
|
||||||
it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt
|
|
||||||
it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[userCodeValue] = userCode?.token?.tokenValue
|
|
||||||
it[userCodeIssuedAt] = userCode?.token?.issuedAt
|
|
||||||
it[userCodeExpiresAt] = userCode?.token?.expiresAt
|
|
||||||
it[userCodeMetadata] = userCode?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
it[deviceCodeValue] = deviceCode?.token?.tokenValue
|
|
||||||
it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt
|
|
||||||
it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt
|
|
||||||
it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> mapToJson(it1) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(authorization: OAuth2Authorization?) {
|
|
||||||
if (authorization == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Authorization.deleteWhere { id eq authorization.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findById(id: String?): OAuth2Authorization? {
|
|
||||||
if (id == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return Authorization.selectAll().where { Authorization.id eq id }.singleOrNull()?.toAuthorization()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? = runBlocking {
|
|
||||||
requireNotNull(token)
|
|
||||||
transaction.transaction {
|
|
||||||
when (tokenType?.value) {
|
|
||||||
null -> {
|
|
||||||
Authorization.selectAll().where { Authorization.authorizationCodeValue eq token }.orWhere {
|
|
||||||
Authorization.accessTokenValue eq token
|
|
||||||
}.orWhere {
|
|
||||||
Authorization.oidcIdTokenValue eq token
|
|
||||||
}.orWhere {
|
|
||||||
Authorization.refreshTokenValue eq token
|
|
||||||
}.orWhere {
|
|
||||||
Authorization.userCodeValue eq token
|
|
||||||
}.orWhere {
|
|
||||||
Authorization.deviceCodeValue eq token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2ParameterNames.STATE -> {
|
|
||||||
Authorization.selectAll().where { Authorization.state eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2ParameterNames.CODE -> {
|
|
||||||
Authorization.selectAll().where { Authorization.authorizationCodeValue eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2ParameterNames.ACCESS_TOKEN -> {
|
|
||||||
Authorization.selectAll().where { Authorization.accessTokenValue eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
OidcParameterNames.ID_TOKEN -> {
|
|
||||||
Authorization.selectAll().where { Authorization.oidcIdTokenValue eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2ParameterNames.REFRESH_TOKEN -> {
|
|
||||||
Authorization.selectAll().where { Authorization.refreshTokenValue eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2ParameterNames.USER_CODE -> {
|
|
||||||
Authorization.selectAll().where { Authorization.userCodeValue eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2ParameterNames.DEVICE_CODE -> {
|
|
||||||
Authorization.selectAll().where { Authorization.deviceCodeValue eq token }
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}?.singleOrNull()?.toAuthorization()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("LongMethod", "CyclomaticComplexMethod", "CastToNullableType", "UNCHECKED_CAST")
|
|
||||||
fun ResultRow.toAuthorization(): OAuth2Authorization {
|
|
||||||
val registeredClientId = this[Authorization.registeredClientId]
|
|
||||||
|
|
||||||
val registeredClient = registeredClientRepository.findById(registeredClientId)
|
|
||||||
|
|
||||||
val builder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
val id = this[Authorization.id]
|
|
||||||
val principalName = this[Authorization.principalName]
|
|
||||||
val authorizationGrantType = this[Authorization.authorizationGrantType]
|
|
||||||
val authorizedScopes = this[Authorization.authorizedScopes]?.split(",").orEmpty().toSet()
|
|
||||||
val attributes = this[Authorization.attributes]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
|
||||||
|
|
||||||
builder.id(id).principalName(principalName)
|
|
||||||
.authorizationGrantType(AuthorizationGrantType(authorizationGrantType)).authorizedScopes(authorizedScopes)
|
|
||||||
.attributes { it.putAll(attributes) }
|
|
||||||
|
|
||||||
val state = this[Authorization.state].orEmpty()
|
|
||||||
if (state.isNotBlank()) {
|
|
||||||
builder.attribute(OAuth2ParameterNames.STATE, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
val authorizationCodeValue = this[Authorization.authorizationCodeValue].orEmpty()
|
|
||||||
if (authorizationCodeValue.isNotBlank()) {
|
|
||||||
val authorizationCodeIssuedAt = this[Authorization.authorizationCodeIssuedAt]
|
|
||||||
val authorizationCodeExpiresAt = this[Authorization.authorizationCodeExpiresAt]
|
|
||||||
val authorizationCodeMetadata = this[Authorization.authorizationCodeMetadata]?.let {
|
|
||||||
jsonToMap<String, Any>(
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}.orEmpty()
|
|
||||||
val oAuth2AuthorizationCode =
|
|
||||||
OAuth2AuthorizationCode(authorizationCodeValue, authorizationCodeIssuedAt, authorizationCodeExpiresAt)
|
|
||||||
builder.token(oAuth2AuthorizationCode) {
|
|
||||||
it.putAll(authorizationCodeMetadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val accessTokenValue = this[Authorization.accessTokenValue].orEmpty()
|
|
||||||
if (accessTokenValue.isNotBlank()) {
|
|
||||||
val accessTokenIssuedAt = this[Authorization.accessTokenIssuedAt]
|
|
||||||
val accessTokenExpiresAt = this[Authorization.accessTokenExpiresAt]
|
|
||||||
val accessTokenMetadata =
|
|
||||||
this[Authorization.accessTokenMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
|
||||||
val accessTokenType =
|
|
||||||
if (this[Authorization.accessTokenType].equals(OAuth2AccessToken.TokenType.BEARER.value, true)) {
|
|
||||||
OAuth2AccessToken.TokenType.BEARER
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val accessTokenScope = this[Authorization.accessTokenScopes]?.split(",").orEmpty().toSet()
|
|
||||||
|
|
||||||
val oAuth2AccessToken = OAuth2AccessToken(
|
|
||||||
accessTokenType,
|
|
||||||
accessTokenValue,
|
|
||||||
accessTokenIssuedAt,
|
|
||||||
accessTokenExpiresAt,
|
|
||||||
accessTokenScope
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.token(oAuth2AccessToken) { it.putAll(accessTokenMetadata) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val oidcIdTokenValue = this[Authorization.oidcIdTokenValue].orEmpty()
|
|
||||||
if (oidcIdTokenValue.isNotBlank()) {
|
|
||||||
val oidcTokenIssuedAt = this[Authorization.oidcIdTokenIssuedAt]
|
|
||||||
val oidcTokenExpiresAt = this[Authorization.oidcIdTokenExpiresAt]
|
|
||||||
val oidcTokenMetadata =
|
|
||||||
this[Authorization.oidcIdTokenMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
|
||||||
|
|
||||||
val oidcIdToken = OidcIdToken(
|
|
||||||
oidcIdTokenValue,
|
|
||||||
oidcTokenIssuedAt,
|
|
||||||
oidcTokenExpiresAt,
|
|
||||||
oidcTokenMetadata.getValue(OAuth2Authorization.Token.CLAIMS_METADATA_NAME)
|
|
||||||
as MutableMap<String, Any>?
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.token(oidcIdToken) { it.putAll(oidcTokenMetadata) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val refreshTokenValue = this[Authorization.refreshTokenValue].orEmpty()
|
|
||||||
if (refreshTokenValue.isNotBlank()) {
|
|
||||||
val refreshTokenIssuedAt = this[Authorization.refreshTokenIssuedAt]
|
|
||||||
val refreshTokenExpiresAt = this[Authorization.refreshTokenExpiresAt]
|
|
||||||
val refreshTokenMetadata =
|
|
||||||
this[Authorization.refreshTokenMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
|
||||||
|
|
||||||
val oAuth2RefreshToken = OAuth2RefreshToken(refreshTokenValue, refreshTokenIssuedAt, refreshTokenExpiresAt)
|
|
||||||
|
|
||||||
builder.token(oAuth2RefreshToken) { it.putAll(refreshTokenMetadata) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val userCodeValue = this[Authorization.userCodeValue].orEmpty()
|
|
||||||
if (userCodeValue.isNotBlank()) {
|
|
||||||
val userCodeIssuedAt = this[Authorization.userCodeIssuedAt]
|
|
||||||
val userCodeExpiresAt = this[Authorization.userCodeExpiresAt]
|
|
||||||
val userCodeMetadata =
|
|
||||||
this[Authorization.userCodeMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
|
||||||
val oAuth2UserCode = OAuth2UserCode(userCodeValue, userCodeIssuedAt, userCodeExpiresAt)
|
|
||||||
builder.token(oAuth2UserCode) { it.putAll(userCodeMetadata) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceCodeValue = this[Authorization.deviceCodeValue].orEmpty()
|
|
||||||
if (deviceCodeValue.isNotBlank()) {
|
|
||||||
val deviceCodeIssuedAt = this[Authorization.deviceCodeIssuedAt]
|
|
||||||
val deviceCodeExpiresAt = this[Authorization.deviceCodeExpiresAt]
|
|
||||||
val deviceCodeMetadata =
|
|
||||||
this[Authorization.deviceCodeMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
|
||||||
|
|
||||||
val oAuth2DeviceCode = OAuth2DeviceCode(deviceCodeValue, deviceCodeIssuedAt, deviceCodeExpiresAt)
|
|
||||||
builder.token(oAuth2DeviceCode) { it.putAll(deviceCodeMetadata) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mapToJson(map: Map<*, *>): String = objectMapper.writeValueAsString(map)
|
|
||||||
|
|
||||||
private fun <T, U> jsonToMap(json: String): Map<T, U> = objectMapper.readValue(json)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val objectMapper: ObjectMapper = ObjectMapper()
|
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
val classLoader = ExposedOAuth2AuthorizationService::class.java.classLoader
|
|
||||||
val modules = SecurityJackson2Modules.getModules(classLoader)
|
|
||||||
objectMapper.registerModules(JavaTimeModule())
|
|
||||||
objectMapper.registerModules(modules)
|
|
||||||
objectMapper.registerModules(OAuth2AuthorizationServerJackson2Module())
|
|
||||||
objectMapper.registerModules(CoreJackson2Module())
|
|
||||||
objectMapper.addMixIn(UserDetailsImpl::class.java, UserDetailsMixin::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Authorization : Table("application_authorization") {
|
|
||||||
val id: Column<String> = varchar("id", 255)
|
|
||||||
val registeredClientId: Column<String> = varchar("registered_client_id", 255)
|
|
||||||
val principalName: Column<String> = varchar("principal_name", 255)
|
|
||||||
val authorizationGrantType: Column<String> = varchar("authorization_grant_type", 255)
|
|
||||||
val authorizedScopes: Column<String?> = varchar("authorized_scopes", 1000).nullable().default(null)
|
|
||||||
val attributes: Column<String?> = varchar("attributes", 4000).nullable().default(null)
|
|
||||||
val state: Column<String?> = varchar("state", 500).nullable().default(null)
|
|
||||||
val authorizationCodeValue: Column<String?> = varchar("authorization_code_value", 4000).nullable().default(null)
|
|
||||||
val authorizationCodeIssuedAt: Column<Instant?> = timestamp("authorization_code_issued_at").nullable().default(null)
|
|
||||||
val authorizationCodeExpiresAt: Column<Instant?> = timestamp("authorization_code_expires_at").nullable().default(
|
|
||||||
null
|
|
||||||
)
|
|
||||||
val authorizationCodeMetadata: Column<String?> = varchar("authorization_code_metadata", 2000).nullable().default(
|
|
||||||
null
|
|
||||||
)
|
|
||||||
val accessTokenValue: Column<String?> = varchar("access_token_value", 4000).nullable().default(null)
|
|
||||||
val accessTokenIssuedAt: Column<Instant?> = timestamp("access_token_issued_at").nullable().default(null)
|
|
||||||
val accessTokenExpiresAt: Column<Instant?> = timestamp("access_token_expires_at").nullable().default(null)
|
|
||||||
val accessTokenMetadata: Column<String?> = varchar("access_token_metadata", 2000).nullable().default(null)
|
|
||||||
val accessTokenType: Column<String?> = varchar("access_token_type", 255).nullable().default(null)
|
|
||||||
val accessTokenScopes: Column<String?> = varchar("access_token_scopes", 1000).nullable().default(null)
|
|
||||||
val refreshTokenValue: Column<String?> = varchar("refresh_token_value", 4000).nullable().default(null)
|
|
||||||
val refreshTokenIssuedAt: Column<Instant?> = timestamp("refresh_token_issued_at").nullable().default(null)
|
|
||||||
val refreshTokenExpiresAt: Column<Instant?> = timestamp("refresh_token_expires_at").nullable().default(null)
|
|
||||||
val refreshTokenMetadata: Column<String?> = varchar("refresh_token_metadata", 2000).nullable().default(null)
|
|
||||||
val oidcIdTokenValue: Column<String?> = varchar("oidc_id_token_value", 4000).nullable().default(null)
|
|
||||||
val oidcIdTokenIssuedAt: Column<Instant?> = timestamp("oidc_id_token_issued_at").nullable().default(null)
|
|
||||||
val oidcIdTokenExpiresAt: Column<Instant?> = timestamp("oidc_id_token_expires_at").nullable().default(null)
|
|
||||||
val oidcIdTokenMetadata: Column<String?> = varchar("oidc_id_token_metadata", 2000).nullable().default(null)
|
|
||||||
val oidcIdTokenClaims: Column<String?> = varchar("oidc_id_token_claims", 2000).nullable().default(null)
|
|
||||||
val userCodeValue: Column<String?> = varchar("user_code_value", 4000).nullable().default(null)
|
|
||||||
val userCodeIssuedAt: Column<Instant?> = timestamp("user_code_issued_at").nullable().default(null)
|
|
||||||
val userCodeExpiresAt: Column<Instant?> = timestamp("user_code_expires_at").nullable().default(null)
|
|
||||||
val userCodeMetadata: Column<String?> = varchar("user_code_metadata", 2000).nullable().default(null)
|
|
||||||
val deviceCodeValue: Column<String?> = varchar("device_code_value", 4000).nullable().default(null)
|
|
||||||
val deviceCodeIssuedAt: Column<Instant?> = timestamp("device_code_issued_at").nullable().default(null)
|
|
||||||
val deviceCodeExpiresAt: Column<Instant?> = timestamp("device_code_expires_at").nullable().default(null)
|
|
||||||
val deviceCodeMetadata: Column<String?> = varchar("device_code_metadata", 2000).nullable().default(null)
|
|
||||||
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.core.infrastructure.springframework.oauth2
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient.clientId
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient.clientSettings
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient.tokenSettings
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
||||||
import org.jetbrains.exposed.sql.javatime.CurrentTimestamp
|
|
||||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.security.jackson2.SecurityJackson2Modules
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType
|
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
|
||||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings
|
|
||||||
import org.springframework.stereotype.Repository
|
|
||||||
import org.springframework.transaction.annotation.Transactional
|
|
||||||
import java.time.Instant
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient as SpringRegisteredClient
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
class RegisteredClientRepositoryImpl : RegisteredClientRepository {
|
|
||||||
|
|
||||||
override fun save(registeredClient: SpringRegisteredClient?) {
|
|
||||||
requireNotNull(registeredClient)
|
|
||||||
val singleOrNull =
|
|
||||||
RegisteredClient.selectAll().where { RegisteredClient.id eq registeredClient.id }.singleOrNull()
|
|
||||||
if (singleOrNull == null) {
|
|
||||||
RegisteredClient.insert {
|
|
||||||
it[id] = registeredClient.id
|
|
||||||
it[clientId] = registeredClient.clientId
|
|
||||||
it[clientIdIssuedAt] = registeredClient.clientIdIssuedAt ?: Instant.now()
|
|
||||||
it[clientSecret] = registeredClient.clientSecret
|
|
||||||
it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt
|
|
||||||
it[clientName] = registeredClient.clientName
|
|
||||||
it[clientAuthenticationMethods] =
|
|
||||||
registeredClient.clientAuthenticationMethods.joinToString(",") { method -> method.value }
|
|
||||||
it[authorizationGrantTypes] =
|
|
||||||
registeredClient.authorizationGrantTypes.joinToString(",") { type -> type.value }
|
|
||||||
it[redirectUris] = registeredClient.redirectUris.joinToString(",")
|
|
||||||
it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",")
|
|
||||||
it[scopes] = registeredClient.scopes.joinToString(",")
|
|
||||||
it[clientSettings] = mapToJson(registeredClient.clientSettings.settings)
|
|
||||||
it[tokenSettings] = mapToJson(registeredClient.tokenSettings.settings)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
RegisteredClient.update({ RegisteredClient.id eq registeredClient.id }) {
|
|
||||||
it[clientId] = registeredClient.clientId
|
|
||||||
it[clientIdIssuedAt] = registeredClient.clientIdIssuedAt ?: Instant.now()
|
|
||||||
it[clientSecret] = registeredClient.clientSecret
|
|
||||||
it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt
|
|
||||||
it[clientName] = registeredClient.clientName
|
|
||||||
it[clientAuthenticationMethods] = registeredClient.clientAuthenticationMethods.joinToString(",")
|
|
||||||
it[authorizationGrantTypes] = registeredClient.authorizationGrantTypes.joinToString(",")
|
|
||||||
it[redirectUris] = registeredClient.redirectUris.joinToString(",")
|
|
||||||
it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",")
|
|
||||||
it[scopes] = registeredClient.scopes.joinToString(",")
|
|
||||||
it[clientSettings] = mapToJson(registeredClient.clientSettings.settings)
|
|
||||||
it[tokenSettings] = mapToJson(registeredClient.tokenSettings.settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findById(id: String?): SpringRegisteredClient? {
|
|
||||||
if (id == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return RegisteredClient.selectAll().where { RegisteredClient.id eq id }.singleOrNull()?.toRegisteredClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun findByClientId(clientId: String?): SpringRegisteredClient? {
|
|
||||||
if (clientId == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val toRegisteredClient =
|
|
||||||
RegisteredClient.selectAll().where { RegisteredClient.clientId eq clientId }.singleOrNull()
|
|
||||||
?.toRegisteredClient()
|
|
||||||
LOGGER.trace("findByClientId: {}", toRegisteredClient)
|
|
||||||
return toRegisteredClient
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mapToJson(map: Map<*, *>): String = objectMapper.writeValueAsString(map)
|
|
||||||
|
|
||||||
private fun <T, U> jsonToMap(json: String): Map<T, U> = objectMapper.readValue(json)
|
|
||||||
|
|
||||||
@Suppress("CyclomaticComplexMethod")
|
|
||||||
fun ResultRow.toRegisteredClient(): SpringRegisteredClient {
|
|
||||||
fun resolveClientAuthenticationMethods(string: String): ClientAuthenticationMethod {
|
|
||||||
return when (string) {
|
|
||||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC.value -> ClientAuthenticationMethod.CLIENT_SECRET_BASIC
|
|
||||||
ClientAuthenticationMethod.CLIENT_SECRET_JWT.value -> ClientAuthenticationMethod.CLIENT_SECRET_JWT
|
|
||||||
ClientAuthenticationMethod.CLIENT_SECRET_POST.value -> ClientAuthenticationMethod.CLIENT_SECRET_POST
|
|
||||||
ClientAuthenticationMethod.NONE.value -> ClientAuthenticationMethod.NONE
|
|
||||||
else -> {
|
|
||||||
ClientAuthenticationMethod(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resolveAuthorizationGrantType(string: String): AuthorizationGrantType {
|
|
||||||
return when (string) {
|
|
||||||
AuthorizationGrantType.AUTHORIZATION_CODE.value -> AuthorizationGrantType.AUTHORIZATION_CODE
|
|
||||||
AuthorizationGrantType.CLIENT_CREDENTIALS.value -> AuthorizationGrantType.CLIENT_CREDENTIALS
|
|
||||||
AuthorizationGrantType.REFRESH_TOKEN.value -> AuthorizationGrantType.REFRESH_TOKEN
|
|
||||||
else -> {
|
|
||||||
AuthorizationGrantType(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientAuthenticationMethods = this[RegisteredClient.clientAuthenticationMethods].split(",").toSet()
|
|
||||||
val authorizationGrantTypes = this[RegisteredClient.authorizationGrantTypes].split(",").toSet()
|
|
||||||
val redirectUris = this[RegisteredClient.redirectUris]?.split(",").orEmpty().toSet()
|
|
||||||
val postLogoutRedirectUris = this[RegisteredClient.postLogoutRedirectUris]?.split(",").orEmpty().toSet()
|
|
||||||
val clientScopes = this[RegisteredClient.scopes].split(",").toSet()
|
|
||||||
|
|
||||||
val builder = SpringRegisteredClient
|
|
||||||
.withId(this[RegisteredClient.id])
|
|
||||||
.clientId(this[clientId])
|
|
||||||
.clientIdIssuedAt(this[RegisteredClient.clientIdIssuedAt])
|
|
||||||
.clientSecret(this[RegisteredClient.clientSecret])
|
|
||||||
.clientSecretExpiresAt(this[RegisteredClient.clientSecretExpiresAt])
|
|
||||||
.clientName(this[RegisteredClient.clientName])
|
|
||||||
.clientAuthenticationMethods {
|
|
||||||
clientAuthenticationMethods.forEach { s ->
|
|
||||||
it.add(resolveClientAuthenticationMethods(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.authorizationGrantTypes {
|
|
||||||
authorizationGrantTypes.forEach { s ->
|
|
||||||
it.add(resolveAuthorizationGrantType(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.redirectUris { it.addAll(redirectUris) }
|
|
||||||
.postLogoutRedirectUris { it.addAll(postLogoutRedirectUris) }
|
|
||||||
.scopes { it.addAll(clientScopes) }
|
|
||||||
.clientSettings(ClientSettings.withSettings(jsonToMap(this[clientSettings])).build())
|
|
||||||
|
|
||||||
val tokenSettingsMap = jsonToMap<String, Any>(this[tokenSettings])
|
|
||||||
val withSettings = TokenSettings.withSettings(tokenSettingsMap)
|
|
||||||
if (tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) {
|
|
||||||
withSettings.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
|
||||||
}
|
|
||||||
builder.tokenSettings(withSettings.build())
|
|
||||||
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val objectMapper: ObjectMapper = ObjectMapper()
|
|
||||||
val LOGGER: Logger = LoggerFactory.getLogger(RegisteredClientRepositoryImpl::class.java)
|
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
val classLoader = ExposedOAuth2AuthorizationService::class.java.classLoader
|
|
||||||
val modules = SecurityJackson2Modules.getModules(classLoader)
|
|
||||||
objectMapper.registerModules(JavaTimeModule())
|
|
||||||
objectMapper.registerModules(modules)
|
|
||||||
objectMapper.registerModules(OAuth2AuthorizationServerJackson2Module())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql
|
|
||||||
object RegisteredClient : Table("registered_client") {
|
|
||||||
val id: Column<String> = varchar("id", 100)
|
|
||||||
val clientId: Column<String> = varchar("client_id", 100)
|
|
||||||
val clientIdIssuedAt: Column<Instant> = timestamp("client_id_issued_at").defaultExpression(CurrentTimestamp)
|
|
||||||
val clientSecret: Column<String?> = varchar("client_secret", 200).nullable().default(null)
|
|
||||||
val clientSecretExpiresAt: Column<Instant?> = timestamp("client_secret_expires_at").nullable().default(null)
|
|
||||||
val clientName: Column<String> = varchar("client_name", 200)
|
|
||||||
val clientAuthenticationMethods: Column<String> = varchar("client_authentication_methods", 1000)
|
|
||||||
val authorizationGrantTypes: Column<String> = varchar("authorization_grant_types", 1000)
|
|
||||||
val redirectUris: Column<String?> = varchar("redirect_uris", 1000).nullable().default(null)
|
|
||||||
val postLogoutRedirectUris: Column<String?> = varchar("post_logout_redirect_uris", 1000).nullable().default(null)
|
|
||||||
val scopes: Column<String> = varchar("scopes", 1000)
|
|
||||||
val clientSettings: Column<String> = varchar("client_settings", 2000)
|
|
||||||
val tokenSettings: Column<String> = varchar("token_settings", 2000)
|
|
||||||
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.core.infrastructure.springframework.oauth2
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAutoDetect
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
|
||||||
import org.springframework.security.core.GrantedAuthority
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
|
||||||
import org.springframework.security.core.userdetails.User
|
|
||||||
import java.io.Serial
|
|
||||||
|
|
||||||
class UserDetailsImpl(
|
|
||||||
val id: Long,
|
|
||||||
username: String?,
|
|
||||||
password: String?,
|
|
||||||
enabled: Boolean,
|
|
||||||
accountNonExpired: Boolean,
|
|
||||||
credentialsNonExpired: Boolean,
|
|
||||||
accountNonLocked: Boolean,
|
|
||||||
authorities: MutableCollection<out GrantedAuthority>?
|
|
||||||
) : User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "UserDetailsImpl(" +
|
|
||||||
"id=$id" +
|
|
||||||
")" +
|
|
||||||
" ${super.toString()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
if (!super.equals(other)) return false
|
|
||||||
|
|
||||||
other as UserDetailsImpl
|
|
||||||
|
|
||||||
return id == other.id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = super.hashCode()
|
|
||||||
result = 31 * result + id.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Serial
|
|
||||||
private const val serialVersionUID: Long = -899168205656607781L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
|
||||||
@JsonDeserialize(using = UserDetailsDeserializer::class)
|
|
||||||
@JsonAutoDetect(
|
|
||||||
fieldVisibility = JsonAutoDetect.Visibility.ANY,
|
|
||||||
getterVisibility = JsonAutoDetect.Visibility.NONE,
|
|
||||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
|
|
||||||
creatorVisibility = JsonAutoDetect.Visibility.NONE
|
|
||||||
)
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
@JsonSubTypes
|
|
||||||
@Suppress("UnnecessaryAbstractClass")
|
|
||||||
abstract class UserDetailsMixin
|
|
||||||
|
|
||||||
class UserDetailsDeserializer : JsonDeserializer<UserDetailsImpl>() {
|
|
||||||
|
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UserDetailsImpl {
|
|
||||||
val mapper = p.codec as ObjectMapper
|
|
||||||
val jsonNode: JsonNode = mapper.readTree(p)
|
|
||||||
val authorities: Set<GrantedAuthority> = mapper.convertValue(
|
|
||||||
jsonNode["authorities"],
|
|
||||||
SIMPLE_GRANTED_AUTHORITY_SET
|
|
||||||
)
|
|
||||||
|
|
||||||
val password = jsonNode.readText("password")
|
|
||||||
return UserDetailsImpl(
|
|
||||||
id = jsonNode["id"].longValue(),
|
|
||||||
username = jsonNode.readText("username"),
|
|
||||||
password = password,
|
|
||||||
enabled = true,
|
|
||||||
accountNonExpired = true,
|
|
||||||
credentialsNonExpired = true,
|
|
||||||
accountNonLocked = true,
|
|
||||||
authorities = authorities.toMutableList(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonNode.readText(field: String, defaultValue: String = ""): String {
|
|
||||||
return when {
|
|
||||||
has(field) -> get(field).asText(defaultValue)
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val SIMPLE_GRANTED_AUTHORITY_SET = object : TypeReference<Set<SimpleGrantedAuthority>>() {}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.core.infrastructure.springframework.security
|
|
||||||
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
|
||||||
import org.springframework.security.oauth2.jwt.Jwt
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class OAuth2JwtLoginUserContextHolder : LoginUserContextHolder {
|
|
||||||
override fun getLoginUserId(): Long {
|
|
||||||
val principal = SecurityContextHolder.getContext().authentication.principal as Jwt
|
|
||||||
|
|
||||||
return principal.getClaim<String>("uid").toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLoginUserIdOrNull(): Long? {
|
|
||||||
val principal = SecurityContextHolder.getContext()?.authentication?.principal
|
|
||||||
if (principal !is Jwt) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return principal.getClaim<String>("uid").toLongOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.generate
|
|
||||||
|
|
||||||
@MustBeDocumented
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
|
||||||
annotation class JsonOrFormBind
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.generate
|
|
||||||
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.core.MethodParameter
|
|
||||||
import org.springframework.validation.BindException
|
|
||||||
import org.springframework.web.bind.support.WebDataBinderFactory
|
|
||||||
import org.springframework.web.context.request.NativeWebRequest
|
|
||||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor
|
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
|
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionCaught")
|
|
||||||
class JsonOrFormModelMethodProcessor(
|
|
||||||
private val modelAttributeMethodProcessor: ModelAttributeMethodProcessor,
|
|
||||||
private val requestResponseBodyMethodProcessor: RequestResponseBodyMethodProcessor
|
|
||||||
) : HandlerMethodArgumentResolver {
|
|
||||||
private val isJsonRegex = Regex("application/((\\w*)\\+)?json")
|
|
||||||
|
|
||||||
override fun supportsParameter(parameter: MethodParameter): Boolean =
|
|
||||||
parameter.hasParameterAnnotation(JsonOrFormBind::class.java)
|
|
||||||
|
|
||||||
override fun resolveArgument(
|
|
||||||
parameter: MethodParameter,
|
|
||||||
mavContainer: ModelAndViewContainer?,
|
|
||||||
webRequest: NativeWebRequest,
|
|
||||||
binderFactory: WebDataBinderFactory?
|
|
||||||
): Any? {
|
|
||||||
val contentType = webRequest.getHeader("Content-Type").orEmpty()
|
|
||||||
logger.trace("ContentType is {}", contentType)
|
|
||||||
if (contentType.contains(isJsonRegex)) {
|
|
||||||
logger.trace("Determine content type as json.")
|
|
||||||
return requestResponseBodyMethodProcessor.resolveArgument(
|
|
||||||
parameter,
|
|
||||||
mavContainer,
|
|
||||||
webRequest,
|
|
||||||
binderFactory
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
|
||||||
} catch (e: BindException) {
|
|
||||||
throw e
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
try {
|
|
||||||
requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
|
||||||
} catch (e: BindException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.warn("Failed to bind request (1)", exception)
|
|
||||||
logger.warn("Failed to bind request (2)", e)
|
|
||||||
throw IllegalArgumentException("Failed to bind request.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val logger: Logger = LoggerFactory.getLogger(JsonOrFormModelMethodProcessor::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.actor
|
package dev.usbharu.hideout.core.domain.model.actor
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
|
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package dev.usbharu
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
println("Hello World!")
|
|
||||||
}
|
|
Loading…
Reference in New Issue