From 2945c875bda2c8236d0b3fd630358fcaca85f4a8 Mon Sep 17 00:00:00 2001 From: Thomas Lenz 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 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 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 extractLoA(AuthnRequest authnReq) throws AuthnRequestValidatorException { + List result = new ArrayList(); + 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