diff options
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> | 
