From 3d9d419a40b17de1f94d46cbc2f5b345a93bff00 Mon Sep 17 00:00:00 2001
From: Thomas <>
Date: Wed, 8 Jun 2022 12:32:16 +0200
Subject: feat(eidas): perform mapping between IDA and eIDAS attributes based
 on external configuration
---
 .../msproxyservice/dto/attributes/Type.java        |   7 +
 .../protocol/ProxyServiceAuthenticationAction.java | 297 ++++++++++-----------
 .../service/ProxyEidasAttributeRegistry.java       |  34 ++-
 3 files changed, 176 insertions(+), 162 deletions(-)
(limited to 'modules/eidas_proxy-sevice/src/main')
diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java
index 86ca49fa..f66bb799 100644
--- a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java
+++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java
@@ -15,6 +15,7 @@ import lombok.Data;
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @JsonPropertyOrder({
     "mds",
+    "autoIncludeWithMandates",
     "mandator"
 })
 @Data
@@ -27,6 +28,12 @@ public class Type {
   @JsonProperty("mds")
   private Boolean mds;
 
+  /**
+   * true if that attribute has to be included into eIDAS response in case of mandates.  
+   */
+  @JsonProperty("autoIncludeWithMandates")
+  private Boolean autoIncludeWithMandates;
+  
   /**
    * Classifie that attribute to specific mandate modes.
    */
diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java
index 92165412..bf1c5e5f 100644
--- a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java
+++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java
@@ -1,6 +1,7 @@
 package at.asitplus.eidas.specific.modules.msproxyservice.protocol;
 
 import java.io.IOException;
+import java.util.Optional;
 import java.util.UUID;
 
 import javax.annotation.PostConstruct;
@@ -15,12 +16,11 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.core.io.ResourceLoader;
 import org.springframework.web.util.UriComponentsBuilder;
 
-import at.asitplus.eidas.specific.core.MsEidasNodeConstants;
 import at.asitplus.eidas.specific.core.gui.StaticGuiBuilderConfiguration;
 import at.asitplus.eidas.specific.modules.core.eidas.EidasConstants;
-import at.asitplus.eidas.specific.modules.core.eidas.service.EidasAttributeRegistry;
 import at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceConstants;
 import at.asitplus.eidas.specific.modules.msproxyservice.exception.EidasProxyServiceException;
+import at.asitplus.eidas.specific.modules.msproxyservice.service.ProxyEidasAttributeRegistry;
 import at.asitplus.eidas.specific.modules.msproxyservice.utils.EidasProxyServiceUtils;
 import at.gv.egiz.eaaf.core.api.IRequest;
 import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions;
@@ -69,35 +69,35 @@ public class ProxyServiceAuthenticationAction implements IAction {
   @Autowired
   ISpringMvcGuiFormBuilder guiBuilder;
   @Autowired
-  EidasAttributeRegistry attrRegistry;
+  ProxyEidasAttributeRegistry attrRegistry;
 
   @Override
   public SloInformationInterface processRequest(IRequest pendingReq, HttpServletRequest httpReq,
       HttpServletResponse httpResp, IAuthData authData) throws EaafException {
     if (pendingReq instanceof ProxyServicePendingRequest) {
-      try {        
-        ILightRequest eidasReq = ((ProxyServicePendingRequest) pendingReq).getEidasRequest();
-        
-        //build eIDAS response
-        Builder lightRespBuilder = LightResponse.builder();
+      try {
+        final ILightRequest eidasReq = ((ProxyServicePendingRequest) pendingReq).getEidasRequest();
+
+        // build eIDAS response
+        final Builder lightRespBuilder = LightResponse.builder();
         lightRespBuilder.id(UUID.randomUUID().toString());
         lightRespBuilder.inResponseToId(eidasReq.getId());
         lightRespBuilder.relayState(eidasReq.getRelayState());
-        
+
         lightRespBuilder.status(ResponseStatus.builder()
             .statusCode(EidasConstants.SUCCESS_URI)
             .build());
-        
-        //TODO: check if we can use transient subjectNameIds
+
+        // TODO: check if we can use transient subjectNameIds
         lightRespBuilder.subject(UUID.randomUUID().toString());
         lightRespBuilder.subjectNameIdFormat(NameIDType.TRANSIENT);
-        
-        //TODO:
+
+        // TODO:
         lightRespBuilder.issuer(basicConfig.getBasicConfiguration(
             MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_NODE_ENTITYID));
-        lightRespBuilder.levelOfAssurance(authData.getEidasQaaLevel());       
+        lightRespBuilder.levelOfAssurance(authData.getEidasQaaLevel());
         lightRespBuilder.attributes(buildAttributesFromAuthData(authData, eidasReq));
-        
+
         // set SLO response object of EAAF framework
         final SloInformationImpl sloInformation = new SloInformationImpl();
         sloInformation.setProtocolType(pendingReq.requestedModule());
@@ -121,7 +121,7 @@ public class ProxyServiceAuthenticationAction implements IAction {
 
     }
   }
-  
+
   @Override
   public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) {
     return true;
@@ -133,28 +133,29 @@ public class ProxyServiceAuthenticationAction implements IAction {
     return PROXYSERVICE_AUTH_ACTION_NAME;
 
   }
-  
 
   /**
    * Forward eIDAS Light response to eIDAS node.
-   *     
-   * @param pendingReq Current pending request.
-   * @param httpReq Current HTTP request
-   * @param httpResp  Current HTTP response
+   *
+   * @param pendingReq    Current pending request.
+   * @param httpReq       Current HTTP request
+   * @param httpResp      Current HTTP response
    * @param lightResponse eIDAS LightResponse
    * @throws EaafConfigurationException In case of a configuration error
-   * @throws IOException In case of a general error
-   * @throws GuiBuildException In case of a GUI rendering error, if http POST binding is used
-   * @throws ServletException In case of a general error
+   * @throws IOException                In case of a general error
+   * @throws GuiBuildException          In case of a GUI rendering error, if http
+   *                                    POST binding is used
+   * @throws ServletException           In case of a general error
    */
   public void forwardToEidasProxy(IRequest pendingReq, HttpServletRequest httpReq,
-      HttpServletResponse httpResp, LightResponse lightResponse) throws EaafConfigurationException, IOException,
+      HttpServletResponse httpResp, LightResponse lightResponse) throws EaafConfigurationException,
+      IOException,
       GuiBuildException, ServletException {
 
     // put request into shared cache
     final BinaryLightToken token = putResponseInCommunicationCache(lightResponse);
     final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token);
-        
+
     // select forward URL regarding the selected environment
     final String forwardUrl = basicConfig.getBasicConfiguration(
         MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_NODE_FORWARD_URL);
@@ -196,148 +197,80 @@ public class ProxyServiceAuthenticationAction implements IAction {
 
     }
   }
-  
-  @PostConstruct 
+
+  @PostConstruct
   private void checkConfiguration() {
-    //TODO: validate configuration on start-up
-    
+    // TODO: validate configuration on start-up
+
   }
-  
-  
-  private ImmutableAttributeMap buildAttributesFromAuthData(IAuthData authData, 
+
+  private ImmutableAttributeMap buildAttributesFromAuthData(IAuthData authData,
       ILightRequest eidasReq) {
-    IEidAuthData eidAuthData = (IEidAuthData) authData;
+    final IEidAuthData eidAuthData = (IEidAuthData) authData;
+    final ImmutableAttributeMap.Builder attributeMap = ImmutableAttributeMap.builder();
+
+    // inject all requested attributres
+    injectRequestedAttributes(attributeMap, eidasReq, eidAuthData);
+
     if (eidAuthData.isUseMandate()) {
       log.debug("Building eIDAS Proxy-Service response with mandate ... ");
-      final ImmutableAttributeMap.Builder attributeMap = ImmutableAttributeMap.builder();
-      injectRepesentativeInformation(attributeMap, eidAuthData);
-      injectMandatorInformation(attributeMap, eidAuthData);
-      
-      // work-around that injects nat. person subject to bypass validation on eIDAS Node
+      injectMdsRepesentativeInformation(attributeMap, eidAuthData, eidasReq.getRequestedAttributes());
+
+      // work-around that injects nat. person subject to bypass validation on eIDAS
+      // Node
       injectJurPersonWorkaroundIfRequired(attributeMap, eidasReq, authData);
-      
-      return attributeMap.build();
-            
-    } else {
-      log.debug("Building eIDAS Proxy-Service response without mandates ... ");
-      return buildAttributesWithoutMandate(eidAuthData);
-      
-    }   
-  }
-    
-  private void injectMandatorInformation(
-      ImmutableAttributeMap.Builder attributeMap, IEidAuthData eidAuthData) {    
-    String natMandatorId = eidAuthData.getGenericData(
-        MsEidasNodeConstants.ATTR_EIDAS_NAT_MANDATOR_PERSONAL_IDENTIFIER, String.class);
-    
-    if (StringUtils.isNotEmpty(natMandatorId)) {
-      log.debug("Injecting natural mandator informations ... ");
-      final AttributeDefinition> attrDefPersonalId = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-          EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER).first();
-      final AttributeDefinition> attrDefFamilyName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-          EidasConstants.eIDAS_ATTR_CURRENTFAMILYNAME).first();
-      final AttributeDefinition> attrDefGivenName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-          EidasConstants.eIDAS_ATTR_CURRENTGIVENNAME).first();
-      final AttributeDefinition> attrDefDateOfBirth = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-          EidasConstants.eIDAS_ATTR_DATEOFBIRTH).first();
-      
-      attributeMap.put(attrDefPersonalId, natMandatorId);
-      attributeMap.put(attrDefFamilyName, eidAuthData.getGenericData(
-          PvpAttributeDefinitions.MANDATE_NAT_PER_FAMILY_NAME_NAME, String.class));
-      attributeMap.put(attrDefGivenName, eidAuthData.getGenericData(
-          PvpAttributeDefinitions.MANDATE_NAT_PER_GIVEN_NAME_NAME, String.class));
-      attributeMap.put(attrDefDateOfBirth, eidAuthData.getGenericData(
-          PvpAttributeDefinitions.MANDATE_NAT_PER_BIRTHDATE_NAME, String.class));
-      
-    } else {
-      log.debug("Injecting legal mandator informations ... ");
-      final AttributeDefinition> commonName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-          EidasConstants.eIDAS_ATTR_LEGALNAME).first();
-      final AttributeDefinition> legalPersonId = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-          EidasConstants.eIDAS_ATTR_LEGALPERSONIDENTIFIER).first();
-      
-      attributeMap.put(commonName, eidAuthData.getGenericData(
-          PvpAttributeDefinitions.MANDATE_LEG_PER_FULL_NAME_NAME, String.class));
-      attributeMap.put(legalPersonId, eidAuthData.getGenericData(
-          MsEidasNodeConstants.ATTR_EIDAS_JUR_MANDATOR_PERSONAL_IDENTIFIER, String.class));
-            
-    }            
-  }
 
-  private void injectRepesentativeInformation(
-      ImmutableAttributeMap.Builder attributeMap, IEidAuthData eidAuthData) {
-    final AttributeDefinition> attrDefPersonalId = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-        EidasConstants.eIDAS_ATTR_REPRESENTATIVE_PERSONALIDENTIFIER).first();
-    final AttributeDefinition> attrDefFamilyName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-        EidasConstants.eIDAS_ATTR_REPRESENTATIVE_CURRENTFAMILYNAME).first();
-    final AttributeDefinition> attrDefGivenName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-        EidasConstants.eIDAS_ATTR_REPRESENTATIVE_CURRENTGIVENNAME).first();
-    final AttributeDefinition> attrDefDateOfBirth = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(
-        EidasConstants.eIDAS_ATTR_REPRESENTATIVE_DATEOFBIRTH).first();
-   
-    attributeMap.put(attrDefPersonalId, 
-            eidAuthData.getGenericData(MsEidasNodeConstants.ATTR_EIDAS_PERSONAL_IDENTIFIER, String.class));
-    attributeMap.put(attrDefFamilyName, eidAuthData.getFamilyName());
-    attributeMap.put(attrDefGivenName, eidAuthData.getGivenName());
-    
-    //TODO: throw an error in case of SZR Date with month or day = "00"
-    attributeMap.put(attrDefDateOfBirth, eidAuthData.getDateOfBirth());
-    
+    }
+
+    return attributeMap.build();
+
   }
 
-  /**
-   * Work-around to inject representative information as nat. person subject to bypass eIDAS Node validation. 
-   * 
-   * 
Injection will only be done if this work-around is enabled by configuration, - * the mandator is a legal person, and both legal and natural person subject's is requested.
- * - * @param attributeMap Attribute set for eIDAS response - * @param eidasReq Incoming eIDAS request - * @param authData Authentication data - */ - private void injectJurPersonWorkaroundIfRequired( - ImmutableAttributeMap.Builder attributeMap, ILightRequest eidasReq, IAuthData authData) { - if (isLegalPersonWorkaroundActive() && isLegalPersonMandateAvailable(authData) - && EidasProxyServiceUtils.isNaturalPersonRequested(eidasReq) - && EidasProxyServiceUtils.isLegalPersonRequested(eidasReq)) { - log.debug("Injecting representative information as nat. person subject to bypass eIDAS Node validation"); - attributeMap.putAll(buildAttributesWithoutMandate(authData)); - - } + private void injectRequestedAttributes(ImmutableAttributeMap.Builder attributeMap, ILightRequest eidasReq, + IEidAuthData eidAuthData) { + eidasReq.getRequestedAttributes().getAttributeMap().keySet().stream() + .forEach(el -> injectEidasAttribute(attributeMap, eidAuthData, + el.getNameUri().toString(), eidAuthData.isUseMandate())); + } - - private ImmutableAttributeMap buildAttributesWithoutMandate(IAuthData eidAuthData) { - //TODO: throw an error in case of SZR Date with month or day = "00" - return buildAttributesWithoutMandate( - eidAuthData.getGenericData(MsEidasNodeConstants.ATTR_EIDAS_PERSONAL_IDENTIFIER, String.class), - eidAuthData.getFamilyName(), - eidAuthData.getGivenName(), - eidAuthData.getDateOfBirth()); - + + private void injectMdsRepesentativeInformation( + ImmutableAttributeMap.Builder attributeMap, IEidAuthData eidAuthData, + ImmutableAttributeMap requestedAttributes) { + attrRegistry.getRepresentativeAttributesToAddByDefault() + .filter(el -> requestedAttributes.getAttributeValuesByNameUri(el) == null) + .forEach(el -> injectEidasAttribute(attributeMap, eidAuthData, el, true)); + } - private ImmutableAttributeMap buildAttributesWithoutMandate(String personalIdentifier, String familyName, - String givenName, String dateOfBirth) { - final AttributeDefinition> attrDefPersonalId = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( - EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER).first(); - final AttributeDefinition> attrDefFamilyName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( - EidasConstants.eIDAS_ATTR_CURRENTFAMILYNAME).first(); - final AttributeDefinition> attrDefGivenName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( - EidasConstants.eIDAS_ATTR_CURRENTGIVENNAME).first(); - final AttributeDefinition> attrDefDateOfBirth = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( - EidasConstants.eIDAS_ATTR_DATEOFBIRTH).first(); - - final ImmutableAttributeMap.Builder attributeMap = - ImmutableAttributeMap.builder() - .put(attrDefPersonalId, personalIdentifier) - .put(attrDefFamilyName, familyName) - .put(attrDefGivenName, givenName) - .put(attrDefDateOfBirth, dateOfBirth); - - return attributeMap.build(); - + private void injectEidasAttribute(ImmutableAttributeMap.Builder attributeMap, IEidAuthData eidAuthData, + String eidasAttrName, boolean mandatesUsed) { + final Optional+ * Injection will only be done if this work-around is enabled by + * configuration, the mandator is a legal person, and both legal and natural + * person subject's is requested. + *
+ * + * @param attributeMap Attribute set for eIDAS response + * @param eidasReq Incoming eIDAS request + * @param authData Authentication data + */ + private void injectJurPersonWorkaroundIfRequired( + ImmutableAttributeMap.Builder attributeMap, ILightRequest eidasReq, IAuthData authData) { + if (isLegalPersonWorkaroundActive() && isLegalPersonMandateAvailable(authData) + && EidasProxyServiceUtils.isNaturalPersonRequested(eidasReq) + && EidasProxyServiceUtils.isLegalPersonRequested(eidasReq)) { + log.debug( + "Injecting representative information as nat. person subject to bypass eIDAS Node validation"); + + final AttributeDefinition> attrDefPersonalId = + attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( + EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER).first(); + final AttributeDefinition> attrDefFamilyName = + attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( + EidasConstants.eIDAS_ATTR_CURRENTFAMILYNAME).first(); + final AttributeDefinition> attrDefGivenName = + attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( + EidasConstants.eIDAS_ATTR_CURRENTGIVENNAME).first(); + final AttributeDefinition> attrDefDateOfBirth = + attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( + EidasConstants.eIDAS_ATTR_DATEOFBIRTH).first(); + + attributeMap.put(attrDefPersonalId, authData.getGenericData(PvpAttributeDefinitions.BPK_NAME, + String.class)); + attributeMap.put(attrDefFamilyName, authData.getFamilyName()); + attributeMap.put(attrDefGivenName, authData.getGivenName()); + attributeMap.put(attrDefDateOfBirth, authData.getDateOfBirth()); + + } + } + private boolean isLegalPersonWorkaroundActive() { return basicConfig.getBasicConfigurationBoolean( - MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_WORKAROUND_MANDATES_LEGAL_PERSON, + MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_WORKAROUND_MANDATES_LEGAL_PERSON, false); - + } - + private boolean isLegalPersonMandateAvailable(IAuthData authData) { return StringUtils.isNoneEmpty(authData.getGenericData( - MsEidasNodeConstants.ATTR_EIDAS_JUR_MANDATOR_PERSONAL_IDENTIFIER, String.class)); - + PvpAttributeDefinitions.MANDATE_LEG_PER_SOURCE_PIN_NAME, String.class)); + } } diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java index b9e0c488..a6a50100 100644 --- a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -76,6 +77,19 @@ public class ProxyEidasAttributeRegistry { } + /** + * Get all eIDAS attributes that are added by default in case of mandates. + * + * @return {@link Stream} of eIDAS attributes + */ + @NonNull + public Streamtrue if mandates are supported, otherwise false
+   * @return Name of the related IDA attribute if available 
+   */
+  public Optional