From 2945c875bda2c8236d0b3fd630358fcaca85f4a8 Mon Sep 17 00:00:00 2001
From: Thomas Lenz <thomas.lenz@egiz.gv.at>
Date: Mon, 2 Jul 2018 18:12:22 +0200
Subject: add requested PVP S-Profile attribute handling

---
 .../specific/connector/MSeIDASNodeConstants.java   |  14 +-
 .../connector/provider/PVPMetadataProvider.java    |  46 +++--
 .../verification/AuthnRequestValidator.java        | 207 +++++++++++++++++++++
 .../MetadataSignatureVerificationFilter.java       |   3 +-
 4 files changed, 254 insertions(+), 16 deletions(-)
 create mode 100644 connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/AuthnRequestValidator.java

diff --git a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/MSeIDASNodeConstants.java b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/MSeIDASNodeConstants.java
index 97defade..94c77297 100644
--- a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/MSeIDASNodeConstants.java
+++ b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/MSeIDASNodeConstants.java
@@ -33,7 +33,7 @@ public class MSeIDASNodeConstants {
 	
 	//default values
 	public static final String POLICY_DEFAULT_ALLOWED_TARGETS = 
-			EAAFConstants.URN_PREFIX_CDID.replaceAll(".", "\\.").replaceAll("+", "\\+") + ".*";
+			EAAFConstants.URN_PREFIX_CDID.replaceAll("\\.", "\\\\.").replaceAll("\\+", "\\\\+") + ".*";
 	public static final int METADATA_SOCKED_TIMEOUT = 20 * 1000;  	//20 seconds metadata socked timeout
 	public static final int DEFAULT_PVP_METADATA_VALIDITY = 24;		//24 hours
 	
@@ -42,12 +42,20 @@ public class MSeIDASNodeConstants {
 	public static final String ENDPOINT_PVP_POST = "/pvp/post";
 	public static final String ENDPOINT_PVP_REDIRECT = "/pvp/redirect";
 	
-
+	public static final String ENDPOINT_COUNTRYSELECTION = "/myHomeCountry";
+	
 	//paths and templates
 	public static final String CLASSPATH_TEMPLATE_DIR = "/templates/";
 	
 	public static final String TEMPLATE_HTML_ERROR = "error.html";
 	public static final String TEMPLATE_HTML_PVP_POSTBINDING = "pvp2_post_binding.html";
-	
+	public static final String TEMPLATE_HTML_COUNTRYSELECTION = "countrySelection.html";
+	
+	//execution context and generic data
+	public static final String REQ_PARAM_SELECTED_COUNTRY = "selectedCountry";
+	public static final String DATA_REQUESTERID = "req_requesterId";
+	public static final String DATA_PROVIDERNAME = "req_providerName";
+	public static final String DATA_REQUESTED_LOA_LIST = "req_requestedLoA";
+	public static final String DATA_REQUESTED_LOA_COMPERISON = "req_requestedLoAComperision";
 	
 }
diff --git a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/provider/PVPMetadataProvider.java b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/provider/PVPMetadataProvider.java
index 0edc5fcd..57f6e373 100644
--- a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/provider/PVPMetadataProvider.java
+++ b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/provider/PVPMetadataProvider.java
@@ -8,6 +8,7 @@ import java.util.List;
 
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.params.HttpClientParams;
+import org.apache.commons.lang3.StringUtils;
 import org.opensaml.saml2.metadata.provider.MetadataProvider;
 import org.opensaml.xml.parse.BasicParserPool;
 import org.slf4j.Logger;
@@ -18,11 +19,14 @@ import org.springframework.stereotype.Service;
 import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
 import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
 import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException;
+import at.gv.egiz.eaaf.core.impl.utils.FileUtils;
+import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2MetadataException;
 import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.AbstractChainingMetadataProvider;
 import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.MetadataFilterChain;
 import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.PVPEntityCategoryFilter;
 import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.SchemaValidationFilter;
 import at.gv.egiz.eidas.specific.connector.MSeIDASNodeConstants;
+import at.gv.egiz.eidas.specific.connector.verification.MetadataSignatureVerificationFilter;
 
 @Service("PVPMetadataProvider")
 public class PVPMetadataProvider extends AbstractChainingMetadataProvider{
@@ -47,14 +51,31 @@ public class PVPMetadataProvider extends AbstractChainingMetadataProvider{
 			throws EAAFConfigurationException, IOException, CertificateException {
 		ISPConfiguration spConfig = basicConfig.getServiceProviderConfiguration(entityId);
 		if (spConfig != null) {
-			String metadataURL = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_URL);
-			String trustStoreUrl = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_TRUSTSTORE);
-			return createNewSimpleMetadataProvider(metadataURL, 								 
-					buildMetadataFilterChain(spConfig, metadataURL, trustStoreUrl), 
-					spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_UNIQUEIDENTIFIER),
-					getTimer(),
-					new BasicParserPool(),
-					createHttpClient(metadataURL));
+			try {
+				String metadataURL = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_URL);
+				if (StringUtils.isEmpty(metadataURL)) {
+					log.debug("Use EntityId: " + entityId + " instead of explicite metadataURL ... ");
+					metadataURL = entityId;
+					
+				}				
+				String trustStoreUrl = FileUtils.makeAbsoluteURL(
+						spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_TRUSTSTORE), 
+						authConfig.getConfigurationRootDirectory());
+				String trustStorePassword = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_TRUSTSTORE_PASSWORD);
+
+				return createNewSimpleMetadataProvider(metadataURL, 								 
+						buildMetadataFilterChain(spConfig, metadataURL, trustStoreUrl, trustStorePassword), 
+						spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_UNIQUEIDENTIFIER),
+						getTimer(),
+						new BasicParserPool(),
+						createHttpClient(metadataURL));
+				
+			} catch (PVP2MetadataException e) {
+				log.info("Can NOT initialize Metadata signature-verification filter. Reason: " + e.getMessage());
+				throw new EAAFConfigurationException(
+						"Can NOT initialize Metadata signature-verification filter. Reason: " + e.getMessage(), e);
+				
+			}
 			
 		} else
 			log.info("No ServiceProvider with entityId: " + entityId + " in configuration.");
@@ -77,14 +98,15 @@ public class PVPMetadataProvider extends AbstractChainingMetadataProvider{
 				
 	}
 	
-	private MetadataFilterChain buildMetadataFilterChain(ISPConfiguration oaParam, String metadataURL, String trustStoreUrl) throws CertificateException{
+	private MetadataFilterChain buildMetadataFilterChain(ISPConfiguration oaParam, String metadataURL, String trustStoreUrl, String trustStorePassword) throws CertificateException, PVP2MetadataException{
 		MetadataFilterChain filterChain = new MetadataFilterChain();		
 		filterChain.getFilters().add(new SchemaValidationFilter(
 				basicConfig.getBasicMOAIDConfigurationBoolean(MSeIDASNodeConstants.PROP_CONFIG_PVP_SCHEME_VALIDATION, true)));
+					
+		filterChain.getFilters().add(
+				new MetadataSignatureVerificationFilter(
+						trustStoreUrl, trustStorePassword, metadataURL));
 				
-		//TODO: add signature validation filter
-		
-		
 		filterChain.getFilters().add(new PVPEntityCategoryFilter(
 				basicConfig.getBasicMOAIDConfigurationBoolean(MSeIDASNodeConstants.PROP_CONFIG_PVP_ENABLE_ENTITYCATEGORIES, true)));
 		
diff --git a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/AuthnRequestValidator.java b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/AuthnRequestValidator.java
new file mode 100644
index 00000000..1b912ed4
--- /dev/null
+++ b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/AuthnRequestValidator.java
@@ -0,0 +1,207 @@
+package at.gv.egiz.eidas.specific.connector.verification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.saml2.core.NameID;
+import org.opensaml.saml2.core.NameIDPolicy;
+import org.opensaml.saml2.core.RequestedAuthnContext;
+import org.opensaml.saml2.core.Scoping;
+import org.opensaml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.xml.XMLObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions;
+import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException;
+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.IAuthnRequestValidator;
+import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException;
+import at.gv.egiz.eidas.specific.connector.MSeIDASNodeConstants;
+import at.gv.egiz.eidas.specific.connector.config.ServiceProviderConfiguration;
+
+public class AuthnRequestValidator implements IAuthnRequestValidator {
+
+	private static final Logger log = LoggerFactory.getLogger(AuthnRequestValidator.class);
+	
+	@Override
+	public void validate(HttpServletRequest httpReq, IRequest pendingReq, AuthnRequest authnReq,
+			SPSSODescriptor spSSODescriptor) throws AuthnRequestValidatorException {
+		try {		
+			//validate NameIDPolicy
+			NameIDPolicy nameIDPolicy = authnReq.getNameIDPolicy();
+			if (nameIDPolicy != null) {
+				String nameIDFormat = nameIDPolicy.getFormat();
+				if (nameIDFormat != null) {
+					if ( !(NameID.TRANSIENT.equals(nameIDFormat) ||
+							NameID.PERSISTENT.equals(nameIDFormat)) ) {
+						 
+						throw new NameIDFormatNotSupportedException(nameIDFormat);
+						
+					}
+					
+				} else
+					log.trace("Find NameIDPolicy, but NameIDFormat is 'null'");							
+			} else
+				log.trace("AuthnRequest includes no 'NameIDPolicy'");
+	
+	
+			//post-process RequesterId
+			String spEntityId = extractScopeRequsterId(authnReq);
+			if (StringUtils.isEmpty(spEntityId)) {
+				log.info("NO service-provider entityID in Authn. request. Stop authn. process ... ");
+				throw new AuthnRequestValidatorException("TODO", null, 
+						"NO service-provider entityID in Authn. request", pendingReq);
+				
+			} else
+				pendingReq.setGenericDataToSession(MSeIDASNodeConstants.DATA_REQUESTERID, spEntityId);
+															
+				
+			//post-process ProviderName
+			String providerName = authnReq.getProviderName();	
+			if (StringUtils.isEmpty(providerName))
+				log.info("Authn. request contains NO SP friendlyName");
+			else
+				pendingReq.setGenericDataToSession(MSeIDASNodeConstants.DATA_PROVIDERNAME, spEntityId);
+			
+			//TODO: set to SPConfiguration
+			//post-process requested LoA
+			List<String> reqLoA = extractLoA(authnReq);
+			pendingReq.setGenericDataToSession(MSeIDASNodeConstants.DATA_REQUESTED_LOA_LIST, reqLoA);
+			
+			//TODO: set to SPConfiguration
+			//post-process requested LoA comparison-level
+			String reqLoAComperison = extractComparisonLevel(authnReq);
+			pendingReq.setGenericDataToSession(MSeIDASNodeConstants.DATA_REQUESTED_LOA_COMPERISON, reqLoAComperison);
+			
+			//validate and process requested attributes
+			boolean sectorDetected = false;
+			List<XMLObject> requestedAttributes = authnReq.getExtensions().getUnknownXMLObjects();
+			for (XMLObject reqAttrObj : requestedAttributes) {
+				if (reqAttrObj instanceof EAAFRequestedAttributes) {
+					EAAFRequestedAttributes reqAttr = (EAAFRequestedAttributes)reqAttrObj;
+					if (reqAttr.getAttributes() != null && reqAttr.getAttributes().size() != 0 ) {
+						for (EAAFRequestedAttribute el : reqAttr.getAttributes()) {
+							log.trace("Processing req. attribute '" + el.getName() + "' ... ");
+							if (el.getName().equals(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME)) {
+								if (el.getAttributeValues() != null && el.getAttributeValues().size() == 1) {
+									String sectorId = el.getAttributeValues().get(0).getDOM().getTextContent();
+									ServiceProviderConfiguration spConfig = pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class);
+									
+									try {																								
+										spConfig.setbPKTargetIdentifier(sectorId);
+										sectorDetected = true;
+										
+									} catch (EAAFException e) {
+										log.info("Requested sector: " + sectorId + " DOES NOT match to allowed sectors for SP: " + spConfig.getUniqueIdentifier());
+									}
+									
+								} else 
+									log.info("Req. attribute '" +  el.getName() + "' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute");
+								
+							} else
+								log.debug("Ignore req. attribute: " + el.getName());
+							
+						}
+											
+					} else 
+						log.debug("No requested Attributes in Authn. Request");
+					
+				} else
+					log.info("Ignore unknown requested attribute: " + reqAttrObj.getElementQName().toString());
+				
+			}
+			
+			if (!sectorDetected) {
+				log.info("Authn.Req validation FAILED. Reason: Contains NO or NO VALID target-sector information.");
+				throw new AuthnRequestValidatorException("TODO", null, 
+						"Authn.Req validation FAILED. Reason: Contains NO or NO VALID target-sector information.");
+				
+			}
+					
+		} catch (EAAFStorageException e) {
+			log.info("Can NOT store Authn. Req. data into pendingRequest." , e);
+			throw new AuthnRequestValidatorException("TODO", null, 
+					"Can NOT store Authn. Req. data into pendingRequest.", e);
+			
+		}
+				
+	}
+
+	private String extractComparisonLevel(AuthnRequest authnReq) {
+		if (authnReq.getRequestedAuthnContext() != null) {
+			RequestedAuthnContext authContext = authnReq.getRequestedAuthnContext();
+			return authContext.getComparison().toString();
+			
+		}
+		
+		return null;
+	}
+
+	private List<String> extractLoA(AuthnRequest authnReq) throws AuthnRequestValidatorException {
+		List<String> result = new ArrayList<String>();
+		if (authnReq.getRequestedAuthnContext() != null) {
+			RequestedAuthnContext authContext = authnReq.getRequestedAuthnContext();
+			if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.MINIMUM)) {
+				if (authContext.getAuthnContextClassRefs().isEmpty())  {
+					log.debug("Authn. Req. contains no requested LoA");
+					
+				} else if (authContext.getAuthnContextClassRefs().size() > 1) {
+					log.info("Authn. Req. contains MORE THAN ONE requested LoA, but " 
+							+ AuthnContextComparisonTypeEnumeration.MINIMUM + " allows only one" );
+					throw new AuthnRequestValidatorException("TODO", null, 
+							"Authn. Req. contains MORE THAN ONE requested LoA, but " 
+									+ AuthnContextComparisonTypeEnumeration.MINIMUM + " allows only one");
+					
+				} else
+					result.add(authContext.getAuthnContextClassRefs().get(0).getAuthnContextClassRef());
+				
+			} else if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.EXACT)) {
+				for (AuthnContextClassRef el : authContext.getAuthnContextClassRefs())
+					result.add(el.getAuthnContextClassRef());
+				
+			} else { 
+				log.info("Currently only '" + AuthnContextComparisonTypeEnumeration.MINIMUM + "' and '" 
+						+ AuthnContextComparisonTypeEnumeration.EXACT + "' are supported");
+				throw new AuthnRequestValidatorException("TODO", null, 
+						"Currently only '" + AuthnContextComparisonTypeEnumeration.MINIMUM + "' and '" 
+								+ AuthnContextComparisonTypeEnumeration.EXACT + "' are supported");
+				
+			}
+							
+		}
+		
+		return result;
+	}
+
+	private String extractScopeRequsterId(AuthnRequest authnReq) {
+		if (authnReq.getScoping() != null) {
+			Scoping scoping = authnReq.getScoping();
+			if (scoping.getRequesterIDs() != null && 
+					scoping.getRequesterIDs().size() > 0) {
+				if (scoping.getRequesterIDs().size() == 1)
+					return scoping.getRequesterIDs().get(0).getRequesterID();
+				
+				else {
+					log.info("Authn. request contains more than on RequesterIDs! Only use first one");
+					return scoping.getRequesterIDs().get(0).getRequesterID();
+					
+				}					
+			}			
+		}
+		
+		return null;
+	}
+	
+
+}
diff --git a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java
index ed88091b..d7d75f90 100644
--- a/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java
+++ b/connector/src/main/java/at/gv/egiz/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java
@@ -61,7 +61,8 @@ public class MetadataSignatureVerificationFilter extends AbstractMetadataSignatu
 				}
 				
 				
-			}
+			} else
+				throw new PVP2MetadataException("Can not open trustStore: " + trustStorePath + " for metadata: " + metadataURL, null);
 			
 		} catch (KeyStoreException | IOException e) {
 			log.warn("Can not open trustStore: " + trustStorePath + " for metadata: " + metadataURL + " Reason: " + e.getMessage(), e);
-- 
cgit v1.2.3