From d2dec4601c41131c3ca509a8f7907b91af0ba2a6 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 19 Dec 2022 15:50:38 +0100 Subject: feat(eidas-connector): support not-notified LoA - not-notified LoA is currently used by Ukraine --- .../eidas/v2/handler/AbstractEidProcessor.java | 8 +-- .../auth/eidas/v2/handler/UaEidProcessor.java | 83 +++++++++++++++++----- .../eidas/v2/tasks/GenerateAuthnRequestTask.java | 12 ++-- .../eidas/v2/tasks/ReceiveAuthnResponseTask.java | 4 ++ .../eidas/v2/validator/EidasResponseValidator.java | 51 ++++++++----- 5 files changed, 113 insertions(+), 45 deletions(-) (limited to 'modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules') diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java index 5c2c43ea..fa26e48f 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java @@ -26,7 +26,6 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; import static at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils.processCountryCode; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils.processDateOfBirthToString; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -38,7 +37,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; @@ -98,7 +96,7 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor { .pseudonym(processPseudonym(eidasAttrMap.get(EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER))) .familyName(processFamilyName(eidasAttrMap.get(EidasConstants.eIDAS_ATTR_CURRENTFAMILYNAME))) .givenName(processGivenName(eidasAttrMap.get(EidasConstants.eIDAS_ATTR_CURRENTGIVENNAME))) - .dateOfBirth(processDateOfBirthToString(eidasAttrMap.get(EidasConstants.eIDAS_ATTR_DATEOFBIRTH))) + .dateOfBirth(processDateOfBirth(eidasAttrMap.get(EidasConstants.eIDAS_ATTR_DATEOFBIRTH))) // additional attributes .placeOfBirth(processPlaceOfBirth(eidasAttrMap.get(EidasConstants.eIDAS_ATTR_PLACEOFBIRTH))) @@ -174,9 +172,9 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor { * @throws EidasAttributeException if NO attribute is available * @throws EidPostProcessingException if post-processing fails */ - protected DateTime processDateOfBirth(Object dateOfBirthObj) throws EidPostProcessingException, + protected String processDateOfBirth(Object dateOfBirthObj) throws EidPostProcessingException, EidasAttributeException { - return EidasResponseUtils.processDateOfBirth(dateOfBirthObj); + return EidasResponseUtils.processDateOfBirthToString(dateOfBirthObj); } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/UaEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/UaEidProcessor.java index 6be0a26b..1656ec40 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/UaEidProcessor.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/UaEidProcessor.java @@ -1,12 +1,21 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import at.asitplus.eidas.specific.core.config.IEidasSpConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; import eu.eidas.auth.commons.light.impl.LightRequest.Builder; @@ -15,8 +24,8 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** - * Ulraine specific eIDAS AuthnRequest generation. - * + * Ulraine specific eIDAS AuthnRequest generation. + * * @author tlenz * */ @@ -24,45 +33,81 @@ import lombok.extern.slf4j.Slf4j; public class UaEidProcessor extends AbstractEidProcessor { private static final String CONFIG_PROP_UA_SPECIFIC_LOA = "auth.eIDAS.node_v2.loa.ua.requested"; - + private static final String CONFIG_PROP_UA_WORKAROUND_DATEOFBIRTH = + "auth.eIDAS.node_v2.workaround.ua.dateofbirth"; + private static final String STATIC_DATE_OF_BIRTH = "2000-05-29"; + private static final String canHandleCC = "UA"; - @Autowired IConfiguration config; - + @Autowired + IConfiguration config; + @Getter @Setter private int priority = 1; - + @Override public String getName() { return "UA-PostProcessor"; - + } @Override public boolean canHandle(String countryCode) { return countryCode != null && countryCode.equalsIgnoreCase(canHandleCC); - + } - + @Override protected Map getCountrySpecificRequestedAttributes() { return new HashMap<>(); - + } - - protected void buildLevelOfAssurance(ISpConfiguration spConfig, Builder authnRequestBuilder) { - - // allow override of LoA, because UA maybe only support not-notified LoA levels - String uaSpecificLoA = config.getBasicConfiguration(CONFIG_PROP_UA_SPECIFIC_LOA); + + @Override + protected void buildLevelOfAssurance(ISpConfiguration spConfig, Builder authnRequestBuilder) { + // allow override of LoA, because UA maybe only support not-notified LoA levels + final String uaSpecificLoA = config.getBasicConfiguration(CONFIG_PROP_UA_SPECIFIC_LOA); if (StringUtils.isNotEmpty(uaSpecificLoA)) { authnRequestBuilder.levelsOfAssuranceValues(Arrays.asList(uaSpecificLoA)); - log.info("Set UA specific LoA level to: {}", uaSpecificLoA); - + + // set non-notified LoA as allowed LoA + final List allowedLoa = new ArrayList<>(); + allowedLoa.addAll(spConfig.getRequiredLoA()); + allowedLoa.add(uaSpecificLoA); + ((IEidasSpConfiguration) spConfig).setRequiredLoA(allowedLoa); + ((IEidasSpConfiguration) spConfig).setLoAMachtingMode(EaafConstants.EIDAS_LOA_MATCHING_EXACT); + log.info("Set UA specific LoA level to: {} with matching-mode: {}", + StringUtils.join(allowedLoa, "|"), EaafConstants.EIDAS_LOA_MATCHING_EXACT); + } else { super.buildLevelOfAssurance(spConfig, authnRequestBuilder); - + } } - + + @Override + protected String processDateOfBirth(Object dateOfBirthObj) throws EidPostProcessingException, + EidasAttributeException { + final String dateOfBirth = super.processDateOfBirth(dateOfBirthObj); + + try { + final Date dateElement = new SimpleDateFormat("yyyy-MM-dd").parse(dateOfBirth); + if (basicConfig.getBasicConfigurationBoolean(CONFIG_PROP_UA_WORKAROUND_DATEOFBIRTH, false) + && dateElement.after(new Date())) { + log.warn("DateOfBirth: {} is in the future. Use static DateOfBirth as backup", dateOfBirth); + return STATIC_DATE_OF_BIRTH; + + } else { + return dateOfBirth; + + } + + } catch (final ParseException e) { + log.warn("Can not parse dateOfBirth", e); + return dateOfBirth; + + } + } + } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java index 93e1033d..cf6ecb8d 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java @@ -119,6 +119,9 @@ public class GenerateAuthnRequestTask extends AbstractAuthServletTask { final LightRequest lightAuthnReq = buildEidasAuthnRequest(citizenCountryCode, issuer); + // store pending request after possible updates + requestStoreage.storePendingRequest(pendingReq); + final BinaryLightToken token = putRequestInCommunicationCache(lightAuthnReq); final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); workaroundRelayState(lightAuthnReq); @@ -136,6 +139,10 @@ public class GenerateAuthnRequestTask extends AbstractAuthServletTask { } revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.EIDAS_NODE_CONNECTED, lightAuthnReq.getId()); + log.info("Allowed LoA: {}", + StringUtils.join(pendingReq.getServiceProviderConfiguration().getRequiredLoA(),", ")); + + } catch (final EidasSAuthenticationException e) { throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", e); } catch (final Exception e) { @@ -238,10 +245,7 @@ public class GenerateAuthnRequestTask extends AbstractAuthServletTask { log.info("Inject alternative MS-Connector end-point: {}", alternativReturnEndpoint); pendingReq.setRawDataToTransaction( MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, alternativReturnEndpoint); - - // store pending request after update - requestStoreage.storePendingRequest(pendingReq); - + } } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java index a16da17f..cc497318 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java @@ -111,6 +111,10 @@ public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { storeInSession(eidasResponse); } + log.info("Allowed LoA Response: {}", + StringUtils.join(pendingReq.getServiceProviderConfiguration().getRequiredLoA(),", ")); + + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); } catch (final EaafException e) { revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java index d1962654..b3c5dac1 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java @@ -26,8 +26,6 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.validator; import java.util.List; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableSet; @@ -40,7 +38,10 @@ import at.gv.egiz.eaaf.core.impl.data.Triple; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.light.ILightResponse; -import eu.eidas.auth.commons.protocol.eidas.LevelOfAssurance; +import eu.eidas.auth.commons.light.LevelOfAssuranceType; +import eu.eidas.auth.commons.light.impl.LevelOfAssurance; +import eu.eidas.auth.commons.protocol.eidas.NotifiedLevelOfAssurance; +import lombok.extern.slf4j.Slf4j; /** * eIDAS Response validator implementation. @@ -48,8 +49,8 @@ import eu.eidas.auth.commons.protocol.eidas.LevelOfAssurance; * @author tlenz * */ +@Slf4j public class EidasResponseValidator { - private static final Logger log = LoggerFactory.getLogger(EidasResponseValidator.class); /** * Validate an eIDAS Response according to internal state. @@ -67,24 +68,39 @@ public class EidasResponseValidator { /*-----------------------------------------------------| * validate received LoA against minimum required LoA | *_____________________________________________________| - */ - final LevelOfAssurance respLoA = LevelOfAssurance.fromString(eidasResponse.getLevelOfAssurance()); + */ + final LevelOfAssurance respLoA = LevelOfAssurance.build(eidasResponse.getLevelOfAssurance()); final List allowedLoAs = pendingReq.getServiceProviderConfiguration().getRequiredLoA(); boolean loaValid = false; for (final String allowedLoaString : allowedLoAs) { - final LevelOfAssurance allowedLoa = LevelOfAssurance.fromString(allowedLoaString); - if (respLoA.numericValue() >= allowedLoa.numericValue()) { - log.debug("Response contains valid LoA. Resume process ... "); - loaValid = true; - break; - + final LevelOfAssurance allowedLoa = LevelOfAssurance.build(allowedLoaString); + if (LevelOfAssuranceType.NOTIFIED.stringValue().equals(respLoA.getType())) { + NotifiedLevelOfAssurance notifiedLoa = NotifiedLevelOfAssurance.fromString(respLoA.getValue()); + NotifiedLevelOfAssurance notifiedAllowedLoa = NotifiedLevelOfAssurance.fromString(allowedLoa.getValue()); + if (notifiedLoa.numericValue() >= notifiedAllowedLoa.numericValue()) { + log.debug("Response contains valid LoA. Resume process ... "); + loaValid = true; + break; + + } else { + log.trace("Allowed LoA: " + allowedLoaString + " DOES NOT match response LoA: " + eidasResponse + .getLevelOfAssurance()); + } + } else { - log.trace("Allowed LoA: " + allowedLoaString + " DOES NOT match response LoA: " + eidasResponse - .getLevelOfAssurance()); - } - + if (respLoA.equals(allowedLoa)) { + log.info("Find not-notified LoA: {}. Use it as it is ... ", respLoA.getValue()); + loaValid = true; + break; + + } else { + log.trace("Allowed LoA: " + allowedLoaString + " DOES NOT match response LoA: " + eidasResponse + .getLevelOfAssurance()); + + } + } } - + if (!loaValid) { log.error("eIDAS Response LevelOfAssurance is lower than the required! " + "(Resp-LoA:{} Req-LoA:{} )", respLoA.getValue(), allowedLoAs.toArray()); @@ -92,6 +108,7 @@ public class EidasResponseValidator { } + /*-----------------------------------------------------| * validate 'PersonalIdentifier' attribute | *_____________________________________________________| -- cgit v1.2.3