Skip to content

Commit

Permalink
Provide a single WebClient bean application wide that is either secur…
Browse files Browse the repository at this point in the history
…e or insecure.
  • Loading branch information
dzarras committed Nov 20, 2023
1 parent 47baec4 commit cb1884d
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 140 deletions.
100 changes: 68 additions & 32 deletions src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import eu.europa.ec.eudi.pidissuer.adapter.out.jose.EncryptCredentialResponseWit
import eu.europa.ec.eudi.pidissuer.adapter.out.persistence.InMemoryCNonceRepository
import eu.europa.ec.eudi.pidissuer.adapter.out.persistence.InMemoryDeferredCredentialRepository
import eu.europa.ec.eudi.pidissuer.adapter.out.pid.*
import eu.europa.ec.eudi.pidissuer.adapter.out.webclient.WebClients
import eu.europa.ec.eudi.pidissuer.domain.*
import eu.europa.ec.eudi.pidissuer.port.input.GetCredentialIssuerMetaData
import eu.europa.ec.eudi.pidissuer.port.input.GetDeferredCredential
Expand All @@ -40,8 +39,11 @@ import eu.europa.ec.eudi.pidissuer.port.out.asDeferred
import eu.europa.ec.eudi.pidissuer.port.out.persistence.GenerateCNonce
import eu.europa.ec.eudi.pidissuer.port.out.persistence.GenerateTransactionId
import eu.europa.ec.eudi.sdjwt.HashAlgorithm
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.util.InsecureTrustManagerFactory
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties
import org.springframework.boot.runApplication
Expand All @@ -53,6 +55,7 @@ import org.springframework.core.env.Environment
import org.springframework.core.env.getProperty
import org.springframework.core.env.getRequiredProperty
import org.springframework.http.HttpStatus
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.http.codec.json.KotlinSerializationJsonDecoder
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder
Expand All @@ -63,31 +66,67 @@ import org.springframework.security.oauth2.server.resource.introspection.SpringR
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.reactive.config.WebFluxConfigurer
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient
import java.time.Clock
import java.time.Duration

private val log = LoggerFactory.getLogger(PidIssuerApplication::class.java)

/**
* [WebClient] instances for usage within the application.
*/
private object WebClients {

/**
* A [WebClient] with [Json] serialization enabled.
*/
val Default: WebClient by lazy {
val json = Json { ignoreUnknownKeys = true }
WebClient
.builder()
.codecs {
it.defaultCodecs().kotlinSerializationJsonDecoder(KotlinSerializationJsonDecoder(json))
it.defaultCodecs().kotlinSerializationJsonEncoder(KotlinSerializationJsonEncoder(json))
it.defaultCodecs().enableLoggingRequestDetails(true)
}
.build()
}

/**
* A [WebClient] with [Json] serialization enabled that trusts *all* certificates.
*/
val Insecure: WebClient by lazy {
log.warn("Using insecure WebClient trusting all certificates")
val sslContext = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build()
val httpClient = HttpClient.create().secure { it.sslContext(sslContext) }
Default.mutate()
.clientConnector(ReactorClientHttpConnector(httpClient))
.build()
}
}

fun beans(clock: Clock) = beans {
//
// Adapters (out ports)
//
bean { clock }
profile("!insecure") {
bean {
GetPidDataFromAuthServer(
env.readRequiredUrl("issuer.authorizationServer.userinfo"),
env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry),
clock,
)
bean {
if ("insecure" in env.activeProfiles) {
WebClients.Insecure
} else {
WebClients.Default
}
}
profile("insecure") {
bean {
GetPidDataFromAuthServer.insecure(
env.readRequiredUrl("issuer.authorizationServer.userinfo"),
env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry),
clock,
)
}
bean {
GetPidDataFromAuthServer(
env.readRequiredUrl("issuer.authorizationServer.userinfo"),
env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry),
clock,
ref(),
)
}
//
// Encryption of credential response
Expand Down Expand Up @@ -122,7 +161,7 @@ fun beans(clock: Clock) = beans {
val issuerPublicUrl = env.readRequiredUrl("issuer.publicUrl")

bean {
EncodePidInCborWithMicroService(env.readRequiredUrl("issuer.pid.mso_mdoc.encoderUrl"))
EncodePidInCborWithMicroService(env.readRequiredUrl("issuer.pid.mso_mdoc.encoderUrl"), ref())
}

CredentialIssuerMetaData(
Expand Down Expand Up @@ -210,20 +249,6 @@ fun beans(clock: Clock) = beans {
//
// Security
//
profile("insecure") {
bean {
val properties = ref<OAuth2ResourceServerProperties>()

SpringReactiveOpaqueTokenIntrospector(
properties.opaquetoken.introspectionUri,
WebClients.insecure {
defaultHeaders {
it.setBasicAuth(properties.opaquetoken.clientId, properties.opaquetoken.clientSecret)
}
},
)
}
}
bean {
/*
* This is a Spring naming convention
Expand Down Expand Up @@ -262,7 +287,18 @@ fun beans(clock: Clock) = beans {
}

oauth2ResourceServer {
opaqueToken {}
opaqueToken {
val properties = ref<OAuth2ResourceServerProperties>()
introspector = SpringReactiveOpaqueTokenIntrospector(
properties.opaquetoken.introspectionUri,
ref<WebClient>()
.mutate()
.defaultHeaders {
it.setBasicAuth(properties.opaquetoken.clientId, properties.opaquetoken.clientSecret)
}
.build(),
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import org.bouncycastle.util.io.pem.PemObject
import org.bouncycastle.util.io.pem.PemWriter
import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.http.codec.json.KotlinSerializationJsonDecoder
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.awaitBody
import java.io.StringWriter
Expand All @@ -35,24 +33,15 @@ import kotlin.io.encoding.ExperimentalEncodingApi

private val log = LoggerFactory.getLogger(EncodePidInCborWithMicroService::class.java)

class EncodePidInCborWithMicroService(private val creatorUrl: HttpsUrl) : EncodePidInCbor {
class EncodePidInCborWithMicroService(
private val creatorUrl: HttpsUrl,
private val webClient: WebClient,
) : EncodePidInCbor {

init {
log.info("Initialized using: $creatorUrl")
}

private val webClient: WebClient by lazy {
val json = Json { ignoreUnknownKeys = true }
WebClient
.builder()
.codecs { configurer ->
configurer.defaultCodecs().kotlinSerializationJsonDecoder(KotlinSerializationJsonDecoder(json))
configurer.defaultCodecs().kotlinSerializationJsonEncoder(KotlinSerializationJsonEncoder(json))
configurer.defaultCodecs().enableLoggingRequestDetails(true)
}.baseUrl(creatorUrl.externalForm)
.build()
}

override suspend fun invoke(
pid: Pid,
pidMetaData: PidMetaData,
Expand All @@ -61,6 +50,7 @@ class EncodePidInCborWithMicroService(private val creatorUrl: HttpsUrl) : Encode
log.info("Requesting PID in mso_mdoc for ${pid.familyName} ...")
val request = createMsoMdocReq(pid, pidMetaData, holderKey)
webClient.post()
.uri(creatorUrl.externalForm)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.out.pid

import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcAddressClaim
import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcAssurancePlaceOfBirth
import eu.europa.ec.eudi.pidissuer.adapter.out.webclient.WebClients
import eu.europa.ec.eudi.pidissuer.domain.HttpsUrl
import kotlinx.serialization.Required
import kotlinx.serialization.SerialName
Expand All @@ -33,7 +32,8 @@ import java.time.LocalDate

private val log = LoggerFactory.getLogger(GetPidDataFromAuthServer::class.java)

class GetPidDataFromAuthServer private constructor(
class GetPidDataFromAuthServer(
private val authorizationServerUserInfoEndPoint: HttpsUrl,
private val issuerCountry: IsoCountry,
private val clock: Clock,
private val webClient: WebClient,
Expand All @@ -52,7 +52,9 @@ class GetPidDataFromAuthServer private constructor(
}

private suspend fun userInfo(accessToken: String): UserInfo =
webClient.get().accept(MediaType.APPLICATION_JSON)
webClient.get()
.uri(authorizationServerUserInfoEndPoint.externalForm)
.accept(MediaType.APPLICATION_JSON)
.headers { headers -> headers.setBearerAuth(accessToken) }
.retrieve()
.awaitBody<UserInfo>()
Expand Down Expand Up @@ -93,42 +95,6 @@ class GetPidDataFromAuthServer private constructor(

return pid to pidMetaData
}

companion object {

/**
* Creates a new [GetPidDataFromAuthServer] using the provided data.
*/
operator fun invoke(
authorizationServerUserInfoEndPoint: HttpsUrl,
issuerCountry: IsoCountry,
clock: Clock,
): GetPidDataFromAuthServer =
GetPidDataFromAuthServer(
issuerCountry,
clock,
WebClients.default {
baseUrl(authorizationServerUserInfoEndPoint.externalForm)
},
)

/**
* Creates a new *insecure* [GetPidDataFromAuthServer] that trusts all certificates.
*/
fun insecure(
authorizationServerUserInfoEndPoint: HttpsUrl,
issuerCountry: IsoCountry,
clock: Clock,
): GetPidDataFromAuthServer {
return GetPidDataFromAuthServer(
issuerCountry,
clock,
WebClients.insecure {
baseUrl(authorizationServerUserInfoEndPoint.externalForm)
},
)
}
}
}

@Serializable
Expand Down

This file was deleted.

0 comments on commit cb1884d

Please sign in to comment.