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("pvp2.22", new Object[] {"NO relaying-party entityID in Authn. request"}, pendingReq); } else pendingReq.setRawDataToTransaction(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.setRawDataToTransaction(MSeIDASNodeConstants.DATA_PROVIDERNAME, spEntityId); //post-process requested LoA List reqLoA = extractLoA(authnReq); pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setRequiredLoA(reqLoA); //post-process requested LoA comparison-level String reqLoAComperison = extractComparisonLevel(authnReq); pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setLoAMachtingMode(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("pvp2.22", new Object[] {"NO or NO VALID target-sector information"}); } } catch (EAAFStorageException e) { log.info("Can NOT store Authn. Req. data into pendingRequest." , e); throw new AuthnRequestValidatorException("internal.02", null, 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("pvp2.22", new Object[] {"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("pvp2.22", new Object[] {"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; } }