Skip to content

Commit

Permalink
Make RequestParametersFrom generic
Browse files Browse the repository at this point in the history
  • Loading branch information
n0900 committed Nov 12, 2024
1 parent 333e693 commit 0410b3f
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 194 deletions.
Original file line number Diff line number Diff line change
@@ -1,46 +1,115 @@
package at.asitplus.openid


import at.asitplus.catching
import io.ktor.http.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.jsonObject

class RequestParametersFromClassSerializer<T : RequestParameters>(
private val parameterSerializer: KSerializer<T>,
) : KSerializer<RequestParametersFromClass<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("RequestParametersFromClass") {
element("JwsSigned", parameterSerializer.descriptor)
element("Json", buildClassSerialDescriptor("Json") {
element<String>("jsonString")
element("parameters", parameterSerializer.descriptor)
})
element("Uri", parameterSerializer.descriptor)
}

override fun deserialize(decoder: Decoder): RequestParametersFromClass<T> {
// Decoder -> JsonDecoder
require(decoder is JsonDecoder) // this class can be decoded only by Json
// JsonDecoder -> JsonElement
val element = decoder.decodeJsonElement()
return when {
"jsonString" in element.jsonObject -> RequestParametersFromClass.Json(
decoder.json.decodeFromJsonElement<String>(element.jsonObject["jsonString"]!!),
decoder.json.decodeFromJsonElement(parameterSerializer, element.jsonObject["parameters"]!!)
)

@Serializable
sealed class AuthenticationRequestParametersFrom : RequestParametersFrom {
else -> TODO()
}
}

fun serialize(): String = odcJsonSerializer.encodeToString(this)
override fun serialize(encoder: Encoder, value: RequestParametersFromClass<T>) {
// Encoder -> JsonEncoder
require(encoder is JsonEncoder) // This class can be encoded only by Json
// value -> JsonElement
val element = when (value) {
is RequestParametersFromClass.Json -> buildJsonObject {
put("jsonString", encoder.json.encodeToJsonElement(value.jsonString))
put(
"parameters", encoder.json.encodeToJsonElement(
parameterSerializer,
value.parameters
)
)
}

companion object {
fun deserialize(input: String) =
catching { odcJsonSerializer.decodeFromString<AuthenticationRequestParametersFrom>(input) }
else -> TODO()
}
// JsonElement -> JsonEncoder
encoder.encodeJsonElement(element)
}
}


@Serializable(with = RequestParametersFromClassSerializer::class)
sealed class RequestParametersFromClass<S : RequestParameters> {

abstract override val parameters: AuthenticationRequestParameters
abstract val parameters: S

@Serializable
@SerialName("JwsSigned")
data class JwsSigned(
data class JwsSigned<T : RequestParameters>(
@Serializable(JwsSignedSerializer::class)
val jwsSigned: at.asitplus.signum.indispensable.josef.JwsSigned<ByteArray>,
override val parameters: AuthenticationRequestParameters,
) : AuthenticationRequestParametersFrom()
val jwsSigned: at.asitplus.signum.indispensable.josef.JwsSigned<T>,
override val parameters: T,
) : RequestParametersFromClass<T>() {

}

@Serializable
@SerialName("Uri")
data class Uri(
data class Uri<T : RequestParameters>(
@Serializable(UrlSerializer::class)
val url: Url,
override val parameters: AuthenticationRequestParameters,
) : AuthenticationRequestParametersFrom()
override val parameters: T,
) : RequestParametersFromClass<T>() {

// override inline fun serialize(json: kotlinx.serialization.json.Json): String = json.encodeToString(this)
}

@Serializable
@SerialName("Json")
data class Json(
data class Json<T : RequestParameters>(
val jsonString: String,
override val parameters: AuthenticationRequestParameters,
) : AuthenticationRequestParametersFrom()
override val parameters: T,
) : RequestParametersFromClass<T>() {

// override fun serialize(json: kotlinx.serialization.json.Json): String = json.encodeToString(this)
}

// companion object {
// inline fun <P : RequestParameters> deserialize(
// it: String,
// json: kotlinx.serialization.json.Json = kotlinx.serialization.json.Json,
// ): KmmResult<RequestParametersFromClass<P>> =
// catching { json.decodeFromString<RequestParametersFromClass<P>>(it) }
// }
}


Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package at.asitplus.openid

interface RequestParametersFrom {
val parameters: RequestParameters
}
//
//interface RequestParametersFrom <T:RequestParameters>{
// val parameters: T
//}
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
package at.asitplus.rqes

import at.asitplus.catching
import at.asitplus.openid.JwsSignedSerializer
import at.asitplus.openid.RequestParametersFrom
import at.asitplus.openid.UrlSerializer
import io.ktor.http.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString


@Serializable
sealed class SignatureRequestParametersFrom : RequestParametersFrom {
fun serialize(): String = rdcJsonSerializer.encodeToString(this)

companion object {
fun deserialize(input: String) =
catching { rdcJsonSerializer.decodeFromString<SignatureRequestParameters>(input) }
}

abstract override val parameters: SignatureRequestParameters

@Serializable
@SerialName("JwsSigned")
data class JwsSigned(
@Serializable(JwsSignedSerializer::class)
val jwsSigned: at.asitplus.signum.indispensable.josef.JwsSigned<ByteArray>,
override val parameters: SignatureRequestParameters,
) : SignatureRequestParametersFrom()

@Serializable
@SerialName("Uri")
data class Uri(
@Serializable(UrlSerializer::class)
val url: Url,
override val parameters: SignatureRequestParameters,
) : SignatureRequestParametersFrom()

@Serializable
@SerialName("Json")
data class Json(
val jsonString: String,
override val parameters: SignatureRequestParameters,
) : SignatureRequestParametersFrom()

}
//
//import at.asitplus.catching
//import at.asitplus.openid.JwsSignedSerializer
//import at.asitplus.openid.RequestParametersFrom
//import at.asitplus.openid.UrlSerializer
//import io.ktor.http.*
//import kotlinx.serialization.SerialName
//import kotlinx.serialization.Serializable
//import kotlinx.serialization.encodeToString
//
//
//@Serializable
//sealed class SignatureRequestParametersFrom : RequestParametersFrom<SignatureRequestParameters> {
// fun serialize(): String = rdcJsonSerializer.encodeToString(this)
//
// companion object {
// fun deserialize(input: String) =
// catching { rdcJsonSerializer.decodeFromString<SignatureRequestParameters>(input) }
// }
//
// @Serializable
// @SerialName("JwsSigned")
// data class JwsSigned(
// @Serializable(JwsSignedSerializer::class)
// val jwsSigned: at.asitplus.signum.indispensable.josef.JwsSigned<SignatureRequestParameters>,
// override val parameters: SignatureRequestParameters,
// ) : SignatureRequestParametersFrom()
//
// @Serializable
// @SerialName("Uri")
// data class Uri(
// @Serializable(UrlSerializer::class)
// val url: Url,
// override val parameters: SignatureRequestParameters,
// ) : SignatureRequestParametersFrom()
//
// @Serializable
// @SerialName("Json")
// data class Json(
// val jsonString: String,
// override val parameters: SignatureRequestParameters,
// ) : SignatureRequestParametersFrom()
//
//}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import at.asitplus.KmmResult
import at.asitplus.catching
import at.asitplus.dif.PresentationDefinition
import at.asitplus.openid.AuthenticationRequestParameters
import at.asitplus.openid.AuthenticationRequestParametersFrom
import at.asitplus.openid.AuthenticationResponseParameters
import at.asitplus.openid.IdTokenType
import at.asitplus.openid.OAuth2AuthorizationServerMetadata
Expand All @@ -18,6 +17,7 @@ import at.asitplus.openid.OpenIdConstants.URN_TYPE_JWK_THUMBPRINT
import at.asitplus.openid.OpenIdConstants.VP_TOKEN
import at.asitplus.openid.RelyingPartyMetadata
import at.asitplus.openid.RequestParameters
import at.asitplus.openid.RequestParametersFromClass
import at.asitplus.signum.indispensable.CryptoPublicKey
import at.asitplus.signum.indispensable.io.Base64UrlStrict
import at.asitplus.signum.indispensable.josef.JsonWebKey
Expand Down Expand Up @@ -78,7 +78,7 @@ class OidcSiopWallet(
/**
* Used to resolve [RequestParameters] by reference and also matches them to the correct [RequestParametersFrom]
*/
private val requestParser: RequestParser
private val requestParser: RequestParser,
) {
constructor(
keyMaterial: KeyMaterial = EphemeralKeyWithoutCert(),
Expand All @@ -97,7 +97,7 @@ class OidcSiopWallet(
* Need to verify the request object serialized as a JWS,
* which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PreRegistered]).
*/
requestObjectJwsVerifier: RequestObjectJwsVerifier = RequestObjectJwsVerifier { _,_ -> true },
requestObjectJwsVerifier: RequestObjectJwsVerifier = RequestObjectJwsVerifier { _ -> true },
/**
* Need to implement if the presentation definition needs to be derived from a scope value.
* See [ScopePresentationDefinitionRetriever] for implementation instructions.
Expand Down Expand Up @@ -149,15 +149,18 @@ class OidcSiopWallet(
* to create [AuthenticationResponseParameters] that can be sent back to the Verifier, see
* [AuthenticationResponseResult].
*/
suspend fun parseAuthenticationRequestParameters(input: String): KmmResult<AuthenticationRequestParametersFrom> =
requestParser.parseRequestParameters(input).transform { KmmResult(it as AuthenticationRequestParametersFrom) }

suspend fun parseAuthenticationRequestParameters(input: String): KmmResult<RequestParametersFromClass<AuthenticationRequestParameters>> {
val test = requestParser.parseRequestParameters(input)
.getOrThrow()
val test2 = catching { test as RequestParametersFromClass<AuthenticationRequestParameters> }
return test2
}
/**
* Pass in the deserialized [AuthenticationRequestParameters], which were either encoded as query params,
* or JSON serialized as a JWT Request Object.
*/
suspend fun createAuthnResponse(
request: AuthenticationRequestParametersFrom,
request: RequestParametersFromClass<AuthenticationRequestParameters>,
): KmmResult<AuthenticationResponseResult> =
createAuthnResponseParams(request).map {
AuthenticationResponseFactory(jwsService).createAuthenticationResponse(request, it)
Expand All @@ -167,7 +170,7 @@ class OidcSiopWallet(
* Creates the authentication response from the RP's [params]
*/
suspend fun createAuthnResponseParams(
params: AuthenticationRequestParametersFrom,
params: RequestParametersFromClass<AuthenticationRequestParameters>,
): KmmResult<AuthenticationResponse> = startAuthorizationResponsePreparation(params).map {
finalizeAuthorizationResponseParameters(
request = params,
Expand All @@ -189,7 +192,7 @@ class OidcSiopWallet(
* Starts the authorization response building process from the RP's authentication request in [params]
*/
suspend fun startAuthorizationResponsePreparation(
params: AuthenticationRequestParametersFrom,
params: RequestParametersFromClass<AuthenticationRequestParameters>,
): KmmResult<AuthorizationResponsePreparationState> = catching {
val clientMetadata = params.parameters.loadClientMetadata()
val presentationDefinition = params.parameters.loadPresentationDefinition()
Expand All @@ -205,7 +208,7 @@ class OidcSiopWallet(
* @param inputDescriptorSubmissions Map from input descriptor ids to [CredentialSubmission]
*/
suspend fun finalizeAuthorizationResponse(
request: AuthenticationRequestParametersFrom,
request: RequestParametersFromClass<AuthenticationRequestParameters>,
preparationState: AuthorizationResponsePreparationState,
inputDescriptorSubmissions: Map<String, CredentialSubmission>? = null,
): KmmResult<AuthenticationResponseResult> = finalizeAuthorizationResponseParameters(
Expand All @@ -228,11 +231,11 @@ class OidcSiopWallet(
* @param inputDescriptorSubmissions Map from input descriptor ids to [CredentialSubmission]
*/
suspend fun finalizeAuthorizationResponseParameters(
request: AuthenticationRequestParametersFrom,
request: RequestParametersFromClass<AuthenticationRequestParameters>,
preparationState: AuthorizationResponsePreparationState,
inputDescriptorSubmissions: Map<String, CredentialSubmission>? = null,
): KmmResult<AuthenticationResponse> = preparationState.catching {
val certKey = (request as? AuthenticationRequestParametersFrom.JwsSigned)
val certKey = (request as? RequestParametersFromClass.JwsSigned<AuthenticationRequestParameters>)
?.jwsSigned?.header?.certificateChain?.firstOrNull()?.publicKey?.toJsonWebKey()
val clientJsonWebKeySet = clientMetadata?.loadJsonWebKeySet()
val audience = request.extractAudience(clientJsonWebKeySet)
Expand Down Expand Up @@ -266,7 +269,7 @@ class OidcSiopWallet(
}

@Throws(OAuth2Exception::class)
private fun AuthenticationRequestParametersFrom.extractAudience(
private fun RequestParametersFromClass<AuthenticationRequestParameters>.extractAudience(
clientJsonWebKeySet: JsonWebKeySet?,
) = clientJsonWebKeySet?.keys?.firstOrNull()
?.let { it.keyId ?: it.didEncoded ?: it.jwkThumbprint }
Expand Down Expand Up @@ -330,7 +333,7 @@ typealias ScopePresentationDefinitionRetriever = suspend (String) -> Presentatio
* Implementations need to verify the passed [JwsSigned] and return its result
*/
fun interface RequestObjectJwsVerifier {
operator fun invoke(jws: JwsSigned<ByteArray>, request: RequestParameters): Boolean
operator fun invoke(jws: JwsSigned<RequestParameters>): Boolean
}

private fun Collection<JsonWebKey>?.combine(certKey: JsonWebKey?): Collection<JsonWebKey> {
Expand Down
Loading

0 comments on commit 0410b3f

Please sign in to comment.