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
|
||||
|
||||
import dev.usbharu.hideout.SpringApplication
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient
|
||||
import dev.usbharu.owl.producer.api.OwlProducer
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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.context.SpringBootTest
|
||||
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.web.servlet.setup.SecurityMockMvcConfigurers
|
||||
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
|
||||
|
||||
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.domain.model.actor.ActorRepository
|
||||
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.service.actor.local.LocalActorDomainService
|
||||
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 org.springframework.stereotype.Service
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* 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.PolicyFactory
|
|
@ -14,7 +14,7 @@
|
|||
* 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.nodes.Document
|
||||
|
@ -24,11 +24,6 @@ import org.jsoup.select.Elements
|
|||
import org.owasp.html.PolicyFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
|
||||
interface PostContentFormatter {
|
||||
fun format(content: String): FormattedPostContent
|
||||
}
|
||||
|
||||
@Service
|
||||
class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : PostContentFormatter {
|
||||
override fun format(content: String): FormattedPostContent {
|
||||
|
@ -101,9 +96,4 @@ class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : Po
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FormattedPostContent(
|
||||
val html: String,
|
||||
val content: String,
|
||||
)
|
||||
}
|
|
@ -14,10 +14,14 @@
|
|||
* 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.
|
||||
*/
|
||||
|
||||
package dev.usbharu.hideout.application.service.id
|
||||
package dev.usbharu.hideout.core.domain.shared.id
|
||||
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
interface IdGenerateService {
|
||||
suspend fun generateId(): Long
|
||||
}
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
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.ActorId
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
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.emoji.EmojiId
|
||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||
|
|
|
@ -14,28 +14,27 @@
|
|||
* 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 kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.slf4j.MDCContext
|
||||
import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger
|
||||
import org.jetbrains.exposed.sql.addLogger
|
||||
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.stereotype.Component
|
||||
import java.sql.Connection
|
||||
|
||||
@Service
|
||||
@Component
|
||||
class ExposedTransaction : Transaction {
|
||||
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
|
||||
warnLongQueriesDuration = 1000
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
runBlocking(MDCContext()) {
|
||||
block()
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.usbharu.hideout.application.infrastructure.exposed
|
||||
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||
|
||||
import org.jetbrains.exposed.sql.Query
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.usbharu.hideout.application.infrastructure.exposed
|
||||
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
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.shared.Domain
|
||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
|
||||
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.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.factory
|
||||
|
||||
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.instance.InstanceId
|
||||
import dev.usbharu.hideout.core.domain.model.shared.Domain
|
||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||
import org.springframework.stereotype.Component
|
||||
import java.net.URI
|
||||
import java.time.Instant
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.factory
|
||||
|
||||
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
|
||||
|
||||
@Component
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.factory
|
||||
|
||||
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.ActorName
|
||||
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.PostOverview
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||
import org.springframework.stereotype.Component
|
||||
import java.net.URI
|
||||
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.
|
||||
*/
|
||||
|
||||
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.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.time.Instant
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
|
||||
open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
|
||||
var lastTimeStamp: Long = -1
|
||||
var sequenceId: Int = 0
|
||||
val mutex = Mutex()
|
|
@ -14,7 +14,7 @@
|
|||
* 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.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
|
||||
|
||||
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
|
||||
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.shared.Domain
|
||||
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.net.URI
|
||||
import java.time.Instant
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package dev.usbharu
|
||||
|
||||
fun main() {
|
||||
println("Hello World!")
|
||||
}
|
Loading…
Reference in New Issue