diff options
author | Thomas <> | 2022-12-19 15:50:38 +0100 |
---|---|---|
committer | Thomas <> | 2022-12-19 15:50:38 +0100 |
commit | d2dec4601c41131c3ca509a8f7907b91af0ba2a6 (patch) | |
tree | 999634c3edaf5d45774593b4cdece1dada857dab | |
parent | c2fa7fa970f717b8b4e27098b3d2b9341c59fae1 (diff) | |
download | National_eIDAS_Gateway-d2dec4601c41131c3ca509a8f7907b91af0ba2a6.tar.gz National_eIDAS_Gateway-d2dec4601c41131c3ca509a8f7907b91af0ba2a6.tar.bz2 National_eIDAS_Gateway-d2dec4601c41131c3ca509a8f7907b91af0ba2a6.zip |
feat(eidas-connector): support not-notified LoA
- not-notified LoA is currently used by Ukraine
14 files changed, 447 insertions, 75 deletions
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<String, Boolean> 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<String> 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<String> 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 | *_____________________________________________________| diff --git a/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasAttributePostProcessingTest.java b/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasAttributePostProcessingTest.java index b8cb0642..7ae432a7 100644 --- a/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasAttributePostProcessingTest.java +++ b/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasAttributePostProcessingTest.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +41,7 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import at.asitplus.eidas.specific.core.test.config.dummy.MsConnectorDummyConfigMap; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.CcSpecificEidProcessingService; @@ -126,6 +128,8 @@ public class EidasAttributePostProcessingTest { private static final String P8_PLACEOFBIRTH = RandomStringUtils.randomAlphabetic(10); private static final String P8_BIRTHNAME = RandomStringUtils.randomAlphabetic(10); + @Autowired + private MsConnectorDummyConfigMap basicConfig; /** * jUnit class initializer. @@ -136,9 +140,19 @@ public class EidasAttributePostProcessingTest { public static void classInitializer() throws IOException { final String current = new java.io.File(".").toURI().toString(); System.setProperty("eidas.ms.configuration", current + "../../basicConfig/default_config.properties"); - + } + /** + * Test initializer. + */ + @Before + public void initialize() { + basicConfig.putConfigValue("eidas.ms.auth.eIDAS.node_v2.workaround.ua.dateofbirth", "false"); + + } + + @Test @SneakyThrows public void deWithHexLowerCase() { @@ -271,6 +285,73 @@ public class EidasAttributePostProcessingTest { } + @Test + public void uaTestCaseWrongDateOfBirthWorkAround() throws Exception { + basicConfig.putConfigValue("eidas.ms.auth.eIDAS.node_v2.workaround.ua.dateofbirth", "true"); + + final SimpleEidasData result = postProcessor.postProcess( + generateInputData( + "UA/AT/asdfsafsdaasfsadf", + "UATestUser", + "mein Vorname", + "2170-05-29", + null, + null)); + + validate(result, + "asdfsafsdaasfsadf", + "UA", + "UATestUser", + "mein Vorname", + "2000-05-29", + null, + null); + + } + + @Test + public void uaTestCaseWrongDateOfBirth() throws Exception { + final SimpleEidasData result = postProcessor.postProcess( + generateInputData( + "UA/AT/asdfsafsdaasfsadf", + "UATestUser", + "mein Vorname", + "2170-05-29", + null, + null)); + + validate(result, + "asdfsafsdaasfsadf", + "UA", + "UATestUser", + "mein Vorname", + "2170-05-29", + null, + null); + + } + + @Test + public void uaTestCaseValidDateOfBirth() throws Exception { + final SimpleEidasData result = postProcessor.postProcess( + generateInputData( + "UA/AT/asdfsafsdaasfsadf", + "UATestUser", + "mein Vorname", + "1970-05-29", + null, + null)); + + validate(result, + "asdfsafsdaasfsadf", + "UA", + "UATestUser", + "mein Vorname", + "1970-05-29", + null, + null); + + } @Test public void eeTestCase() throws Exception { diff --git a/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasRequestPreProcessingSecondTest.java b/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasRequestPreProcessingSecondTest.java index 7cfd2d5c..6f385789 100644 --- a/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasRequestPreProcessingSecondTest.java +++ b/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasRequestPreProcessingSecondTest.java @@ -25,6 +25,7 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.validation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.Map; @@ -40,13 +41,13 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import at.asitplus.eidas.specific.core.config.ServiceProviderConfiguration; import at.asitplus.eidas.specific.core.test.config.dummy.MsConnectorDummyConfigMap; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPreProcessingException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.CcSpecificEidProcessingService; import at.gv.egiz.eaaf.core.api.data.EaafConfigConstants; import at.gv.egiz.eaaf.core.api.data.EaafConstants; -import at.gv.egiz.eaaf.core.impl.idp.module.test.DummySpConfiguration; import at.gv.egiz.eaaf.core.impl.idp.module.test.TestRequestImpl; import eu.eidas.auth.commons.light.impl.LightRequest; import eu.eidas.auth.commons.light.impl.LightRequest.Builder; @@ -65,7 +66,7 @@ public class EidasRequestPreProcessingSecondTest { private CcSpecificEidProcessingService preProcessor; private TestRequestImpl pendingReq; - private DummySpConfiguration oaParam; + private ServiceProviderConfiguration oaParam; private Builder authnRequestBuilder; @@ -74,13 +75,16 @@ public class EidasRequestPreProcessingSecondTest { * */ @Before + @SneakyThrows public void setUp() { final Map<String, String> spConfig = new HashMap<>(); spConfig.put(EaafConfigConstants.SERVICE_UNIQUEIDENTIFIER, "testSp"); - spConfig.put("target", "urn:publicid:gv.at:cdid+XX"); - oaParam = new DummySpConfiguration(spConfig, basicConfig); - + + oaParam = new ServiceProviderConfiguration(spConfig, basicConfig); + oaParam.setBpkTargetIdentifier("urn:publicid:gv.at:cdid+XX"); + + pendingReq = new TestRequestImpl(); pendingReq.setSpConfig(oaParam); pendingReq.setPendingReqId(at.gv.egiz.eaaf.core.impl.utils.Random.nextProcessReferenceValue()); @@ -105,9 +109,8 @@ public class EidasRequestPreProcessingSecondTest { public void privateSpAllowed() { basicConfig.putConfigValue( "eidas.ms.auth.eIDAS.node_v2.proxyservices.privatesp.notsupported", "XX,XY"); - basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.publicSectorTargets"); - - oaParam.getFullConfiguration().put("target", "urn:publicid:gv.at:wbpk+XFN+123456a"); + basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.publicSectorTargets"); + oaParam.setBpkTargetIdentifier("urn:publicid:gv.at:wbpk+XFN+123456a"); final String testCountry = "DE"; authnRequestBuilder.citizenCountryCode(testCountry); @@ -123,10 +126,9 @@ public class EidasRequestPreProcessingSecondTest { public void privateSpNotAllowed() { basicConfig.putConfigValue( "eidas.ms.auth.eIDAS.node_v2.proxyservices.privatesp.notsupported", "XX,XY"); - basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.publicSectorTargets"); - - oaParam.getFullConfiguration().put("target", "urn:publicid:gv.at:wbpk+XFN+123456a"); - + basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.publicSectorTargets"); + oaParam.setBpkTargetIdentifier("urn:publicid:gv.at:wbpk+XFN+123456a"); + final String testCountry = "XY"; authnRequestBuilder.citizenCountryCode(testCountry); @@ -225,6 +227,14 @@ public class EidasRequestPreProcessingSecondTest { Assert.assertEquals("wrong LoA", "http://eidas.europa.eu/NotNotified/LoA/high", lightReq.getLevelsOfAssurance().get(0).getValue()); + assertEquals("SP allowed LoA", 2, oaParam.getRequiredLoA().size()); + assertTrue("missing not-notified LoA", oaParam.getRequiredLoA().stream() + .filter(el -> el.equals("http://eidas.europa.eu/NotNotified/LoA/high")).findFirst().isPresent()); + assertEquals("wrong LoA matching-mode", "exact", oaParam.getLoAMatchingMode()); + + + + } } diff --git a/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasResponseValidatorTest.java b/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasResponseValidatorTest.java index 91a50d28..d7831dbd 100644 --- a/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasResponseValidatorTest.java +++ b/modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/validation/EidasResponseValidatorTest.java @@ -36,6 +36,7 @@ import eu.eidas.auth.commons.attribute.ImmutableAttributeMap.Builder; import eu.eidas.auth.commons.attribute.impl.StringAttributeValue; import eu.eidas.auth.commons.light.ILightResponse; import eu.eidas.auth.commons.protocol.impl.AuthenticationResponse; +import lombok.SneakyThrows; import lombok.val; @RunWith(SpringJUnit4ClassRunner.class) @@ -100,6 +101,49 @@ public class EidasResponseValidatorTest { } @Test + public void loaFromResponseNotAllowed() throws URISyntaxException { + //set-up + ILightResponse eidasResponse = buildDummyAuthResponse( + "LU/AT/" + RandomStringUtils.randomNumeric(10), + "http://eidas.europa.eu/NotNotified/LoA/high", + false); + String spCountry = "AT"; + String citizenCountryCode = "XX"; + + //execute test + try { + EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, attrRegistry); + Assert.fail("Wrong eIDAS response not detected"); + + } catch (EidasValidationException e) { + Assert.assertEquals("ErrorId", "eidas.06", e.getErrorId()); + Assert.assertEquals("wrong parameter size", 1, e.getParams().length); + Assert.assertEquals("wrong errorMsg", "http://eidas.europa.eu/NotNotified/LoA/high", + e.getParams()[0]); + + } + } + + @Test + @SneakyThrows + public void loaFromResponseNotNotified() throws URISyntaxException { + + //set-up + ILightResponse eidasResponse = buildDummyAuthResponse( + "LU/AT/" + RandomStringUtils.randomNumeric(10), + "http://eidas.europa.eu/NotNotified/LoA/high", + false); + String spCountry = "AT"; + String citizenCountryCode = "LU"; + + oaParam.setLoa(Arrays.asList(EaafConstants.EIDAS_LOA_HIGH, "http://eidas.europa.eu/NotNotified/LoA/high")); + + //execute test + EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, attrRegistry); + + } + + @Test public void noEidasSpCountry() throws URISyntaxException { //set-up ILightResponse eidasResponse = buildDummyAuthResponse( diff --git a/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/IEidasSpConfiguration.java b/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/IEidasSpConfiguration.java new file mode 100644 index 00000000..bb61a5cc --- /dev/null +++ b/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/IEidasSpConfiguration.java @@ -0,0 +1,32 @@ +package at.asitplus.eidas.specific.core.config; + +import java.util.List; + +/** + * eIDAS specific Service-Provider configuration. + * + * @author tlenz + * + */ +public interface IEidasSpConfiguration { + + /** + * Set the minimum level of eIDAS authentication for this SP <br> + * <b>Default:</b> http://eidas.europa.eu/LoA/high <br> + * <b>Info:</b> In case of MINIMUM matching-mode, only one entry is allowed + * + * @param minimumLoA eIDAS LoA URIs + */ + + void setRequiredLoA(List<String> minimumLoA); + + /** + * Set the mode of operation for LoA matching for this SP. <b>Default: + * minimum</b> <br> + * <b>Info:</b> Currently only 'minimum' and 'exact' are supported + * + * @param mode LoA matching mode according to SAML2 core specification + */ + void setLoAMachtingMode(String mode); + +} diff --git a/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/ServiceProviderConfiguration.java b/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/ServiceProviderConfiguration.java index d2177323..b6858527 100644 --- a/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/ServiceProviderConfiguration.java +++ b/modules/core_common_lib/src/main/java/at/asitplus/eidas/specific/core/config/ServiceProviderConfiguration.java @@ -42,7 +42,7 @@ import at.gv.egiz.eaaf.core.impl.idp.conf.SpConfigurationImpl; import lombok.Getter; import lombok.Setter; -public class ServiceProviderConfiguration extends SpConfigurationImpl { +public class ServiceProviderConfiguration extends SpConfigurationImpl implements IEidasSpConfiguration { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(ServiceProviderConfiguration.class); diff --git a/ms_specific_connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java b/ms_specific_connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java index 23702264..0452353a 100644 --- a/ms_specific_connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java +++ b/ms_specific_connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java @@ -58,7 +58,7 @@ import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttributes; import at.gv.egiz.eaaf.modules.pvp2.api.validation.IAuthnRequestPostProcessor; import at.gv.egiz.eaaf.modules.pvp2.exception.NameIdFormatNotSupportedException; -import eu.eidas.auth.commons.protocol.eidas.LevelOfAssurance; +import eu.eidas.auth.commons.protocol.eidas.NotifiedLevelOfAssurance; public class AuthnRequestValidator implements IAuthnRequestPostProcessor { @@ -266,13 +266,13 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { final List<String> reqLoA = extractLoA(authnReq); log.trace("SP requests LoA with: {}", String.join(", ", reqLoA)); - LevelOfAssurance minimumLoAFromConfig = LevelOfAssurance.fromString(basicConfig.getBasicConfiguration( - MsEidasNodeConstants.PROP_EIDAS_REQUEST_LOA_MINIMUM_LEVEL, - EaafConstants.EIDAS_LOA_HIGH)); + NotifiedLevelOfAssurance minimumLoAFromConfig = NotifiedLevelOfAssurance.fromString( + basicConfig.getBasicConfiguration(MsEidasNodeConstants.PROP_EIDAS_REQUEST_LOA_MINIMUM_LEVEL, + EaafConstants.EIDAS_LOA_HIGH)); if (minimumLoAFromConfig == null) { log.warn("Can not load minimum LoA from configuration. Use LoA: {} as default", EaafConstants.EIDAS_LOA_HIGH); - minimumLoAFromConfig = LevelOfAssurance.HIGH; + minimumLoAFromConfig = NotifiedLevelOfAssurance.HIGH; } @@ -281,7 +281,7 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { final List<String> allowedLoA = new ArrayList<>(); for (final String loa : reqLoA) { try { - final LevelOfAssurance intLoa = LevelOfAssurance.fromString(loa); + final NotifiedLevelOfAssurance intLoa = NotifiedLevelOfAssurance.fromString(loa); String selectedLoA = EaafConstants.EIDAS_LOA_HIGH; if (intLoa != null && intLoa.numericValue() <= minimumLoAFromConfig.numericValue()) { @@ -340,11 +340,13 @@ public class AuthnRequestValidator implements IAuthnRequestPostProcessor { } else { result.add(authContext.getAuthnContextClassRefs().get(0).getAuthnContextClassRef()); + } } else if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.EXACT)) { for (final AuthnContextClassRef el : authContext.getAuthnContextClassRefs()) { result.add(el.getAuthnContextClassRef()); + } } else { diff --git a/ms_specific_connector/src/test/java/at/asitplus/eidas/specific/connector/test/FullStartUpAndProcessTest.java b/ms_specific_connector/src/test/java/at/asitplus/eidas/specific/connector/test/FullStartUpAndProcessTest.java index e5fea3b3..46079ac5 100644 --- a/ms_specific_connector/src/test/java/at/asitplus/eidas/specific/connector/test/FullStartUpAndProcessTest.java +++ b/ms_specific_connector/src/test/java/at/asitplus/eidas/specific/connector/test/FullStartUpAndProcessTest.java @@ -372,7 +372,7 @@ public class FullStartUpAndProcessTest { Assert.assertFalse("eidas req. token", eidasNodeReqToken.isEmpty()); //check eIDAS node request and build respose - String eidasRespToken = validateEidasNodeRequestAndBuildResponse(eidasNodeReqToken); + String eidasRespToken = validateEidasNodeRequestAndBuildResponse(eidasNodeReqToken, EaafConstants.EIDAS_LOA_HIGH); Assert.assertFalse("eidas resp. token", eidasRespToken.isEmpty()); @@ -450,6 +450,142 @@ public class FullStartUpAndProcessTest { } + @Test + public void fullSuccessProcessNonNotifiedLoa() throws EaafException, Exception { + //start authentication process by sending a SAML2 Authn-Request + MockHttpServletRequest saml2Req = new MockHttpServletRequest("POST", "https://localhost/ms_connector"); + injectSaml2AuthnReq(saml2Req); + MockHttpServletResponse selectCountryResp = new MockHttpServletResponse(); + RequestContextHolder.resetRequestAttributes(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(saml2Req, selectCountryResp)); + + // send SAML2 AuthnRequest + sProfile.pvpIdpPostRequest(saml2Req, selectCountryResp); + + //check country-selection response + Assert.assertEquals("no country-selection page", 200, selectCountryResp.getStatus()); + Assert.assertEquals("cc-selection page", "text/html;charset=UTF-8", selectCountryResp.getContentType()); + String selectionPage = selectCountryResp.getContentAsString(); + Assert.assertNotNull("selectionPage is null", selectionPage); + Assert.assertFalse("selectionPage is empty", selectionPage.isEmpty()); + + String pendingReqId = extractRequestToken(selectionPage, + "<input type=\"hidden\" name=\"pendingid\" value=\""); + Assert.assertFalse("PendingReqId", pendingReqId.isEmpty()); + + + //set UA as citizen country-code + cc = "UA"; + pseudonym = RandomStringUtils.randomNumeric(64); + personalId = cc + "/AT/" + pseudonym; + familyName = RandomStringUtils.randomAlphabetic(10); + givenName = RandomStringUtils.randomAlphabetic(10); + dateOfBirth = "2015-10-12"; + + + // set-up country-selection request + MockHttpServletRequest selectCountryReq = new MockHttpServletRequest("POST", "https://localhost/ms_connector"); + selectCountryReq.setParameter("pendingid", pendingReqId); + selectCountryReq.setParameter("selectedCountry", cc); + + MockHttpServletResponse forwardEidasNodeResp = new MockHttpServletResponse(); + RequestContextHolder.resetRequestAttributes(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(selectCountryReq, forwardEidasNodeResp)); + + // send country-selection request + signal.performGenericAuthenticationProcess(selectCountryReq, forwardEidasNodeResp); + + //check forward to eIDAS node response + Assert.assertEquals("forward to eIDAS Node", 200, forwardEidasNodeResp.getStatus()); + Assert.assertEquals("forward to eIDAS Node page", "text/html;charset=UTF-8", forwardEidasNodeResp.getContentType()); + String forwardPage = forwardEidasNodeResp.getContentAsString(); + Assert.assertNotNull("forward to eIDAS Node is null", forwardPage); + Assert.assertFalse("forward to eIDAS Node is empty", forwardPage.isEmpty()); + + String eidasNodeReqToken = extractRequestToken(forwardPage, + "<input type=\"hidden\" name=\"token\" value=\""); + Assert.assertFalse("eidas req. token", eidasNodeReqToken.isEmpty()); + + //check eIDAS node request and build respose + String eidasRespToken = validateEidasNodeRequestAndBuildResponse(eidasNodeReqToken, + EaafConstants.EIDAS_LOA_NOT_NOTIFIED_PREFIX + "high"); + Assert.assertFalse("eidas resp. token", eidasRespToken.isEmpty()); + + + // set-up eIDAS-node response + MockHttpServletRequest eidasNodeRespReq = new MockHttpServletRequest("POST", "https://localhost/ms_connector"); + eidasNodeRespReq.setParameter("token", eidasRespToken); + + MockHttpServletResponse finalizeResp = new MockHttpServletResponse(); + RequestContextHolder.resetRequestAttributes(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(eidasNodeRespReq, finalizeResp)); + + // inject ZMR, ERnP and SZR responses for matching + injectZmrResponse(); + injectSzrResponse(); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("{}") // empty response because we simulate result from ZMR + .setHeader("Content-Type", "application/json;charset=utf-8")); + + //excute eIDAS node response + eidasSignal.restoreEidasAuthProcess(eidasNodeRespReq, finalizeResp); + + //validate state + Assert.assertEquals("forward to finalization", 302, finalizeResp.getStatus()); + Assert.assertNotNull("missing redirect header", finalizeResp.getHeader("Location")); + Assert.assertTrue("wrong redirect header", finalizeResp.getHeader("Location").startsWith(FINAL_REDIRECT)); + String finalPendingReqId = finalizeResp.getHeader("Location").substring(FINAL_REDIRECT.length()); + Assert.assertFalse("final pendingRequestId", finalPendingReqId.isEmpty()); + + + //set-up finalization request + MockHttpServletRequest finalizationReq = new MockHttpServletRequest("POST", "https://localhost/ms_connector"); + finalizationReq.setParameter("pendingid", finalPendingReqId); + + MockHttpServletResponse saml2Resp = new MockHttpServletResponse(); + RequestContextHolder.resetRequestAttributes(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(finalizationReq, saml2Resp)); + + // exexcute finalization step + finalize.finalizeAuthProtocol(finalizationReq, saml2Resp); + + //validate state + Assert.assertEquals("forward to finalization", 200, saml2Resp.getStatus()); + Assert.assertEquals("forward to eIDAS Node page", "text/html;charset=UTF-8", saml2Resp.getContentType()); + String saml2RespPage = saml2Resp.getContentAsString(); + Assert.assertNotNull("selectionPage is null", saml2RespPage); + Assert.assertFalse("selectionPage is empty", saml2RespPage.isEmpty()); + + //validate SAML2 response + String saml2RespB64 = extractRequestToken(saml2RespPage, + "<input type=\"hidden\" name=\"SAMLResponse\" value=\""); + Assert.assertNotNull("SAML2 response", saml2RespB64); + + StatusResponseType saml2 = (StatusResponseType) XMLObjectSupport.unmarshallFromInputStream( + XMLObjectProviderRegistrySupport.getParserPool(), + new ByteArrayInputStream(Base64Utils.decodeFromString(saml2RespB64))); + Assert.assertEquals("SAML2 status", EidasConstants.SUCCESS_URI, saml2.getStatus().getStatusCode().getValue()); + + final AssertionAttributeExtractor extractor = new AssertionAttributeExtractor(saml2); + + Assert.assertEquals("wrong resp attr. size", 7, extractor.getAllIncludeAttributeNames().size()); + Assert.assertEquals("Wrong attr: LoA ", "http://eidas.europa.eu/NotNotified/LoA/high", + extractor.getSingleAttributeValue("urn:oid:1.2.40.0.10.2.1.1.261.108")); + Assert.assertEquals("Wrong attr: PVP_VERSION ", "2.2", + extractor.getSingleAttributeValue("urn:oid:1.2.40.0.10.2.1.1.261.10")); + Assert.assertEquals("Wrong attr: EID_ISSUER_NATION ", cc, + extractor.getSingleAttributeValue("urn:oid:1.2.40.0.10.2.1.1.261.32")); + Assert.assertEquals("Wrong attr: eidasBind", eidasBind, + extractor.getSingleAttributeValue("urn:eidgvat:attributes.eidbind")); + Assert.assertNotNull("Wrong attr: authBlock", + extractor.getSingleAttributeValue("urn:eidgvat:attributes.authblock.signed")); + Assert.assertNotNull("Wrong attr: piiTras.Id ", + extractor.getSingleAttributeValue("urn:eidgvat:attributes.piiTransactionId")); + Assert.assertEquals("Wrong attr:EID_STATUS_LEVEL ", "http://eid.gv.at/eID/status/identity", + extractor.getSingleAttributeValue(PvpAttributeDefinitions.EID_IDENTITY_STATUS_LEVEL_NAME)); + + } + private void injectSzrResponse() throws Exception { when(szrMock.getStammzahlEncrypted(any(), any())).thenReturn(vsz); @@ -509,7 +645,7 @@ public class FullStartUpAndProcessTest { } - private String validateEidasNodeRequestAndBuildResponse(String eidasNodeReqToken) + private String validateEidasNodeRequestAndBuildResponse(String eidasNodeReqToken, String loa) throws SpecificCommunicationException, URISyntaxException { final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = (SpecificCommunicationService) wac.getBean( @@ -521,17 +657,16 @@ public class FullStartUpAndProcessTest { Assert.assertNotNull("eIDAS Node req", req); Assert.assertEquals("Wrong CC", cc, req.getCitizenCountryCode()); - Assert.assertEquals("Wrong CC", EaafConstants.EIDAS_LOA_HIGH, req.getLevelOfAssurance()); - - + Assert.assertEquals("Wrong CC", loa, req.getLevelsOfAssurance().get(0).getValue()); + //set response from eIDAS node BinaryLightToken respoToken = springManagedSpecificConnectorCommunicationService.putResponse( - buildDummyAuthResponse(EidasConstants.SUCCESS_URI, req.getId())); + buildDummyAuthResponse(EidasConstants.SUCCESS_URI, req.getId(), loa)); return Base64Utils.encodeToString(respoToken.getTokenBytes()); } - private AuthenticationResponse buildDummyAuthResponse(String statusCode, String reqId) throws URISyntaxException { + private AuthenticationResponse buildDummyAuthResponse(String statusCode, String reqId, String loa) throws URISyntaxException { final AttributeDefinition<?> attributeDef = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER).first(); final AttributeDefinition<?> attributeDef2 = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( @@ -554,7 +689,7 @@ public class FullStartUpAndProcessTest { .statusCode(statusCode) .inResponseTo(reqId) .subjectNameIdFormat("afaf") - .levelOfAssurance(EaafConstants.EIDAS_LOA_HIGH) + .levelOfAssurance(loa) .attributes(attributeMap) .build(); diff --git a/ms_specific_connector/src/test/resources/config/junit_config_1_springboot.properties b/ms_specific_connector/src/test/resources/config/junit_config_1_springboot.properties index dc2b1587..9c0de7b0 100644 --- a/ms_specific_connector/src/test/resources/config/junit_config_1_springboot.properties +++ b/ms_specific_connector/src/test/resources/config/junit_config_1_springboot.properties @@ -106,6 +106,7 @@ eidas.ms.sp.1.policy.hasBaseIdTransferRestriction=true eidas.ms.auth.eIDAS.node_v2.proxy.entityId=ownSpecificProxy eidas.ms.auth.eIDAS.node_v2.proxy.forward.endpoint=http://eidas.proxy/endpoint +eidas.ms.auth.eIDAS.node_v2.loa.ua.requested=http://eidas.europa.eu/NotNotified/LoA/high ## PVP2 S-Profile communication with ID Austria System # EntityId and optional metadata of ID Austria System @@ -24,8 +24,7 @@ <!-- ===================================================================== --> <egiz-spring-api>0.3</egiz-spring-api> <egiz-eventlog-slf4jBackend>0.4</egiz-eventlog-slf4jBackend> - <eaaf-core.version>1.3.10</eaaf-core.version> - + <eaaf-core.version>1.3.11</eaaf-core.version> <spring-boot-starter-web.version>2.7.5</spring-boot-starter-web.version> <spring-boot-admin-starter-client.version>2.7.7</spring-boot-admin-starter-client.version> <org.springframework.version>5.3.24</org.springframework.version> |