From efdff98106e6fa1edd0be047710b37b641a620e8 Mon Sep 17 00:00:00 2001 From: babisRoutis Date: Fri, 22 Nov 2024 11:51:40 +0200 Subject: [PATCH 01/15] Aligned PID SdJwtVc model --- .../pidissuer/adapter/out/oauth/Claims.kt | 8 ++-- .../adapter/out/pid/EncodePidInSdJwtVc.kt | 47 +++++++++++-------- .../adapter/out/pid/IssueSdJwtVcPid.kt | 27 +++++------ .../ec/eudi/pidissuer/adapter/out/pid/Pid.kt | 12 ++++- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt index ea2d1eba..da0fd409 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt @@ -69,11 +69,11 @@ val OidcGender: AttributeDetails by lazy { @Serializable data class OidcAddressClaim( @SerialName("street_address") val streetAddress: String? = null, - val locality: String? = null, - val region: String? = null, + @SerialName("locality") val locality: String? = null, + @SerialName("region") val region: String? = null, @SerialName("postal_code") val postalCode: String? = null, - val country: String? = null, - val formatted: String? = null, + @SerialName("country") val country: String? = null, + @SerialName("formatted") val formatted: String? = null, @SerialName("house_number") val houseNumber: String? = null, ) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index b8319bd8..7b59bc33 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -122,42 +122,32 @@ private fun selectivelyDisclosed( nbf?.let { nbf(it.epochSecond) } exp(exp.epochSecond) cnf(holderPubKey) - plain("vct", vct.value) + plain("vct", vct.value) // TODO add "urn:" // // Selectively Disclosed claims // - // https://openid.net/specs/openid-connect-4-identity-assurance-1_0.html#section-4 - sd(Attributes.IssuanceDate.name, pidMetaData.issuanceDate.toString()) // TODO Check issuance date - sd(OidcGivenName.name, pid.givenName.value) - // TODO Check also_known_as sd(OidcFamilyName.name, pid.familyName.value) + sd(OidcGivenName.name, pid.givenName.value) sd(OidcBirthDate.name, pid.birthDate.toString()) structured(Attributes.AgeEqualOrOver.name) { pid.ageOver18?.let { sd(Attributes.AgeOver18.name, it) } } - - // TODO - // Here we need a mapping in OIDC gender can be male, female on null - // In PID the use iso - pid.gender?.let { sd(OidcGender.name, it.value.toInt()) } - pid.nationality?.let { - val nationalities = buildJsonArray { add(it.value) } - sd(OidcAssuranceNationalities.name, nationalities) - } pid.ageInYears?.let { sd(Attributes.AgeInYears.name, it.toInt()) } - pid.ageBirthYear?.let { sd(Attributes.BirthDateYear.name, it.value.toString()) } + pid.ageBirthYear?.let { sd(Attributes.AgeBirthYear.name, it.value.toString()) } pid.familyNameBirth?.let { sd(OidcAssuranceBirthFamilyName.name, it.value) } pid.givenNameBirth?.let { sd(OidcAssuranceBirthGivenName.name, it.value) } + pid.oidcAssurancePlaceOfBirth()?.let { placeOfBirth -> - recursive(OidcAssurancePlaceOfBirth.NAME) { + // TODO double-check the names of the nested fields + structured(OidcAssurancePlaceOfBirth.NAME) { placeOfBirth.locality?.let { sd("locality", it) } placeOfBirth.region?.let { sd("region", it) } placeOfBirth.country?.let { sd("country", it) } } } pid.oidcAddressClaim()?.let { address -> - recursive(OidcAddressClaim.NAME) { + structured(OidcAddressClaim.NAME) { address.formatted?.let { sd("formatted", it) } address.country?.let { sd("country", it) } address.region?.let { sd("region", it) } @@ -167,30 +157,47 @@ private fun selectivelyDisclosed( address.houseNumber?.let { sd("house_number", it) } } } + // TODO + // Add a new field to the PID + pid.gender?.let { sd(OidcGender.name, it.value.toInt()) } + pid.nationality?.let { + val nationalities = buildJsonArray { add(it.value) } + sd(OidcAssuranceNationalities.name, nationalities) + } + sd(IssuingAuthorityAttribute.name, pidMetaData.issuingAuthority.valueAsString()) + pidMetaData.documentNumber?.let { sd(DocumentNumberAttribute.name, it.value) } + pidMetaData.administrativeNumber?.let { sd(AdministrativeNumberAttribute.name, it.value) } + sd(IssuingCountryAttribute.name, pidMetaData.issuingCountry.value) + pidMetaData.issuingJurisdiction?.let { sd(IssuingJurisdictionAttribute.name, it) } } } private fun Pid.oidcAssurancePlaceOfBirth(): OidcAssurancePlaceOfBirth? = if (birthCountry != null || birthState != null || birthCity != null) { + // TODO + // birth_lace and birth_city are both mapped to locality + // https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/pull/160#discussion_r1853638874 OidcAssurancePlaceOfBirth( + locality = birthCity?.value, country = birthCountry?.value, region = residentState?.value, - locality = residentCity?.value, ) } else null private fun Pid.oidcAddressClaim(): OidcAddressClaim? = if ( - residentCountry != null || residentState != null || + residentAddress != null || residentCountry != null || residentState != null || residentCity != null || residentPostalCode != null || - residentStreet != null + residentStreet != null || residentHouseNumber != null ) { OidcAddressClaim( + formatted = residentAddress, country = residentCountry?.value, region = residentState?.value, locality = residentCity?.value, postalCode = residentPostalCode?.value, streetAddress = residentStreet?.value, + houseNumber = residentHouseNumber, ) } else null diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index ac417a63..54690aca 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -45,8 +45,8 @@ val PidSdJwtVcScope: Scope = Scope("${PID_DOCTYPE}_vc_sd_jwt") internal object Attributes { - val BirthDateYear = AttributeDetails( - name = "birthdate_year", + val AgeBirthYear = AttributeDetails( + name = "age_birth_year", mandatory = false, ) val AgeEqualOrOver = AttributeDetails( @@ -63,25 +63,24 @@ internal object Attributes { display = mapOf(Locale.ENGLISH to "The subject’s current age in years."), ) - val IssuanceDate = AttributeDetails( - name = "issuance_date", - mandatory = true, - ) - val pidAttributes = listOf( OidcFamilyName, OidcGivenName, OidcBirthDate, - OidcAddressClaim.attribute, - OidcGender, - OidcAssuranceNationalities, - OidcAssuranceBirthFamilyName, - OidcAssuranceBirthGivenName, AgeEqualOrOver, AgeInYears, + AgeBirthYear, + OidcAssuranceBirthFamilyName, + OidcAssuranceBirthGivenName, OidcAssurancePlaceOfBirth.attribute, - IssuanceDate, - BirthDateYear, + OidcAddressClaim.attribute, + OidcGender, + OidcAssuranceNationalities, + IssuingAuthorityAttribute, + DocumentNumberAttribute, + AdministrativeNumberAttribute, + IssuingCountryAttribute, + IssuingJurisdictionAttribute, ) } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt index f06a6ce9..ae3a7828 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt @@ -121,8 +121,16 @@ data class Pid( * if there is no separate authority authorized to issue PID */ sealed interface IssuingAuthority { - data class MemberState(val code: IsoCountry) : IssuingAuthority - data class AdministrativeAuthority(val value: String) : IssuingAuthority + @JvmInline + value class MemberState(val code: IsoCountry) : IssuingAuthority + + @JvmInline + value class AdministrativeAuthority(val value: String) : IssuingAuthority + + fun valueAsString(): String = when (this) { + is AdministrativeAuthority -> value + is MemberState -> code.value + } } @JvmInline From 9cf526003446a19019aa7e7088f495f7cb661c79 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 12:24:38 +0200 Subject: [PATCH 02/15] Fix numeric value of ISO Gender 'Not Applicable'. --- docker-compose/keycloak/realms/pid-issuer-realm-realm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json index 1657bfa0..768c0a66 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json @@ -1644,7 +1644,7 @@ "subComponents": {}, "config": { "kc.user.profile.config": [ - "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"3\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"3\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputType\":\"select\",\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}}],\"unmanagedAttributePolicy\":\"ENABLED\"}" + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputType\":\"select\",\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] } } From ca069a915f493567f6dc2d955b5e6ac1c7a3d38e Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 12:26:57 +0200 Subject: [PATCH 03/15] Add mapping between IsoGender and OidcGender. --- .../adapter/out/pid/EncodePidInSdJwtVc.kt | 17 ++++++++++++++--- .../adapter/out/pid/IssueSdJwtVcPid.kt | 5 +++-- .../ec/eudi/pidissuer/adapter/out/pid/Pid.kt | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index 7b59bc33..fde8cdb1 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory import java.time.Clock import java.time.Instant import java.time.ZonedDateTime +import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcGender as OidcGenderAttribute private val log = LoggerFactory.getLogger(EncodePidInSdJwtVc::class.java) @@ -157,9 +158,7 @@ private fun selectivelyDisclosed( address.houseNumber?.let { sd("house_number", it) } } } - // TODO - // Add a new field to the PID - pid.gender?.let { sd(OidcGender.name, it.value.toInt()) } + pid.gender?.let { sd(OidcGenderAttribute.name, it.toOidGender().value) } pid.nationality?.let { val nationalities = buildJsonArray { add(it.value) } sd(OidcAssuranceNationalities.name, nationalities) @@ -220,3 +219,15 @@ private object Printer { return str } } + +/** + * Converts an [IsoGender] to an [OidcGender]. + */ +private fun IsoGender.toOidGender(): OidcGender = + when (value) { + 0u -> OidcGender("not known") + 1u -> OidcGender.Male + 2u -> OidcGender.Female + 9u -> OidcGender("not applicable") + else -> OidcGender(value.toString()) + } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index 54690aca..6d1fd50c 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -40,6 +40,7 @@ import java.time.Clock import java.time.Instant import java.time.ZonedDateTime import java.util.* +import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcGender as OidcGenderAttribute val PidSdJwtVcScope: Scope = Scope("${PID_DOCTYPE}_vc_sd_jwt") @@ -63,7 +64,7 @@ internal object Attributes { display = mapOf(Locale.ENGLISH to "The subject’s current age in years."), ) - val pidAttributes = listOf( + val pidAttributes: List = listOf( OidcFamilyName, OidcGivenName, OidcBirthDate, @@ -74,7 +75,7 @@ internal object Attributes { OidcAssuranceBirthGivenName, OidcAssurancePlaceOfBirth.attribute, OidcAddressClaim.attribute, - OidcGender, + OidcGenderAttribute, OidcAssuranceNationalities, IssuingAuthorityAttribute, DocumentNumberAttribute, diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt index ae3a7828..b1a8fabd 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt @@ -52,6 +52,24 @@ typealias Address = String @JvmInline value class IsoGender(val value: UInt) +/** + * Gender, using a value as defined in OpenID Connect Core 1.0. + */ +@JvmInline +value class OidcGender private constructor(val value: String) { + companion object { + val Male: OidcGender = OidcGender("male") + val Female: OidcGender = OidcGender("female") + + operator fun invoke(value: String): OidcGender = + when (value) { + "male" -> Male + "female" -> Female + else -> OidcGender(value) + } + } +} + typealias Nationality = IsoCountry /** From ebb5244d56cb754f3b19f6bb6da3072589a8a881 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 12:44:07 +0200 Subject: [PATCH 04/15] Align `vct` with ARF value. --- .../adapter/out/pid/EncodePidInSdJwtVc.kt | 2 +- .../adapter/out/pid/IssueMsoMdocPid.kt | 24 ++++++++++--------- .../adapter/out/pid/IssueSdJwtVcPid.kt | 4 +++- .../pidissuer/adapter/out/pid/PidCmnMeta.kt | 6 ----- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index fde8cdb1..a7c62263 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -123,7 +123,7 @@ private fun selectivelyDisclosed( nbf?.let { nbf(it.epochSecond) } exp(exp.epochSecond) cnf(holderPubKey) - plain("vct", vct.value) // TODO add "urn:" + plain("vct", vct.value) // // Selectively Disclosed claims diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt index c4e876f5..c7b4f372 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt @@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory import java.time.Clock import java.util.* -val PidMsoMdocScope: Scope = Scope("${PID_DOCTYPE}_${MSO_MDOC_FORMAT.value}") +val PidMsoMdocScope: Scope = Scope("eu.europa.ec.eudi.pid_mso_mdoc") val PidMsoMdocNamespace: MsoNameSpace = pidNameSpace(1) @@ -225,6 +225,18 @@ private val pidAttributes = PidMsoMdocNamespace to listOf( IssuingJurisdictionAttribute, ) +private const val PID_DOCTYPE = "eu.europa.ec.eudi.pid" + +private fun pidDocType(v: Int?): String = + if (v == null) PID_DOCTYPE + else "$PID_DOCTYPE.$v" + +private fun pidNameSpace(v: Int?): MsoNameSpace = pidDocType(v) + +private fun pidDomesticNameSpace(v: Int?, countryCode: String): MsoNameSpace = + if (v == null) "$PID_DOCTYPE.$countryCode" + else "$PID_DOCTYPE.$countryCode.$v" + val PidMsoMdocV1: MsoMdocCredentialConfiguration = MsoMdocCredentialConfiguration( id = CredentialConfigurationId(PidMsoMdocScope.value), @@ -242,16 +254,6 @@ val PidMsoMdocV1: MsoMdocCredentialConfiguration = policy = MsoMdocPolicy(oneTimeUse = true), ) -// -// Meta -// - -private fun pidDomesticNameSpace(v: Int?, countryCode: String): MsoNameSpace = - if (v == null) "$PID_DOCTYPE.$countryCode" - else "$PID_DOCTYPE.$countryCode.$v" - -private fun pidNameSpace(v: Int?): MsoNameSpace = pidDocType(v) - /** * Service for issuing PID MsoMdoc credential */ diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index 6d1fd50c..4010c799 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -42,7 +42,7 @@ import java.time.ZonedDateTime import java.util.* import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcGender as OidcGenderAttribute -val PidSdJwtVcScope: Scope = Scope("${PID_DOCTYPE}_vc_sd_jwt") +val PidSdJwtVcScope: Scope = Scope("eu.europa.ec.eudi.pid_vc_sd_jwt") internal object Attributes { @@ -85,6 +85,8 @@ internal object Attributes { ) } +private fun pidDocType(version: Int): String = "urn:eu.europa.ec.eudi:pid:$version" + fun pidSdJwtVcV1(signingAlgorithm: JWSAlgorithm): SdJwtVcCredentialConfiguration = SdJwtVcCredentialConfiguration( id = CredentialConfigurationId(PidSdJwtVcScope.value), diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/PidCmnMeta.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/PidCmnMeta.kt index 3d41a01b..9c39c3a0 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/PidCmnMeta.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/PidCmnMeta.kt @@ -19,12 +19,6 @@ import eu.europa.ec.eudi.pidissuer.domain.* import java.net.URI import java.util.Locale.ENGLISH -internal const val PID_DOCTYPE = "eu.europa.ec.eudi.pid" - -internal fun pidDocType(v: Int?): String = - if (v == null) PID_DOCTYPE - else "$PID_DOCTYPE.$v" - val pidDisplay = listOf( CredentialDisplay( name = DisplayName("PID", ENGLISH), From 55a5e084498dae2d8ab7cc76834a24b2b7266857 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 12:54:56 +0200 Subject: [PATCH 05/15] Fix incorrect mapping of birthplace values. --- .../pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index a7c62263..d10de655 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -140,7 +140,6 @@ private fun selectivelyDisclosed( pid.givenNameBirth?.let { sd(OidcAssuranceBirthGivenName.name, it.value) } pid.oidcAssurancePlaceOfBirth()?.let { placeOfBirth -> - // TODO double-check the names of the nested fields structured(OidcAssurancePlaceOfBirth.NAME) { placeOfBirth.locality?.let { sd("locality", it) } placeOfBirth.region?.let { sd("region", it) } @@ -148,6 +147,7 @@ private fun selectivelyDisclosed( } } pid.oidcAddressClaim()?.let { address -> + // TODO double-check the names of the nested fields structured(OidcAddressClaim.NAME) { address.formatted?.let { sd("formatted", it) } address.country?.let { sd("country", it) } @@ -172,14 +172,14 @@ private fun selectivelyDisclosed( } private fun Pid.oidcAssurancePlaceOfBirth(): OidcAssurancePlaceOfBirth? = - if (birthCountry != null || birthState != null || birthCity != null) { + if (birthPlace != null || birthCountry != null || birthState != null || birthCity != null) { // TODO - // birth_lace and birth_city are both mapped to locality + // birth_place and birth_city are both mapped to locality // https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/pull/160#discussion_r1853638874 OidcAssurancePlaceOfBirth( - locality = birthCity?.value, + locality = birthPlace ?: birthCity?.value, country = birthCountry?.value, - region = residentState?.value, + region = birthState?.value, ) } else null From 288e3e9a2cb0c7bab299db47ff74614196e6ef18 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 14:35:18 +0200 Subject: [PATCH 06/15] Update claim descriptions as per PID Rulebook. --- .../pidissuer/adapter/out/oauth/Claims.kt | 15 +++++---- .../adapter/out/pid/IssueMsoMdocPid.kt | 32 +++++++++---------- .../adapter/out/pid/IssueSdJwtVcPid.kt | 7 ++-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt index da0fd409..c8adc7b8 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/oauth/Claims.kt @@ -39,21 +39,21 @@ val OidcSub: AttributeDetails by lazy { val OidcFamilyName: AttributeDetails by lazy { AttributeDetails( name = "family_name", - display = mapOf(Locale.ENGLISH to "Current Family Name"), + display = mapOf(Locale.ENGLISH to "Current last name(s) or surname(s) of the PID User."), ) } val OidcGivenName: AttributeDetails by lazy { AttributeDetails( name = "given_name", - display = mapOf(Locale.ENGLISH to "Current First Names"), + display = mapOf(Locale.ENGLISH to "Current first name(s), including middle name(s), of the PID User."), ) } val OidcBirthDate: AttributeDetails by lazy { AttributeDetails( name = "birthdate", - display = mapOf(Locale.ENGLISH to "Date of Birth"), + display = mapOf(Locale.ENGLISH to "Day, month, and year on which the PID User was born."), ) } @@ -61,7 +61,7 @@ val OidcGender: AttributeDetails by lazy { AttributeDetails( name = "gender", mandatory = false, - display = mapOf(Locale.ENGLISH to "PID User’s gender, using a value as defined in ISO/IEC 5218."), + display = mapOf(Locale.ENGLISH to "PID User’s gender, using a value as defined in OpenID Connect Core 1.0."), ) } @@ -84,7 +84,8 @@ data class OidcAddressClaim( name = NAME, mandatory = false, display = mapOf( - Locale.ENGLISH to "Resident street_address, country, region, locality and postal_code", + Locale.ENGLISH to "The full address of the place where the PID User currently resides and/or " + + "can be contacted (street name, house number, city etc.).", ), ) } @@ -98,7 +99,7 @@ val OidcAssuranceNationalities: AttributeDetails by lazy { AttributeDetails( name = "nationalities", mandatory = false, - display = mapOf(Locale.ENGLISH to "Array of nationalities"), + display = mapOf(Locale.ENGLISH to "Alpha-2 country code as specified in ISO 3166-1, representing the nationality of the PID User."), ) } @@ -129,7 +130,7 @@ data class OidcAssurancePlaceOfBirth( get() = AttributeDetails( name = NAME, mandatory = false, - display = mapOf(Locale.ENGLISH to "The country, region, and locality"), + display = mapOf(Locale.ENGLISH to "The country, state, and city where the PID User was born."), ) } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt index c7b4f372..cace42c8 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt @@ -44,17 +44,17 @@ val PidMsoMdocNamespace: MsoNameSpace = pidNameSpace(1) val GivenNameAttribute = AttributeDetails( name = "given_name", mandatory = true, - display = mapOf(Locale.ENGLISH to "Current First Names"), + display = mapOf(Locale.ENGLISH to "Current first name(s), including middle name(s), of the PID User."), ) val FamilyNameAttribute = AttributeDetails( name = "family_name", mandatory = true, - display = mapOf(Locale.ENGLISH to "Current Family Name"), + display = mapOf(Locale.ENGLISH to "Current last name(s) or surname(s) of the PID User."), ) val BirthDateAttribute = AttributeDetails( name = "birth_date", mandatory = true, - display = mapOf(Locale.ENGLISH to "Date of Birth"), + display = mapOf(Locale.ENGLISH to "Day, month, and year on which the PID User was born."), ) val FamilyNameBirthAttribute = AttributeDetails( name = "family_name_birth", @@ -69,7 +69,7 @@ val GivenNameBirthAttribute = AttributeDetails( val GenderAttribute = AttributeDetails( name = "gender", mandatory = false, - display = mapOf(Locale.ENGLISH to "PID User’s gender, using a value as defined in ISO/IEC 5218."), + display = mapOf(Locale.ENGLISH to "PID User's gender, using a value as defined in ISO/IEC 5218."), ) val AgeOver18Attribute = AttributeDetails( name = "age_over_18", @@ -89,7 +89,7 @@ val AgeInYearsAttribute = AttributeDetails( val NationalityAttribute = AttributeDetails( name = "nationality", mandatory = false, - display = mapOf(Locale.ENGLISH to "Alpha-2 country code, representing the nationality of the PID User."), + display = mapOf(Locale.ENGLISH to "Alpha-2 country code as specified in ISO 3166-1, representing the nationality of the PID User."), ) val IssuanceDateAttribute = AttributeDetails( name = "issuance_date", @@ -113,22 +113,22 @@ val IssuingAuthorityAttribute = AttributeDetails( val BirthPlaceAttribute = AttributeDetails( name = "birth_place", mandatory = false, - display = mapOf(Locale.ENGLISH to "The country, state, and city where the PID User was born."), + display = mapOf(Locale.ENGLISH to "The state, province, district, or local area where the PID User was born."), ) val BirthCountryAttribute = AttributeDetails( name = "birth_country", mandatory = false, - display = mapOf(Locale.ENGLISH to "The country where the PID User was born, as an Alpha-2 country code."), + display = mapOf(Locale.ENGLISH to "The country where the PID User was born, as an Alpha-2 country code as specified in ISO 3166-1."), ) val BirthStateAttribute = AttributeDetails( name = "birth_state", mandatory = false, - display = mapOf(Locale.ENGLISH to "The state, province, district, or local area where the PID User was born. "), + display = mapOf(Locale.ENGLISH to "The state, province, district, or local area where the PID User was born."), ) val BirthCityAttribute = AttributeDetails( name = "birth_city", mandatory = false, - display = mapOf(Locale.ENGLISH to "The municipality, city, town, or village where the PID User was born. "), + display = mapOf(Locale.ENGLISH to "The municipality, city, town, or village where the PID User was born."), ) val ResidenceAddressAttribute = AttributeDetails( name = "resident_address", @@ -142,13 +142,13 @@ val ResidenceCountryAttribute = AttributeDetails( name = "resident_country", mandatory = false, display = mapOf( - Locale.ENGLISH to "he country where the PID User currently resides, as an Alpha-2 country code as specified in ISO 3166-1.", + Locale.ENGLISH to "The country where the PID User currently resides, as an Alpha-2 country code as specified in ISO 3166-1.", ), ) val ResidenceStateAttribute = AttributeDetails( name = "resident_state", mandatory = false, - display = mapOf(Locale.ENGLISH to "The state, province, district, or local area where the PID User currently resides"), + display = mapOf(Locale.ENGLISH to "The state, province, district, or local area where the PID User currently resides."), ) val ResidenceCityAttribute = AttributeDetails( name = "resident_city", @@ -163,7 +163,7 @@ val ResidencePostalCodeAttribute = AttributeDetails( val ResidenceStreetAttribute = AttributeDetails( name = "resident_street", mandatory = false, - display = mapOf(Locale.ENGLISH to "The name of the street where the PID User currently resides"), + display = mapOf(Locale.ENGLISH to "The name of the street where the PID User currently resides."), ) val ResidenceHouseNumberAttribute = AttributeDetails( name = "resident_house_number", @@ -176,14 +176,14 @@ val DocumentNumberAttribute = AttributeDetails( display = mapOf(Locale.ENGLISH to "A number for the PID, assigned by the PID Provider."), ) val AdministrativeNumberAttribute = AttributeDetails( - name = "document_number", + name = "administrative_number", mandatory = false, - display = mapOf(Locale.ENGLISH to "A number for the PID, assigned by the PID Provider."), + display = mapOf(Locale.ENGLISH to "A number assigned by the PID Provider for audit control or other purposes."), ) val IssuingCountryAttribute = AttributeDetails( name = "issuing_country", mandatory = true, - display = mapOf(Locale.ENGLISH to "Alpha-2 country code, as defined in ISO 3166-1, of the PID Provider’s country or territory."), + display = mapOf(Locale.ENGLISH to "Alpha-2 country code, as defined in ISO 3166-1, of the PID Provider's country or territory."), ) val IssuingJurisdictionAttribute = AttributeDetails( name = "issuing_jurisdiction", @@ -199,8 +199,8 @@ private val pidAttributes = PidMsoMdocNamespace to listOf( GivenNameAttribute, BirthDateAttribute, AgeOver18Attribute, - AgeBirthYearAttribute, AgeInYearsAttribute, + AgeBirthYearAttribute, FamilyNameBirthAttribute, GivenNameBirthAttribute, BirthPlaceAttribute, diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index 4010c799..f964860d 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -49,19 +49,20 @@ internal object Attributes { val AgeBirthYear = AttributeDetails( name = "age_birth_year", mandatory = false, + display = mapOf(Locale.ENGLISH to "The year when the PID User was born."), ) val AgeEqualOrOver = AttributeDetails( name = "age_equal_or_over", - display = mapOf(Locale.ENGLISH to "Age attestations"), + display = mapOf(Locale.ENGLISH to "Attesting attributes for the age of the PID User."), ) val AgeOver18 = AttributeDetails( name = "18", - display = mapOf(Locale.ENGLISH to "Adult or minor"), + display = mapOf(Locale.ENGLISH to "Attesting whether the PID User is currently an adult (true) or a minor (false)."), ) val AgeInYears = AttributeDetails( name = "age_in_years", - display = mapOf(Locale.ENGLISH to "The subject’s current age in years."), + display = mapOf(Locale.ENGLISH to "The current age of the PID User in years."), ) val pidAttributes: List = listOf( From 424740f1454bfa92ec4698834bd33dd731b663ac Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 14:35:37 +0200 Subject: [PATCH 07/15] Verify the remainder of the SD-JWT-VC claims as per PID Rulebook. --- .../ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index d10de655..16f62876 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -147,7 +147,6 @@ private fun selectivelyDisclosed( } } pid.oidcAddressClaim()?.let { address -> - // TODO double-check the names of the nested fields structured(OidcAddressClaim.NAME) { address.formatted?.let { sd("formatted", it) } address.country?.let { sd("country", it) } From 3cf1b13b929016a7a3bd9b0cec0c703882edddec Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 16:28:10 +0200 Subject: [PATCH 08/15] Populate missing PID Metadata values. --- README.md | 6 ++++- docker-compose/docker-compose.yaml | 1 + .../ec/eudi/pidissuer/PidIssuerApplication.kt | 11 ++++++---- .../out/pid/GetPidDataFromAuthServer.kt | 22 ++++++++++++++----- src/main/resources/application.properties | 1 + 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5d215036..9dcaa344 100644 --- a/README.md +++ b/README.md @@ -179,10 +179,14 @@ Variable: `ISSUER_PID_SD_JWT_VC_NOTIFICATIONS_ENABLED` Description: Whether to enabled Notifications Endpoint support for PIDs issued in *SD JWT VC*. Default value: `true` -Variable: `ISSUER_PID_ISSUING_COUNTRY` +Variable: `ISSUER_PID_ISSUINGCOUNTRY` Description: Code of the Country issuing the PID Default value: `GR` +Variable: `ISSUER_PID_ISSUINGJURISDICTION` +Description: Country subdivision code of the jurisdiction issuing the PID +Default value: `GR-I` + Variable: `ISSUER_MDL_ENABLED` Description: Whether to enable support for issuing mDL. Default value: `true` diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index c294cb15..7de0e163 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -65,6 +65,7 @@ services: - ISSUER_PID_SD_JWT_VC_DEFERRED=true - ISSUER_PID_SD_JWT_VC_NOTIFICATIONS_ENABLED=true - ISSUER_PID_ISSUINGCOUNTRY=GR + - ISSUER_PID_ISSUINGJURISDICTION=GR-I - ISSUER_MDL_ENABLED=true - ISSUER_MDL_MSO_MDOC_ENCODER_DURATION=P5D - ISSUER_MDL_NOTIFICATIONS_ENABLED=true diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index 4da3fd19..ec6eed51 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -273,10 +273,13 @@ fun beans(clock: Clock) = beans { .build() GetPidDataFromAuthServer( - env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry), - clock, - keycloak, - keycloakProperties.userRealm, + issuerCountry = env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry), + issuingJurisdiction = env.getProperty("issuer.pid.issuingJurisdiction"), + clock = clock, + keycloak = keycloak, + userRealm = keycloakProperties.userRealm, + generateDocumentNumber = { DocumentNumber(UUID.randomUUID().toString()) }, + generateAdministrativeNumber = { AdministrativeNumber(UUID.randomUUID().toString()) }, ) } bean(isLazyInit = true) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index bd4f0c6d..36990755 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -35,10 +35,20 @@ private val log = LoggerFactory.getLogger(GetPidDataFromAuthServer::class.java) class GetPidDataFromAuthServer( private val issuerCountry: IsoCountry, + private val issuingJurisdiction: IsoCountrySubdivision?, + private val generateDocumentNumber: suspend () -> DocumentNumber, + private val generateAdministrativeNumber: suspend () -> AdministrativeNumber, private val clock: Clock, private val keycloak: Keycloak, private val userRealm: String, ) : GetPidData { + init { + issuingJurisdiction?.let { + require(it.startsWith(issuerCountry.value)) { + "Issuing Jurisdiction must be within the Issuing Country" + } + } + } private val jsonSupport: Json by lazy { Json { prettyPrint = true } @@ -100,20 +110,20 @@ class GetPidDataFromAuthServer( } } - private fun genPidMetaData(): PidMetaData { + private suspend fun genPidMetaData(): PidMetaData { val issuanceDate = LocalDate.now(clock) return PidMetaData( expiryDate = issuanceDate.plusDays(100), issuanceDate = issuanceDate, issuingCountry = issuerCountry, - issuingAuthority = IssuingAuthority.AdministrativeAuthority("Foo bar administrative authority"), - documentNumber = null, - administrativeNumber = null, - issuingJurisdiction = null, + issuingAuthority = IssuingAuthority.AdministrativeAuthority("${issuerCountry.value} Administrative authority"), + documentNumber = generateDocumentNumber(), + administrativeNumber = generateAdministrativeNumber(), + issuingJurisdiction = issuingJurisdiction, ) } - private fun pid(userInfo: UserInfo): Pair { + private suspend fun pid(userInfo: UserInfo): Pair { val birthDate = requireNotNull(userInfo.birthDate) { "missing required attribute 'birthDate'" }.let { LocalDate.parse(it) } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ada76b50..d92e2ff6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -30,6 +30,7 @@ issuer.pid.sd_jwt_vc.notUseBefore=PT20 issuer.pid.sd_jwt_vc.deferred=true issuer.pid.sd_jwt_vc.notifications.enabled=true issuer.pid.issuingCountry=GR +issuer.pid.issuingJurisdiction=GR-I issuer.mdl.enabled=true issuer.mdl.mso_mdoc.encoder.duration=P5D issuer.mdl.notifications.enabled=true From 57cecfce1aaba74c3719dad998472e934b35856f Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 16:48:04 +0200 Subject: [PATCH 09/15] Register Address House Number in Keycloak and populate it in PID. --- .../realms/pid-issuer-realm-realm.json | 36 ++++++++++++++++++- .../realms/pid-issuer-realm-users-0.json | 5 ++- .../out/pid/GetPidDataFromAuthServer.kt | 8 ++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json index 768c0a66..1b0b5291 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json @@ -1160,6 +1160,23 @@ "jsonType.label": "boolean" } }, + { + "id": "3e551e24-0938-47c2-8125-deafcfd3492d", + "name": "House Number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "address_house_number", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "house_number", + "jsonType.label": "String" + } + }, { "id": "27a29370-a2a6-4e8c-8378-6d4999ac092e", "name": "picture", @@ -1332,6 +1349,23 @@ "jsonType.label": "boolean" } }, + { + "id": "5ed66dc9-c414-40c4-86bc-d6dfccbe5511", + "name": "House Number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "address_house_number", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "house_number", + "jsonType.label": "String" + } + }, { "id": "9cda1d0b-2413-4bc5-aef0-14860e042c6b", "name": "picture", @@ -1644,7 +1678,7 @@ "subComponents": {}, "config": { "kc.user.profile.config": [ - "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputType\":\"select\",\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}}],\"unmanagedAttributePolicy\":\"ENABLED\"}" + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}}]}" ] } } diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json index 975d9022..be656c9e 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json @@ -22,7 +22,10 @@ "true" ], "street": [ - "101 Trauner" + "Trauner" + ], + "address_house_number": [ + "101" ], "locality": [ "Gemeinde Biberbach" diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index 36990755..ef0dea1d 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -65,15 +65,21 @@ class GetPidDataFromAuthServer( private suspend fun userInfo(username: Username): UserInfo? { fun UserRepresentation.address(): OidcAddressClaim? { val street = attributes["street"]?.firstOrNull() + val houseNumber = attributes["address_house_number"]?.firstOrNull() val locality = attributes["locality"]?.firstOrNull() val region = attributes["region"]?.firstOrNull() val postalCode = attributes["postal_code"]?.firstOrNull() val country = attributes["country"]?.firstOrNull() val formatted = attributes["formatted"]?.firstOrNull() - return if (street != null || locality != null || region != null || postalCode != null || country != null || formatted != null) { + return if (street != null || houseNumber != null || + locality != null || region != null || + postalCode != null || country != null || + formatted != null + ) { OidcAddressClaim( streetAddress = street, + houseNumber = houseNumber, locality = locality, region = region, postalCode = postalCode, From 87500c5cf1972100d91ee2a4ba93dcf592c280ce Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 17:16:26 +0200 Subject: [PATCH 10/15] Configure required attributes/claims in keycloak and populate birthplace in PID. --- .../realms/pid-issuer-realm-realm.json | 138 +++++++++++++++++- .../realms/pid-issuer-realm-users-0.json | 9 ++ .../out/pid/GetPidDataFromAuthServer.kt | 17 ++- 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json index 1b0b5291..8c33d809 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json @@ -1177,6 +1177,74 @@ "jsonType.label": "String" } }, + { + "id": "73819e2a-eed3-428c-911c-47b1077af59e", + "name": "birth_place", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_place", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_place", + "jsonType.label": "String" + } + }, + { + "id": "3baad28d-5913-48c3-be05-c7fded80623d", + "name": "birth_country", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_country", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_country", + "jsonType.label": "String" + } + }, + { + "id": "dc0a55e3-1f55-420e-9012-bcfbba0fa251", + "name": "birth_state", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_state", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_state", + "jsonType.label": "String" + } + }, + { + "id": "63f3161c-f4bf-4f63-a706-d97cc65a356e", + "name": "birth_city", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_city", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_city", + "jsonType.label": "String" + } + }, { "id": "27a29370-a2a6-4e8c-8378-6d4999ac092e", "name": "picture", @@ -1366,6 +1434,74 @@ "jsonType.label": "String" } }, + { + "id": "eb7435a1-ffa0-4d60-b14d-f26706b2872a", + "name": "birth_place", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_place", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_place", + "jsonType.label": "String" + } + }, + { + "id": "1a5263a8-f7b9-492e-8765-f5a2ae655136", + "name": "birth_country", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_country", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_country", + "jsonType.label": "String" + } + }, + { + "id": "bba26d11-ad17-46bb-9455-4b9c9980e0c0", + "name": "birth_state", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_state", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_state", + "jsonType.label": "String" + } + }, + { + "id": "a98a80aa-6925-433d-b255-89429c59da93", + "name": "birth_city", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_city", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_city", + "jsonType.label": "String" + } + }, { "id": "9cda1d0b-2413-4bc5-aef0-14860e042c6b", "name": "picture", @@ -1678,7 +1814,7 @@ "subComponents": {}, "config": { "kc.user.profile.config": [ - "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}}]}" + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}]}" ] } } diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json index be656c9e..38cb3269 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json @@ -38,6 +38,15 @@ ], "country": [ "AT" + ], + "birth_country": [ + "AT" + ], + "birth_city": [ + "Gemeinde Biberbach" + ], + "birth_place": [ + "101 Trauner" ] }, "credentials": [ diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index ef0dea1d..1d0d1b8d 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -91,6 +91,21 @@ class GetPidDataFromAuthServer( } } + fun UserRepresentation.birthPlace(): OidcAssurancePlaceOfBirth? { + val birthPlace = attributes["birth_place"]?.firstOrNull() + val birthCountry = attributes["birth_country"]?.firstOrNull() + val birthState = attributes["birth_state"]?.firstOrNull() + val birthCity = attributes["birth_city"]?.firstOrNull() + + return if (birthPlace != null || birthCountry != null || birthState != null || birthCity != null) { + OidcAssurancePlaceOfBirth( + locality = birthPlace ?: birthCity, + region = birthState, + country = birthCountry, + ) + } else null + } + return withContext(Dispatchers.IO) { val users = keycloak.realm(userRealm) .users() @@ -108,7 +123,7 @@ class GetPidDataFromAuthServer( address = user.address(), birthDate = user.attributes["birthdate"]?.firstOrNull(), gender = user.attributes["gender"]?.firstOrNull()?.toUInt(), - placeOfBirth = null, + placeOfBirth = user.birthPlace(), ageOver18 = user.attributes["age_over_18"]?.firstOrNull()?.toBoolean(), picture = null, ) From c76c572345bb13f5ad9bbe133f75c4359e9ab05a Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 17:37:07 +0200 Subject: [PATCH 11/15] Populate PID nationality. --- .../realms/pid-issuer-realm-realm.json | 36 ++++++++++++++++++- .../realms/pid-issuer-realm-users-0.json | 3 ++ .../out/pid/GetPidDataFromAuthServer.kt | 13 +++++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json index 8c33d809..60dd4d4f 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json @@ -1177,6 +1177,23 @@ "jsonType.label": "String" } }, + { + "id": "a0c64f99-246e-4244-9bf3-f93c4e3eee35", + "name": "Nationality", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "nationality", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "nationality", + "jsonType.label": "String" + } + }, { "id": "73819e2a-eed3-428c-911c-47b1077af59e", "name": "birth_place", @@ -1434,6 +1451,23 @@ "jsonType.label": "String" } }, + { + "id": "e3546620-8fd3-4adf-b6ae-7ade6f4c95c3", + "name": "Nationality", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "nationality", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "nationality", + "jsonType.label": "String" + } + }, { "id": "eb7435a1-ffa0-4d60-b14d-f26706b2872a", "name": "birth_place", @@ -1814,7 +1848,7 @@ "subComponents": {}, "config": { "kc.user.profile.config": [ - "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}]}" + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"nationality\",\"displayName\":\"Nationality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}],\"unmanagedAttributePolicy\":\"ENABLED\"}" ] } } diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json index 38cb3269..fb7b07a0 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json @@ -47,6 +47,9 @@ ], "birth_place": [ "101 Trauner" + ], + "nationality": [ + "AT" ] }, "credentials": [ diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index 1d0d1b8d..c1d651c8 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -126,6 +126,7 @@ class GetPidDataFromAuthServer( placeOfBirth = user.birthPlace(), ageOver18 = user.attributes["age_over_18"]?.firstOrNull()?.toBoolean(), picture = null, + nationality = user.attributes["nationality"]?.firstOrNull(), ) } } @@ -158,16 +159,21 @@ class GetPidDataFromAuthServer( ageOver18 = userInfo.ageOver18 ?: false, ageBirthYear = Year.from(birthDate), ageInYears = ageInYears, + familyNameBirth = null, + givenNameBirth = null, + birthPlace = null, + birthCountry = userInfo.placeOfBirth?.country?.let { IsoCountry(it) }, + birthState = userInfo.placeOfBirth?.region?.let { State(it) }, + birthCity = userInfo.placeOfBirth?.locality?.let { City(it) }, residentAddress = userInfo.address?.formatted, residentStreet = userInfo.address?.streetAddress?.let { Street(it) }, residentCountry = userInfo.address?.country?.let { IsoCountry(it) }, residentState = userInfo.address?.region?.let { State(it) }, residentCity = userInfo.address?.locality?.let { City(it) }, residentPostalCode = userInfo.address?.postalCode?.let { PostalCode(it) }, - birthCity = userInfo.placeOfBirth?.locality?.let { City(it) }, - birthCountry = userInfo.placeOfBirth?.country?.let { IsoCountry(it) }, - birthState = userInfo.placeOfBirth?.region?.let { State(it) }, + residentHouseNumber = userInfo.address?.houseNumber, gender = userInfo.gender?.let { IsoGender(it) }, + nationality = userInfo.nationality?.let { IsoCountry(it) }, ) val pidMetaData = genPidMetaData() @@ -188,4 +194,5 @@ private data class UserInfo( @SerialName(OidcAssurancePlaceOfBirth.NAME) val placeOfBirth: OidcAssurancePlaceOfBirth? = null, @SerialName("age_over_18") val ageOver18: Boolean? = null, val picture: String? = null, + val nationality: String? = null, ) From 52c64383566b9bb480ee41a4a4e7b12dfe4a7690 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 22 Nov 2024 17:50:51 +0200 Subject: [PATCH 12/15] Populate missing PID attributes. --- .../realms/pid-issuer-realm-realm.json | 70 ++++++++++++++++++- .../realms/pid-issuer-realm-users-0.json | 6 ++ .../out/pid/GetPidDataFromAuthServer.kt | 8 ++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json index 60dd4d4f..a3a0a582 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json @@ -1160,6 +1160,40 @@ "jsonType.label": "boolean" } }, + { + "id": "ef49deeb-b0ee-47a4-8ffb-79b1e960e9ef", + "name": "Birth Family Name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_family_name", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_family_name", + "jsonType.label": "String" + } + }, + { + "id": "c16dd91d-f816-4f66-ab04-e290ffdec4d7", + "name": "Birth Given Name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_given_name", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_given_name", + "jsonType.label": "String" + } + }, { "id": "3e551e24-0938-47c2-8125-deafcfd3492d", "name": "House Number", @@ -1434,6 +1468,40 @@ "jsonType.label": "boolean" } }, + { + "id": "f7d6ae9c-f149-46c3-8538-17cb40422f94", + "name": "Birth Family Name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_family_name", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_family_name", + "jsonType.label": "String" + } + }, + { + "id": "4585bcb1-d4c1-469e-9ce7-15be02d6f186", + "name": "Birth Given Name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "birth_given_name", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "birth_given_name", + "jsonType.label": "String" + } + }, { "id": "5ed66dc9-c414-40c4-86bc-d6dfccbe5511", "name": "House Number", @@ -1848,7 +1916,7 @@ "subComponents": {}, "config": { "kc.user.profile.config": [ - "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"nationality\",\"displayName\":\"Nationality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}],\"unmanagedAttributePolicy\":\"ENABLED\"}" + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birth_given_name\",\"displayName\":\"First name given at birth\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birth_family_name\",\"displayName\":\"Last name given at birth\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"nationality\",\"displayName\":\"Nationality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}]}" ] } } diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json index fb7b07a0..7aeae85a 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json @@ -50,6 +50,12 @@ ], "nationality": [ "AT" + ], + "birth_family_name": [ + "Neal" + ], + "birth_given_name": [ + "Tyler" ] }, "credentials": [ diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index c1d651c8..00a8ae2b 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -118,6 +118,8 @@ class GetPidDataFromAuthServer( UserInfo( familyName = user.lastName, givenName = user.firstName, + birthFamilyName = user.attributes["birth_family_name"]?.firstOrNull(), + birthGivenName = user.attributes["birth_given_name"]?.firstOrNull(), sub = user.username, email = user.email, address = user.address(), @@ -159,8 +161,8 @@ class GetPidDataFromAuthServer( ageOver18 = userInfo.ageOver18 ?: false, ageBirthYear = Year.from(birthDate), ageInYears = ageInYears, - familyNameBirth = null, - givenNameBirth = null, + familyNameBirth = userInfo.birthFamilyName?.let { FamilyName(it) }, + givenNameBirth = userInfo.birthGivenName?.let { GivenName(it) }, birthPlace = null, birthCountry = userInfo.placeOfBirth?.country?.let { IsoCountry(it) }, birthState = userInfo.placeOfBirth?.region?.let { State(it) }, @@ -186,6 +188,8 @@ class GetPidDataFromAuthServer( private data class UserInfo( @Required @SerialName("family_name") val familyName: String, @Required @SerialName("given_name") val givenName: String, + @SerialName("birth_family_name") val birthFamilyName: String? = null, + @SerialName("birth_given_name") val birthGivenName: String? = null, @Required val sub: String, val email: String? = null, @SerialName(OidcAddressClaim.NAME) val address: OidcAddressClaim? = null, From be9affd091abcf1518dc9e443071ed2b81c574ae Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Mon, 25 Nov 2024 09:42:51 +0200 Subject: [PATCH 13/15] Remove generateDocumentNumber() and generateAdministrativeNumber() from GetPidDataFromAuthServer. --- .../europa/ec/eudi/pidissuer/PidIssuerApplication.kt | 2 -- .../adapter/out/pid/GetPidDataFromAuthServer.kt | 11 +++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index ec6eed51..6f1e710d 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -278,8 +278,6 @@ fun beans(clock: Clock) = beans { clock = clock, keycloak = keycloak, userRealm = keycloakProperties.userRealm, - generateDocumentNumber = { DocumentNumber(UUID.randomUUID().toString()) }, - generateAdministrativeNumber = { AdministrativeNumber(UUID.randomUUID().toString()) }, ) } bean(isLazyInit = true) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index 00a8ae2b..58c70831 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -29,6 +29,7 @@ import org.keycloak.admin.client.Keycloak import org.keycloak.representations.idm.UserRepresentation import org.slf4j.LoggerFactory import java.time.* +import java.util.* import kotlin.math.ceil private val log = LoggerFactory.getLogger(GetPidDataFromAuthServer::class.java) @@ -36,8 +37,6 @@ private val log = LoggerFactory.getLogger(GetPidDataFromAuthServer::class.java) class GetPidDataFromAuthServer( private val issuerCountry: IsoCountry, private val issuingJurisdiction: IsoCountrySubdivision?, - private val generateDocumentNumber: suspend () -> DocumentNumber, - private val generateAdministrativeNumber: suspend () -> AdministrativeNumber, private val clock: Clock, private val keycloak: Keycloak, private val userRealm: String, @@ -134,20 +133,20 @@ class GetPidDataFromAuthServer( } } - private suspend fun genPidMetaData(): PidMetaData { + private fun genPidMetaData(): PidMetaData { val issuanceDate = LocalDate.now(clock) return PidMetaData( expiryDate = issuanceDate.plusDays(100), issuanceDate = issuanceDate, issuingCountry = issuerCountry, issuingAuthority = IssuingAuthority.AdministrativeAuthority("${issuerCountry.value} Administrative authority"), - documentNumber = generateDocumentNumber(), - administrativeNumber = generateAdministrativeNumber(), + documentNumber = DocumentNumber(UUID.randomUUID().toString()), + administrativeNumber = AdministrativeNumber(UUID.randomUUID().toString()), issuingJurisdiction = issuingJurisdiction, ) } - private suspend fun pid(userInfo: UserInfo): Pair { + private fun pid(userInfo: UserInfo): Pair { val birthDate = requireNotNull(userInfo.birthDate) { "missing required attribute 'birthDate'" }.let { LocalDate.parse(it) } From 3c6c255a3aaeaa69ba592e74ddf9580639f23d2a Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Mon, 25 Nov 2024 10:04:24 +0200 Subject: [PATCH 14/15] Model OIDC Gender as a simple string. --- .../realms/pid-issuer-realm-realm.json | 36 ++++++++++++++++++- .../realms/pid-issuer-realm-users-0.json | 3 ++ .../adapter/out/pid/EncodePidInSdJwtVc.kt | 15 +------- .../out/pid/GetPidDataFromAuthServer.kt | 3 ++ .../adapter/out/pid/IssueSdJwtVcPid.kt | 3 +- .../ec/eudi/pidissuer/adapter/out/pid/Pid.kt | 19 +--------- 6 files changed, 44 insertions(+), 35 deletions(-) diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json index a3a0a582..4c443ced 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-realm.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json @@ -1228,6 +1228,23 @@ "jsonType.label": "String" } }, + { + "id": "5343c74e-e247-4b85-bf79-3ff843aee565", + "name": "gender_as_string", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "gender_as_string", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "gender_as_string", + "jsonType.label": "String" + } + }, { "id": "73819e2a-eed3-428c-911c-47b1077af59e", "name": "birth_place", @@ -1536,6 +1553,23 @@ "jsonType.label": "String" } }, + { + "id": "0bba757d-b188-4434-8f10-ed760d266dc4", + "name": "gender_as_string", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "false", + "userinfo.token.claim": "true", + "user.attribute": "gender_as_string", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "false", + "claim.name": "gender_as_string", + "jsonType.label": "String" + } + }, { "id": "eb7435a1-ffa0-4d60-b14d-f26706b2872a", "name": "birth_place", @@ -1916,7 +1950,7 @@ "subComponents": {}, "config": { "kc.user.profile.config": [ - "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birth_given_name\",\"displayName\":\"First name given at birth\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birth_family_name\",\"displayName\":\"Last name given at birth\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"nationality\",\"displayName\":\"Nationality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}]}" + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"credentials\",\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birth_given_name\",\"displayName\":\"First name given at birth\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birth_family_name\",\"displayName\":\"Last name given at birth\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender\",\"displayName\":\"Gender\",\"validations\":{\"options\":{\"options\":[\"0\",\"1\",\"2\",\"9\"]}},\"annotations\":{\"inputOptionLabels\":{\"0\":\"Not known\",\"1\":\"Male\",\"2\":\"Female\",\"9\":\"Not applicable\"},\"inputHelperTextAfter\":\"Select one of the above\",\"inputType\":\"select-radiobuttons\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"gender_as_string\",\"displayName\":\"Gender\",\"validations\":{},\"annotations\":{\"inputTypePlaceholder\":\"gender\",\"inputHelperTextAfter\":\"Or provide your own\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"birthdate\",\"displayName\":\"Birthdate\",\"validations\":{\"iso-date\":{}},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"age_over_18\",\"displayName\":\"Age over 18\",\"validations\":{\"options\":{\"options\":[\"true\",\"false\"]}},\"annotations\":{\"inputOptionLabels\":{\"true\":\"Yes\",\"false\":\"No\"},\"inputType\":\"select\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"picture\",\"displayName\":\"Picture\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false},{\"name\":\"street\",\"displayName\":\"Street\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"address_house_number\",\"displayName\":\"House Number\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"locality\",\"displayName\":\"Locality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"region\",\"displayName\":\"Region\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"postal_code\",\"displayName\":\"Postal Code\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"formatted\",\"displayName\":\"Formatted\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"address\",\"multivalued\":false},{\"name\":\"birth_place\",\"displayName\":\"Place\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_city\",\"displayName\":\"City\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_state\",\"displayName\":\"State\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"birth_country\",\"displayName\":\"Country\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"birth_place\",\"multivalued\":false},{\"name\":\"nationality\",\"displayName\":\"Nationality\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"profile\",\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"},{\"name\":\"address\",\"displayHeader\":\"Address\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"credentials\",\"displayHeader\":\"Credentials\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"profile\",\"displayHeader\":\"Profile\",\"displayDescription\":\"\",\"annotations\":{}},{\"name\":\"birth_place\",\"displayHeader\":\"Birth Place\",\"displayDescription\":\"\",\"annotations\":{}}]}" ] } } diff --git a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json index 7aeae85a..32c75dbe 100644 --- a/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json +++ b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json @@ -15,6 +15,9 @@ "gender": [ "1" ], + "gender_as_string": [ + "male" + ], "birthdate": [ "1955-04-12" ], diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index 16f62876..36b93020 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -39,7 +39,6 @@ import org.slf4j.LoggerFactory import java.time.Clock import java.time.Instant import java.time.ZonedDateTime -import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcGender as OidcGenderAttribute private val log = LoggerFactory.getLogger(EncodePidInSdJwtVc::class.java) @@ -157,7 +156,7 @@ private fun selectivelyDisclosed( address.houseNumber?.let { sd("house_number", it) } } } - pid.gender?.let { sd(OidcGenderAttribute.name, it.toOidGender().value) } + pid.genderAsString?.let { sd(OidcGender.name, it) } pid.nationality?.let { val nationalities = buildJsonArray { add(it.value) } sd(OidcAssuranceNationalities.name, nationalities) @@ -218,15 +217,3 @@ private object Printer { return str } } - -/** - * Converts an [IsoGender] to an [OidcGender]. - */ -private fun IsoGender.toOidGender(): OidcGender = - when (value) { - 0u -> OidcGender("not known") - 1u -> OidcGender.Male - 2u -> OidcGender.Female - 9u -> OidcGender("not applicable") - else -> OidcGender(value.toString()) - } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index 58c70831..77626f81 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -124,6 +124,7 @@ class GetPidDataFromAuthServer( address = user.address(), birthDate = user.attributes["birthdate"]?.firstOrNull(), gender = user.attributes["gender"]?.firstOrNull()?.toUInt(), + genderAsString = user.attributes["gender_as_string"]?.firstOrNull(), placeOfBirth = user.birthPlace(), ageOver18 = user.attributes["age_over_18"]?.firstOrNull()?.toBoolean(), picture = null, @@ -174,6 +175,7 @@ class GetPidDataFromAuthServer( residentPostalCode = userInfo.address?.postalCode?.let { PostalCode(it) }, residentHouseNumber = userInfo.address?.houseNumber, gender = userInfo.gender?.let { IsoGender(it) }, + genderAsString = userInfo.genderAsString, nationality = userInfo.nationality?.let { IsoCountry(it) }, ) @@ -194,6 +196,7 @@ private data class UserInfo( @SerialName(OidcAddressClaim.NAME) val address: OidcAddressClaim? = null, @SerialName("birthdate") val birthDate: String? = null, @SerialName("gender") val gender: UInt? = null, + @SerialName("gender_as_string") val genderAsString: String? = null, @SerialName(OidcAssurancePlaceOfBirth.NAME) val placeOfBirth: OidcAssurancePlaceOfBirth? = null, @SerialName("age_over_18") val ageOver18: Boolean? = null, val picture: String? = null, diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index f964860d..6bc9ca9e 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -40,7 +40,6 @@ import java.time.Clock import java.time.Instant import java.time.ZonedDateTime import java.util.* -import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcGender as OidcGenderAttribute val PidSdJwtVcScope: Scope = Scope("eu.europa.ec.eudi.pid_vc_sd_jwt") @@ -76,7 +75,7 @@ internal object Attributes { OidcAssuranceBirthGivenName, OidcAssurancePlaceOfBirth.attribute, OidcAddressClaim.attribute, - OidcGenderAttribute, + OidcGender, OidcAssuranceNationalities, IssuingAuthorityAttribute, DocumentNumberAttribute, diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt index b1a8fabd..95c9e236 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/Pid.kt @@ -52,24 +52,6 @@ typealias Address = String @JvmInline value class IsoGender(val value: UInt) -/** - * Gender, using a value as defined in OpenID Connect Core 1.0. - */ -@JvmInline -value class OidcGender private constructor(val value: String) { - companion object { - val Male: OidcGender = OidcGender("male") - val Female: OidcGender = OidcGender("female") - - operator fun invoke(value: String): OidcGender = - when (value) { - "male" -> Male - "female" -> Female - else -> OidcGender(value) - } - } -} - typealias Nationality = IsoCountry /** @@ -122,6 +104,7 @@ data class Pid( val residentPostalCode: PostalCode? = null, val residentHouseNumber: String? = null, val gender: IsoGender? = null, + val genderAsString: String? = null, val nationality: Nationality? = null, ) { init { From 3fc96b093ef12af75700715c57c642159392ce11 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Mon, 25 Nov 2024 10:13:16 +0200 Subject: [PATCH 15/15] Also include `kid` and `x5c` header claims of the issuer's signing key. --- .../ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index 36b93020..54feda89 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -66,7 +66,10 @@ class EncodePidInSdJwtVc( val sdJwtFactory = SdJwtFactory(hashAlgorithm = hashAlgorithm, fallbackMinimumDigests = null) val signer = ECDSASigner(issuerSigningKey.key) SdJwtIssuer.nimbus(sdJwtFactory, signer, issuerSigningKey.signingAlgorithm) { + // TODO: This will change to dc+sd-jwt in a future release type(JOSEObjectType("vc+sd-jwt")) + keyID(issuerSigningKey.key.keyID) + x509CertChain(issuerSigningKey.key.x509CertChain) } }