Skip to content

Commit

Permalink
Removed ValidatedRSSPMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
babisRoutis committed Oct 14, 2024
1 parent 69f5050 commit 44626ad
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 223 deletions.
19 changes: 7 additions & 12 deletions src/main/kotlin/eu/europa/ec/eudi/rqes/CSCClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ interface CSCClient :
rsspId: String,
ktorHttpClientFactory: KtorHttpClientFactory = DefaultHttpClientFactory,
): Result<CSCClient> = kotlin.runCatching {
val metadata = run {
val id = RSSPId(rsspId).getOrThrow()
val resolver = RSSPMetadataResolver(ktorHttpClientFactory)
resolver.resolve(id, cscClientConfig.locale).getOrThrow()
}
val metadata =
run {
val id = RSSPId(rsspId).getOrThrow()
val resolver = RSSPMetadataResolver(ktorHttpClientFactory)
resolver.resolve(id, cscClientConfig.locale).getOrThrow()
}
oauth2(cscClientConfig, metadata, ktorHttpClientFactory).getOrThrow()
}

Expand All @@ -51,13 +52,7 @@ interface CSCClient :
ktorHttpClientFactory: KtorHttpClientFactory = DefaultHttpClientFactory,
): Result<CSCClient> = runCatching {
val oauth2AuthType =
rsspMetadata.authTypes.values
.filterIsInstance<AuthType.OAuth2>()
.firstOrNull()
requireNotNull(oauth2AuthType) {
"RSSP doesn't support OAUTH2"
}

requireNotNull(rsspMetadata.oauth2AuthType()) { "RSSP doesn't support OAUTH2" }
val (authServerMetadata, grants) = oauth2AuthType

val tokenEndpointClient = TokenEndpointClient(
Expand Down
96 changes: 40 additions & 56 deletions src/main/kotlin/eu/europa/ec/eudi/rqes/RSSPMetadataResolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,39 @@
*/
package eu.europa.ec.eudi.rqes

import com.nimbusds.oauth2.sdk.`as`.ReadOnlyAuthorizationServerMetadata
import eu.europa.ec.eudi.rqes.AuthType.*
import eu.europa.ec.eudi.rqes.AuthType.Digest
import eu.europa.ec.eudi.rqes.internal.DefaultRSSPMetadataResolver
import java.io.Serializable
import java.net.URI
import java.util.*

/**
* The metadata of a RSSP.
*/
data class RSSPMetadata(
data class RSSPMetadataContent<T>(
val rsspId: RSSPId,
val specs: String,
val name: String,
val logo: URI,
val region: String,
val lang: Locale,
val description: String,
val authTypes: AuthTypesSupported,
val specs: String?,
val name: String?,
val logo: URI?,
val region: String?,
val lang: Locale?,
val description: String?,
val authTypes: Set<AuthType<T>>,
val asynchronousOperationMode: Boolean? = false,
val methods: List<RSSPMethod>,
val validationInfo: Boolean? = false,
)
val validationInfo: Boolean = false,
) {
init {
require(authTypes.isNotEmpty())
require(methods.isNotEmpty())
}
}

internal inline fun <reified T> RSSPMetadataContent<T>.oauth2AuthType(): AuthType.OAuth2<T>? =
authTypes.filterIsInstance<AuthType.OAuth2<T>>().firstOrNull()

/**
* The metadata of a RSSP.
*/
typealias RSSPMetadata = RSSPMetadataContent<CSCAuthorizationServerMetadata>

enum class RSSPMethod {
Info,
Expand All @@ -59,59 +70,32 @@ enum class RSSPMethod {
Oauth2Revoke,
;

companion object {
fun from(s: String): RSSPMethod? = when (s) {
"info" -> Info
"auth/login" -> AuthLogin
"auth/revoke" -> AuthRevoke
"credentials/list" -> CredentialsList
"credentials/info" -> CredentialsInfo
"credentials/authorize" -> CredentialsAuthorize
"credentials/authorizeCheck" -> CredentialsAuthorizeCheck
"credentials/getChallenge" -> CredentialsGetChallenge
"credentials/sendOTP" -> CredentialsSendOTP
"credentials/extendTransaction" -> CredentialsExtendTransaction
"signatures/signHash" -> SignaturesSignHash
"signatures/signDoc" -> SignaturesSignDoc
"signatures/signPolling" -> SignaturesSignPolling
"signatures/timestamp" -> SignaturesTimestamp
"oauth2/authorize" -> Oauth2Authorize
"oauth2/token" -> Oauth2Token
"oauth2/pushed_authorize" -> Oauth2PushedAuthorize
"oauth2/revoke" -> Oauth2Revoke
else -> null
}
}
}

/**
* The authentication types supported by the RSSP.
*/
@JvmInline
value class AuthTypesSupported(val values: Set<AuthType>) {
init {
require(values.isNotEmpty()) { "At least one AuthType must be provided" }
}
companion object
}

enum class Oauth2Grant {
AuthorizationCode,
ClientCredentials,
}

sealed interface AuthType {
data object External : AuthType
data object Basic : AuthType
data object Digest : AuthType
data object TLS : AuthType
data class OAuth2(
val authorizationServerMetadata: ReadOnlyAuthorizationServerMetadata,
val grantsTypes: Set<Oauth2Grant>,
) : AuthType {
sealed interface AuthType<out T> {
data class OAuth2<T>(val authorizationServer: T, val grantsTypes: Set<Oauth2Grant>) : AuthType<T> {
init {
require(grantsTypes.isNotEmpty()) { "At least one GrantType must be provided" }
}
}

data object External : AuthType<Nothing>
data object Basic : AuthType<Nothing>
data object Digest : AuthType<Nothing>
data object TLS : AuthType<Nothing>
}
internal inline fun <T, Y> AuthType<T>.map(f: (T) -> Y): AuthType<Y> = when (this) {
Basic -> Basic
Digest -> Digest
External -> External
is OAuth2 -> OAuth2(f(authorizationServer), grantsTypes)
TLS -> TLS
}

sealed class RSSPMetadataError(cause: Throwable) : Throwable(cause), Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,97 +29,59 @@ import kotlinx.serialization.json.put
import java.net.URI
import java.util.*

sealed interface ValidatedAuthType {
data object External : ValidatedAuthType
data object Basic : ValidatedAuthType
data object Digest : ValidatedAuthType
data object TLS : ValidatedAuthType
data class OAuth2(
val oauth2Issuer: HttpsUrl? = null,
val oauth2: HttpsUrl? = null,
val grantsTypes: Set<Oauth2Grant>,
) : ValidatedAuthType {
init {
require(grantsTypes.isNotEmpty()) { "At least one GrantType must be provided" }
require(oauth2Issuer != null || oauth2 != null) { "At least one of oauth2Issuer or oauth2 must be provided" }
}
}
internal sealed interface AuthorizationServerRef {
@JvmInline
value class IssuerClaim(val value: HttpsUrl) : AuthorizationServerRef

@JvmInline
value class CSCAuth2Claim(val value: HttpsUrl) : AuthorizationServerRef
}

internal data class ValidatedRSSPMetadata(
val specs: String,
val name: String,
val logo: URI,
val region: String,
val lang: Locale,
val description: String,
val authTypes: Set<ValidatedAuthType>,
val asynchronousOperationMode: Boolean? = false,
val methods: List<RSSPMethod>,
val validationInfo: Boolean? = false,
)
internal class DefaultRSSPMetadataResolver(
private val httpClient: HttpClient,
) : RSSPMetadataResolver {

internal class DefaultRSSPMetadataResolver(private val httpClient: HttpClient) : RSSPMetadataResolver {
override suspend fun resolve(rsspId: RSSPId, lang: Locale?): Result<RSSPMetadata> = runCatching {
val json: String = try {
override suspend fun resolve(rsspId: RSSPId, lang: Locale?): Result<RSSPMetadata> =
runCatching {
val metadataInJson = fetchMetadata(rsspId, lang)
val contents = RSSPMetadataJsonParser.parseMetaData(rsspId, metadataInJson)
val resolved = contents.map {
serverRef ->
fetchAuthorizationServerMetadata(serverRef, contents.methods)
}
resolved
}

private suspend fun fetchMetadata(rsspId: RSSPId, lang: Locale?): String =
try {
httpClient.post(rsspId.info()) {
contentType(ContentType.Application.Json)
setBody(
buildJsonObject {
lang?.let { put("lang", it.toLanguageTag()) }
},
)
}.body()
}.body<String>()
} catch (t: Throwable) {
throw RSSPMetadataError.UnableToFetchRSSPMetadata(t)
}

val validated = RSSPMetadataJsonParser.parseMetaData(json)
resolveOauth2Meta(rsspId, validated)
}
private suspend fun fetchAuthorizationServerMetadata(
serverRef: AuthorizationServerRef,
methods: List<RSSPMethod>,
): CSCAuthorizationServerMetadata = when (serverRef) {
is AuthorizationServerRef.IssuerClaim ->
DefaultAuthorizationServerMetadataResolver(httpClient).resolve(serverRef.value).getOrThrow()

private suspend fun resolveOauth2Meta(
rsspId: RSSPId,
validatedRSSPMetadata: ValidatedRSSPMetadata,
): RSSPMetadata {
return RSSPMetadata(
rsspId = rsspId,
specs = validatedRSSPMetadata.specs,
name = validatedRSSPMetadata.name,
logo = validatedRSSPMetadata.logo,
region = validatedRSSPMetadata.region,
lang = validatedRSSPMetadata.lang,
methods = validatedRSSPMetadata.methods,
asynchronousOperationMode = validatedRSSPMetadata.asynchronousOperationMode,
validationInfo = validatedRSSPMetadata.validationInfo,
description = validatedRSSPMetadata.description,
authTypes = validatedRSSPMetadata.authTypes.map { authType ->
when (authType) {
ValidatedAuthType.Basic -> AuthType.Basic
ValidatedAuthType.Digest -> AuthType.Digest
ValidatedAuthType.External -> AuthType.External
ValidatedAuthType.TLS -> AuthType.TLS
is ValidatedAuthType.OAuth2 -> resolveOauth2(authType, validatedRSSPMetadata.methods)
}
}.let { types -> AuthTypesSupported(types.toSet()) },
)
}

private suspend fun resolveOauth2(authType: ValidatedAuthType.OAuth2, methods: List<RSSPMethod>): AuthType.OAuth2 {
val (oauth2Issuer, oauth2, grants) = authType
val meta = when {
oauth2Issuer != null -> DefaultAuthorizationServerMetadataResolver(httpClient).resolve(oauth2Issuer).getOrThrow()
oauth2 != null -> asMetadata(oauth2, methods)
else -> error("Cannot happen")
}
return AuthType.OAuth2(meta, grants)
is AuthorizationServerRef.CSCAuth2Claim ->
asMetadata(serverRef.value, methods)
}
}

internal fun asMetadata(
oauth2Url: HttpsUrl,
methods: List<RSSPMethod>,
): ReadOnlyAuthorizationServerMetadata {
): CSCAuthorizationServerMetadata {
val issuer = Issuer(oauth2Url.toString())
val meta = AuthorizationServerMetadata(issuer).apply {
tokenEndpointURI = URI("$oauth2Url/token")
Expand All @@ -141,3 +103,21 @@ private fun RSSPId.info() = URLBuilder(Url(value.value.toURI()))
.build()
.toURI()
.toURL()

internal inline fun <reified T, reified Y> RSSPMetadataContent<T>.map(f: (T) -> Y): RSSPMetadataContent<Y> {
val authTypes = authTypes.map { authType -> authType.map { f(it) } }.toSet()

return RSSPMetadataContent(
rsspId = rsspId,
specs = specs,
name = name,
logo = logo,
region = region,
lang = lang,
methods = methods,
asynchronousOperationMode = asynchronousOperationMode,
validationInfo = validationInfo,
description = description,
authTypes = authTypes,
)
}
Loading

0 comments on commit 44626ad

Please sign in to comment.