diff options
Diffstat (limited to 'eaaf_modules')
2 files changed, 528 insertions, 1062 deletions
diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPVP2XProtocol.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPVP2XProtocol.java deleted file mode 100644 index 597d3c22..00000000 --- a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPVP2XProtocol.java +++ /dev/null @@ -1,549 +0,0 @@ -/******************************************************************************* - * Copyright 2017 Graz University of Technology - * EAAF-Core Components has been developed in a cooperation between EGIZ, - * A-SIT Plus, A-SIT, and Graz University of Technology. - * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by - * the European Commission - subsequent versions of the EUPL (the "Licence"); - * You may not use this work except in compliance with the Licence. - * You may obtain a copy of the Licence at: - * https://joinup.ec.europa.eu/news/understanding-eupl-v12 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the Licence is distributed on an "AS IS" basis, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Licence for the specific language governing permissions and - * limitations under the Licence. - * - * This product combines work with different licenses. See the "NOTICE" text - * file for details on the various modules and licenses. - * The "NOTICE" text file is part of the distribution. Any derivative works - * that you distribute must include a readable copy of the "NOTICE" text file. - *******************************************************************************/ -/******************************************************************************* - *******************************************************************************/ -/******************************************************************************* - *******************************************************************************/ -package at.gv.egiz.eaaf.modules.pvp2.idp.impl; - -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang.StringEscapeUtils; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.opensaml.common.xml.SAMLConstants; -import org.opensaml.saml2.core.AuthnRequest; -import org.opensaml.saml2.core.Issuer; -import org.opensaml.saml2.core.NameID; -import org.opensaml.saml2.core.Response; -import org.opensaml.saml2.core.Status; -import org.opensaml.saml2.core.StatusCode; -import org.opensaml.saml2.core.StatusMessage; -import org.opensaml.saml2.metadata.AssertionConsumerService; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.ws.security.SecurityPolicyException; -import org.opensaml.xml.security.x509.X509Credential; -import org.opensaml.xml.signature.SignableXMLObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -import at.gv.egiz.components.eventlog.api.EventConstants; -import at.gv.egiz.eaaf.core.api.IRequest; -import at.gv.egiz.eaaf.core.api.data.EAAFConstants; -import at.gv.egiz.eaaf.core.api.idp.IModulInfo; -import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; -import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; -import at.gv.egiz.eaaf.core.exceptions.EAAFException; -import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; -import at.gv.egiz.eaaf.core.exceptions.NoPassivAuthenticationException; -import at.gv.egiz.eaaf.core.exceptions.SLOException; -import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; -import at.gv.egiz.eaaf.modules.pvp2.PVPEventConstants; -import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; -import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataProvider; -import at.gv.egiz.eaaf.modules.pvp2.api.validation.IAuthnRequestPostProcessor; -import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPVPRequestException; -import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException; -import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; -import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; -import at.gv.egiz.eaaf.modules.pvp2.idp.exception.InvalidAssertionConsumerServiceException; -import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; -import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; -import at.gv.egiz.eaaf.modules.pvp2.impl.binding.SoapBinding; -import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; -import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; -import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EAAFURICompare; -import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; -import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SAMLVerificationEngine; - -public abstract class AbstractPVP2XProtocol extends AbstractController implements IModulInfo { - private static final Logger log = LoggerFactory.getLogger(AbstractPVP2XProtocol.class); - - @Autowired(required=true) protected IPVP2BasicConfiguration pvpBasicConfiguration; - @Autowired(required=true) protected IPVPMetadataProvider metadataProvider; - @Autowired(required=true) protected SAMLVerificationEngine samlVerificationEngine; - @Autowired(required=false) protected List<IAuthnRequestPostProcessor> authRequestPostProcessors; - - private AbstractCredentialProvider pvpIDPCredentials; - - - - /** - * Sets a specific credential provider for PVP S-Profile IDP component. - * @param pvpIDPCredentials credential provider - */ - public void setPvpIDPCredentials(AbstractCredentialProvider pvpIDPCredentials) { - this.pvpIDPCredentials = pvpIDPCredentials; - - } - - public boolean generateErrorMessage(Throwable e, - HttpServletRequest request, HttpServletResponse response, - IRequest protocolRequest) throws Throwable { - - if(protocolRequest == null) { - throw e; - } - - if(!(protocolRequest instanceof PVPSProfilePendingRequest) ) { - throw e; - } - PVPSProfilePendingRequest pvpRequest = (PVPSProfilePendingRequest)protocolRequest; - - Response samlResponse = - SAML2Utils.createSAMLObject(Response.class); - Status status = SAML2Utils.createSAMLObject(Status.class); - StatusCode statusCode = SAML2Utils.createSAMLObject(StatusCode.class); - StatusMessage statusMessage = SAML2Utils.createSAMLObject(StatusMessage.class); - - String moaError = null; - - if(e instanceof NoPassivAuthenticationException) { - statusCode.setValue(StatusCode.NO_PASSIVE_URI); - statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); - - } else if (e instanceof NameIDFormatNotSupportedException) { - statusCode.setValue(StatusCode.INVALID_NAMEID_POLICY_URI); - statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); - - } else if (e instanceof SLOException) { - //SLOExecpetions only occurs if session information is lost - return false; - - } else if(e instanceof PVP2Exception) { - PVP2Exception ex = (PVP2Exception) e; - statusCode.setValue(ex.getStatusCodeValue()); - String statusMessageValue = ex.getStatusMessageValue(); - if(statusMessageValue != null) { - statusMessage.setMessage(StringEscapeUtils.escapeXml(statusMessageValue)); - } - moaError = statusMessager.mapInternalErrorToExternalError(ex.getErrorId()); - - } else { - statusCode.setValue(StatusCode.RESPONDER_URI); - statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); - moaError = statusMessager.getResponseErrorCode(e); - } - - - if (StringUtils.isNotEmpty(moaError)) { - StatusCode moaStatusCode = SAML2Utils.createSAMLObject(StatusCode.class); - moaStatusCode.setValue(moaError); - statusCode.setStatusCode(moaStatusCode); - } - - status.setStatusCode(statusCode); - if(statusMessage.getMessage() != null) { - status.setStatusMessage(statusMessage); - } - samlResponse.setStatus(status); - String remoteSessionID = SAML2Utils.getSecureIdentifier(); - samlResponse.setID(remoteSessionID); - - samlResponse.setIssueInstant(new DateTime()); - Issuer nissuer = SAML2Utils.createSAMLObject(Issuer.class); - nissuer.setValue(pvpBasicConfiguration.getIDPEntityId(pvpRequest.getAuthURL())); - nissuer.setFormat(NameID.ENTITY); - samlResponse.setIssuer(nissuer); - - IEncoder encoder = null; - - if(pvpRequest.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { - encoder = applicationContext.getBean("PVPRedirectBinding", RedirectBinding.class); - - } else if(pvpRequest.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { - encoder = applicationContext.getBean("PVPPOSTBinding", PostBinding.class); - - } else if (pvpRequest.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) { - encoder = applicationContext.getBean("PVPSOAPBinding", SoapBinding.class); - } - - if(encoder == null) { - // default to redirect binding - encoder = new RedirectBinding(); - } - - String relayState = null; - if (pvpRequest.getRequest() != null) - relayState = pvpRequest.getRequest().getRelayState(); - - X509Credential signCred = pvpIDPCredentials.getIDPAssertionSigningCredential(); - - encoder.encodeRespone(request, response, samlResponse, pvpRequest.getConsumerURL(), - relayState, signCred, protocolRequest); - return true; - } - - public boolean validate(HttpServletRequest request, - HttpServletResponse response, IRequest pending) { - - return true; - } - - protected void pvpMetadataRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { - //create pendingRequest object - PVPSProfilePendingRequest pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); - pendingReq.initialize(req, authConfig); - pendingReq.setModule(getName()); - - revisionsLogger.logEvent( - pendingReq.getUniqueSessionIdentifier(), - pendingReq.getUniqueTransactionIdentifier(), - EventConstants.TRANSACTION_IP, - req.getRemoteAddr()); - - MetadataAction metadataAction = applicationContext.getBean(MetadataAction.class); - metadataAction.processRequest(pendingReq, - req, resp, null); - - } - - protected void PVPIDPPostRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { - PVPSProfilePendingRequest pendingReq = null; - - try { - //create pendingRequest object - pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); - pendingReq.initialize(req, authConfig); - pendingReq.setModule(getName()); - - revisionsLogger.logEvent(EventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); - revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); - revisionsLogger.logEvent( - pendingReq.getUniqueSessionIdentifier(), - pendingReq.getUniqueTransactionIdentifier(), - EventConstants.TRANSACTION_IP, - req.getRemoteAddr()); - - //get POST-Binding decoder implementation - InboundMessage msg = (InboundMessage) new PostBinding().decode( - req, resp, metadataProvider, false, - new EAAFURICompare(pvpBasicConfiguration.getIDPSSOPostService(pendingReq.getAuthURL()))); - pendingReq.setRequest(msg); - - //preProcess Message - preProcess(req, resp, pendingReq); - - } catch (SecurityPolicyException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); - - } catch (SecurityException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); - - } catch (EAAFException e) { - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw e; - - } catch (Throwable e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new EAAFException("pvp2.24", new Object[] {e.getMessage()}, e); - } - } - - protected void PVPIDPRedirecttRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { - PVPSProfilePendingRequest pendingReq = null; - try { - //create pendingRequest object - pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); - pendingReq.initialize(req, authConfig); - pendingReq.setModule(getName()); - - revisionsLogger.logEvent(EventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); - revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); - revisionsLogger.logEvent( - pendingReq.getUniqueSessionIdentifier(), - pendingReq.getUniqueTransactionIdentifier(), - EventConstants.TRANSACTION_IP, - req.getRemoteAddr()); - - //get POST-Binding decoder implementation - InboundMessage msg = (InboundMessage) new RedirectBinding().decode( - req, resp, metadataProvider, false, - new EAAFURICompare(pvpBasicConfiguration.getIDPSSORedirectService(pendingReq.getAuthURL()))); - pendingReq.setRequest(msg); - - //preProcess Message - preProcess(req, resp, pendingReq); - - } catch (SecurityPolicyException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); - - } catch (SecurityException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); - - } catch (EAAFException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.info("Receive INVALID protocol request: " + samlRequest); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw e; - - } catch (Throwable e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new EAAFException("pvp2.24", new Object[] {e.getMessage()}, e); - } - } - - - - /** - * - * - * @param request - * @param response - * @param msg - * @return true if preprocess can handle this request type, otherwise false - * @throws Throwable - */ - abstract protected boolean childPreProcess(HttpServletRequest request, - HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable; - - protected void preProcess(HttpServletRequest request, - HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { - - InboundMessage msg = pendingReq.getRequest(); - - if (StringUtils.isEmpty(msg.getEntityID())) { - throw new InvalidProtocolRequestException("pvp2.20", new Object[] {}); - - } - - if(!msg.isVerified()) { - samlVerificationEngine.verify(msg, - TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); - msg.setVerified(true); - - } - - revisionsLogger.logEvent(pendingReq, IRevisionLogger.AUTHPROTOCOL_TYPE, getAuthProtocolIdentifier()); - - if (msg instanceof PVPSProfileRequest && - ((PVPSProfileRequest)msg).getSamlRequest() instanceof AuthnRequest) - preProcessAuthRequest(request, response, pendingReq); - - else if (childPreProcess(request, response, pendingReq)) - log.debug("Find protocol handler in child implementation"); - - else { - log.error("Receive unsupported PVP21 message of type: " + ((PVPSProfileRequest)msg).getSamlRequest().getClass().getName()); - throw new InvalidPVPRequestException("pvp2.09", - new Object[] {((PVPSProfileRequest)msg).getSamlRequest().getClass().getName()}); - } - - - - //switch to session authentication - protAuthService.performAuthentication(request, response, pendingReq); - } - - - /** - * PreProcess Authn request - * @param request - * @param response - * @param pendingReq - * @throws Throwable - */ - private void preProcessAuthRequest(HttpServletRequest request, - HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { - - PVPSProfileRequest moaRequest = ((PVPSProfileRequest)pendingReq.getRequest()); - SignableXMLObject samlReq = moaRequest.getSamlRequest(); - - if(!(samlReq instanceof AuthnRequest)) { - throw new InvalidPVPRequestException("Unsupported request", new Object[] {}); - } - - EntityDescriptor metadata = moaRequest.getEntityMetadata(metadataProvider); - if(metadata == null) { - throw new NoMetadataInformationException(); - } - SPSSODescriptor spSSODescriptor = metadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS); - - AuthnRequest authnRequest = (AuthnRequest)samlReq; - - if (authnRequest.getIssueInstant() == null) { - log.warn("Unsupported request: No IssueInstant Attribute found."); - throw new AuthnRequestValidatorException("pvp2.22", - new Object[] {"Unsupported request: No IssueInstant Attribute found"}, - pendingReq); - - } - - if (authnRequest.getIssueInstant().minusMinutes(EAAFConstants.ALLOWED_TIME_JITTER).isAfterNow()) { - log.warn("Unsupported request: No IssueInstant DateTime is not valid anymore."); - throw new AuthnRequestValidatorException("pvp2.22", - new Object[] {"Unsupported request: No IssueInstant DateTime is not valid anymore."}, - pendingReq); - - } - - //parse AssertionConsumerService - AssertionConsumerService consumerService = null; - if (StringUtils.isNotEmpty(authnRequest.getAssertionConsumerServiceURL()) && - StringUtils.isNotEmpty(authnRequest.getProtocolBinding())) { - //use AssertionConsumerServiceURL from request - - //check requested AssertionConsumingService URL against metadata - List<AssertionConsumerService> metadataAssertionServiceList = spSSODescriptor.getAssertionConsumerServices(); - for (AssertionConsumerService service : metadataAssertionServiceList) { - if (authnRequest.getProtocolBinding().equals(service.getBinding()) - && authnRequest.getAssertionConsumerServiceURL().equals(service.getLocation())) { - consumerService = SAML2Utils.createSAMLObject(AssertionConsumerService.class); - consumerService.setBinding(authnRequest.getProtocolBinding()); - consumerService.setLocation(authnRequest.getAssertionConsumerServiceURL()); - log.debug("Requested AssertionConsumerServiceURL is valid."); - } - } - - if (consumerService == null) { - throw new InvalidAssertionConsumerServiceException(authnRequest.getAssertionConsumerServiceURL()); - - } - - - } else { - //use AssertionConsumerServiceIndex and select consumerService from metadata - Integer aIdx = authnRequest.getAssertionConsumerServiceIndex(); - int assertionidx = 0; - - if(aIdx != null) { - assertionidx = aIdx.intValue(); - - } else { - assertionidx = SAML2Utils.getDefaultAssertionConsumerServiceIndex(spSSODescriptor); - - } - consumerService = spSSODescriptor.getAssertionConsumerServices().get(assertionidx); - - if (consumerService == null) { - throw new InvalidAssertionConsumerServiceException(aIdx); - - } - } - - - //validate AuthnRequest - AuthnRequest authReq = (AuthnRequest) samlReq; - String oaURL = moaRequest.getEntityMetadata(metadataProvider).getEntityID(); - log.info("Dispatch PVP2 AuthnRequest: OAURL=" + oaURL + " Binding=" + consumerService.getBinding()); - - pendingReq.setSPEntityId(StringEscapeUtils.escapeHtml(oaURL)); - pendingReq.setOnlineApplicationConfiguration(authConfig.getServiceProviderConfiguration(pendingReq.getSPEntityId())); - pendingReq.setBinding(consumerService.getBinding()); - pendingReq.setRequest(moaRequest); - pendingReq.setConsumerURL(consumerService.getLocation()); - - //parse AuthRequest - pendingReq.setPassiv(authReq.isPassive()); - pendingReq.setForce(authReq.isForceAuthn()); - - //AuthnRequest needs authentication - pendingReq.setNeedAuthentication(true); - - //set protocol action, which should be executed after authentication - pendingReq.setAction(AuthenticationAction.class.getName()); - - // do post-processing if required - log.trace("Starting extended AuthnRequest validation and processing ... "); - if (authRequestPostProcessors != null) { - for (final IAuthnRequestPostProcessor processor : authRequestPostProcessors) { - log.trace("Post-process AuthnRequest with module: {}", processor.getClass().getSimpleName()); - processor.process(request, pendingReq, authReq, spSSODescriptor); - - } - } - - log.debug("Extended AuthnRequest validation and processing finished"); - - //write revisionslog entry - revisionsLogger.logEvent(pendingReq, PVPEventConstants.AUTHPROTOCOL_PVP_REQUEST_AUTHREQUEST, authReq.getID()); - - } - - @PostConstruct - private void verifyInitialization() { - if (pvpIDPCredentials == null) { - log.error("No SAML2 credentialProvider injected!"); - throw new RuntimeException("No SAML2 credentialProvider injected!"); - - } - } - -} diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPvp2XProtocol.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPvp2XProtocol.java index 597d3c22..a56c8726 100644 --- a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPvp2XProtocol.java +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/AbstractPvp2XProtocol.java @@ -1,29 +1,22 @@ -/******************************************************************************* - * Copyright 2017 Graz University of Technology - * EAAF-Core Components has been developed in a cooperation between EGIZ, - * A-SIT Plus, A-SIT, and Graz University of Technology. +/* + * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a + * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by - * the European Commission - subsequent versions of the EUPL (the "Licence"); - * You may not use this work except in compliance with the Licence. - * You may obtain a copy of the Licence at: + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the Licence is distributed on an "AS IS" basis, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Licence for the specific language governing permissions and - * limitations under the Licence. - * - * This product combines work with different licenses. See the "NOTICE" text - * file for details on the various modules and licenses. - * The "NOTICE" text file is part of the distribution. Any derivative works - * that you distribute must include a readable copy of the "NOTICE" text file. - *******************************************************************************/ -/******************************************************************************* - *******************************************************************************/ -/******************************************************************************* - *******************************************************************************/ + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text file for details on the + * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative + * works that you distribute must include a readable copy of the "NOTICE" text file. +*/ + package at.gv.egiz.eaaf.modules.pvp2.idp.impl; import java.util.List; @@ -32,518 +25,540 @@ import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang.StringEscapeUtils; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.opensaml.common.xml.SAMLConstants; -import org.opensaml.saml2.core.AuthnRequest; -import org.opensaml.saml2.core.Issuer; -import org.opensaml.saml2.core.NameID; -import org.opensaml.saml2.core.Response; -import org.opensaml.saml2.core.Status; -import org.opensaml.saml2.core.StatusCode; -import org.opensaml.saml2.core.StatusMessage; -import org.opensaml.saml2.metadata.AssertionConsumerService; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.ws.security.SecurityPolicyException; -import org.opensaml.xml.security.x509.X509Credential; -import org.opensaml.xml.signature.SignableXMLObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - import at.gv.egiz.components.eventlog.api.EventConstants; import at.gv.egiz.eaaf.core.api.IRequest; -import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.api.idp.IModulInfo; import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; -import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; import at.gv.egiz.eaaf.core.exceptions.NoPassivAuthenticationException; -import at.gv.egiz.eaaf.core.exceptions.SLOException; +import at.gv.egiz.eaaf.core.exceptions.SloException; import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; -import at.gv.egiz.eaaf.modules.pvp2.PVPEventConstants; -import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.PvpEventConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.IPvp2BasicConfiguration; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; -import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.api.utils.IPvp2CredentialProvider; import at.gv.egiz.eaaf.modules.pvp2.api.validation.IAuthnRequestPostProcessor; -import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPVPRequestException; -import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException; +import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPvpRequestException; +import at.gv.egiz.eaaf.modules.pvp2.exception.NameIdFormatNotSupportedException; import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; -import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2Exception; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; import at.gv.egiz.eaaf.modules.pvp2.idp.exception.InvalidAssertionConsumerServiceException; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.SoapBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; -import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; -import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; -import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EAAFURICompare; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EaafUriCompare; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; -import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SAMLVerificationEngine; - -public abstract class AbstractPVP2XProtocol extends AbstractController implements IModulInfo { - private static final Logger log = LoggerFactory.getLogger(AbstractPVP2XProtocol.class); - - @Autowired(required=true) protected IPVP2BasicConfiguration pvpBasicConfiguration; - @Autowired(required=true) protected IPVPMetadataProvider metadataProvider; - @Autowired(required=true) protected SAMLVerificationEngine samlVerificationEngine; - @Autowired(required=false) protected List<IAuthnRequestPostProcessor> authRequestPostProcessors; - - private AbstractCredentialProvider pvpIDPCredentials; - - - - /** - * Sets a specific credential provider for PVP S-Profile IDP component. - * @param pvpIDPCredentials credential provider - */ - public void setPvpIDPCredentials(AbstractCredentialProvider pvpIDPCredentials) { - this.pvpIDPCredentials = pvpIDPCredentials; - - } - - public boolean generateErrorMessage(Throwable e, - HttpServletRequest request, HttpServletResponse response, - IRequest protocolRequest) throws Throwable { - - if(protocolRequest == null) { - throw e; - } - - if(!(protocolRequest instanceof PVPSProfilePendingRequest) ) { - throw e; - } - PVPSProfilePendingRequest pvpRequest = (PVPSProfilePendingRequest)protocolRequest; - - Response samlResponse = - SAML2Utils.createSAMLObject(Response.class); - Status status = SAML2Utils.createSAMLObject(Status.class); - StatusCode statusCode = SAML2Utils.createSAMLObject(StatusCode.class); - StatusMessage statusMessage = SAML2Utils.createSAMLObject(StatusMessage.class); - - String moaError = null; - - if(e instanceof NoPassivAuthenticationException) { - statusCode.setValue(StatusCode.NO_PASSIVE_URI); - statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); - - } else if (e instanceof NameIDFormatNotSupportedException) { - statusCode.setValue(StatusCode.INVALID_NAMEID_POLICY_URI); - statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); - - } else if (e instanceof SLOException) { - //SLOExecpetions only occurs if session information is lost - return false; - - } else if(e instanceof PVP2Exception) { - PVP2Exception ex = (PVP2Exception) e; - statusCode.setValue(ex.getStatusCodeValue()); - String statusMessageValue = ex.getStatusMessageValue(); - if(statusMessageValue != null) { - statusMessage.setMessage(StringEscapeUtils.escapeXml(statusMessageValue)); - } - moaError = statusMessager.mapInternalErrorToExternalError(ex.getErrorId()); - - } else { - statusCode.setValue(StatusCode.RESPONDER_URI); - statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); - moaError = statusMessager.getResponseErrorCode(e); - } - - - if (StringUtils.isNotEmpty(moaError)) { - StatusCode moaStatusCode = SAML2Utils.createSAMLObject(StatusCode.class); - moaStatusCode.setValue(moaError); - statusCode.setStatusCode(moaStatusCode); - } - - status.setStatusCode(statusCode); - if(statusMessage.getMessage() != null) { - status.setStatusMessage(statusMessage); - } - samlResponse.setStatus(status); - String remoteSessionID = SAML2Utils.getSecureIdentifier(); - samlResponse.setID(remoteSessionID); - - samlResponse.setIssueInstant(new DateTime()); - Issuer nissuer = SAML2Utils.createSAMLObject(Issuer.class); - nissuer.setValue(pvpBasicConfiguration.getIDPEntityId(pvpRequest.getAuthURL())); - nissuer.setFormat(NameID.ENTITY); - samlResponse.setIssuer(nissuer); - - IEncoder encoder = null; - - if(pvpRequest.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { - encoder = applicationContext.getBean("PVPRedirectBinding", RedirectBinding.class); - - } else if(pvpRequest.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { - encoder = applicationContext.getBean("PVPPOSTBinding", PostBinding.class); - - } else if (pvpRequest.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) { - encoder = applicationContext.getBean("PVPSOAPBinding", SoapBinding.class); - } - - if(encoder == null) { - // default to redirect binding - encoder = new RedirectBinding(); - } - - String relayState = null; - if (pvpRequest.getRequest() != null) - relayState = pvpRequest.getRequest().getRelayState(); - - X509Credential signCred = pvpIDPCredentials.getIDPAssertionSigningCredential(); - - encoder.encodeRespone(request, response, samlResponse, pvpRequest.getConsumerURL(), - relayState, signCred, protocolRequest); - return true; - } - - public boolean validate(HttpServletRequest request, - HttpServletResponse response, IRequest pending) { - - return true; - } - - protected void pvpMetadataRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { - //create pendingRequest object - PVPSProfilePendingRequest pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); - pendingReq.initialize(req, authConfig); - pendingReq.setModule(getName()); - - revisionsLogger.logEvent( - pendingReq.getUniqueSessionIdentifier(), - pendingReq.getUniqueTransactionIdentifier(), - EventConstants.TRANSACTION_IP, - req.getRemoteAddr()); - - MetadataAction metadataAction = applicationContext.getBean(MetadataAction.class); - metadataAction.processRequest(pendingReq, - req, resp, null); - - } - - protected void PVPIDPPostRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { - PVPSProfilePendingRequest pendingReq = null; - - try { - //create pendingRequest object - pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); - pendingReq.initialize(req, authConfig); - pendingReq.setModule(getName()); - - revisionsLogger.logEvent(EventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); - revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); - revisionsLogger.logEvent( - pendingReq.getUniqueSessionIdentifier(), - pendingReq.getUniqueTransactionIdentifier(), - EventConstants.TRANSACTION_IP, - req.getRemoteAddr()); - - //get POST-Binding decoder implementation - InboundMessage msg = (InboundMessage) new PostBinding().decode( - req, resp, metadataProvider, false, - new EAAFURICompare(pvpBasicConfiguration.getIDPSSOPostService(pendingReq.getAuthURL()))); - pendingReq.setRequest(msg); - - //preProcess Message - preProcess(req, resp, pendingReq); - - } catch (SecurityPolicyException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); - - } catch (SecurityException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); - - } catch (EAAFException e) { - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw e; - - } catch (Throwable e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new EAAFException("pvp2.24", new Object[] {e.getMessage()}, e); - } - } - - protected void PVPIDPRedirecttRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { - PVPSProfilePendingRequest pendingReq = null; - try { - //create pendingRequest object - pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); - pendingReq.initialize(req, authConfig); - pendingReq.setModule(getName()); - - revisionsLogger.logEvent(EventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); - revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); - revisionsLogger.logEvent( - pendingReq.getUniqueSessionIdentifier(), - pendingReq.getUniqueTransactionIdentifier(), - EventConstants.TRANSACTION_IP, - req.getRemoteAddr()); - - //get POST-Binding decoder implementation - InboundMessage msg = (InboundMessage) new RedirectBinding().decode( - req, resp, metadataProvider, false, - new EAAFURICompare(pvpBasicConfiguration.getIDPSSORedirectService(pendingReq.getAuthURL()))); - pendingReq.setRequest(msg); - - //preProcess Message - preProcess(req, resp, pendingReq); - - } catch (SecurityPolicyException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); - - } catch (SecurityException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); - - } catch (EAAFException e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.info("Receive INVALID protocol request: " + samlRequest); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw e; - - } catch (Throwable e) { - String samlRequest = req.getParameter("SAMLRequest"); - log.warn("Receive INVALID protocol request: " + samlRequest, e); - - //write revision log entries - if (pendingReq != null) - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); - - throw new EAAFException("pvp2.24", new Object[] {e.getMessage()}, e); - } - } - - - - /** - * - * - * @param request - * @param response - * @param msg - * @return true if preprocess can handle this request type, otherwise false - * @throws Throwable - */ - abstract protected boolean childPreProcess(HttpServletRequest request, - HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable; - - protected void preProcess(HttpServletRequest request, - HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { - - InboundMessage msg = pendingReq.getRequest(); - - if (StringUtils.isEmpty(msg.getEntityID())) { - throw new InvalidProtocolRequestException("pvp2.20", new Object[] {}); - - } - - if(!msg.isVerified()) { - samlVerificationEngine.verify(msg, - TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); - msg.setVerified(true); - - } - - revisionsLogger.logEvent(pendingReq, IRevisionLogger.AUTHPROTOCOL_TYPE, getAuthProtocolIdentifier()); - - if (msg instanceof PVPSProfileRequest && - ((PVPSProfileRequest)msg).getSamlRequest() instanceof AuthnRequest) - preProcessAuthRequest(request, response, pendingReq); - - else if (childPreProcess(request, response, pendingReq)) - log.debug("Find protocol handler in child implementation"); - - else { - log.error("Receive unsupported PVP21 message of type: " + ((PVPSProfileRequest)msg).getSamlRequest().getClass().getName()); - throw new InvalidPVPRequestException("pvp2.09", - new Object[] {((PVPSProfileRequest)msg).getSamlRequest().getClass().getName()}); - } - - - - //switch to session authentication - protAuthService.performAuthentication(request, response, pendingReq); - } - - - /** - * PreProcess Authn request - * @param request - * @param response - * @param pendingReq - * @throws Throwable - */ - private void preProcessAuthRequest(HttpServletRequest request, - HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { - - PVPSProfileRequest moaRequest = ((PVPSProfileRequest)pendingReq.getRequest()); - SignableXMLObject samlReq = moaRequest.getSamlRequest(); - - if(!(samlReq instanceof AuthnRequest)) { - throw new InvalidPVPRequestException("Unsupported request", new Object[] {}); - } - - EntityDescriptor metadata = moaRequest.getEntityMetadata(metadataProvider); - if(metadata == null) { - throw new NoMetadataInformationException(); - } - SPSSODescriptor spSSODescriptor = metadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS); - - AuthnRequest authnRequest = (AuthnRequest)samlReq; - - if (authnRequest.getIssueInstant() == null) { - log.warn("Unsupported request: No IssueInstant Attribute found."); - throw new AuthnRequestValidatorException("pvp2.22", - new Object[] {"Unsupported request: No IssueInstant Attribute found"}, - pendingReq); - - } - - if (authnRequest.getIssueInstant().minusMinutes(EAAFConstants.ALLOWED_TIME_JITTER).isAfterNow()) { - log.warn("Unsupported request: No IssueInstant DateTime is not valid anymore."); - throw new AuthnRequestValidatorException("pvp2.22", - new Object[] {"Unsupported request: No IssueInstant DateTime is not valid anymore."}, - pendingReq); - - } - - //parse AssertionConsumerService - AssertionConsumerService consumerService = null; - if (StringUtils.isNotEmpty(authnRequest.getAssertionConsumerServiceURL()) && - StringUtils.isNotEmpty(authnRequest.getProtocolBinding())) { - //use AssertionConsumerServiceURL from request - - //check requested AssertionConsumingService URL against metadata - List<AssertionConsumerService> metadataAssertionServiceList = spSSODescriptor.getAssertionConsumerServices(); - for (AssertionConsumerService service : metadataAssertionServiceList) { - if (authnRequest.getProtocolBinding().equals(service.getBinding()) - && authnRequest.getAssertionConsumerServiceURL().equals(service.getLocation())) { - consumerService = SAML2Utils.createSAMLObject(AssertionConsumerService.class); - consumerService.setBinding(authnRequest.getProtocolBinding()); - consumerService.setLocation(authnRequest.getAssertionConsumerServiceURL()); - log.debug("Requested AssertionConsumerServiceURL is valid."); - } - } - - if (consumerService == null) { - throw new InvalidAssertionConsumerServiceException(authnRequest.getAssertionConsumerServiceURL()); - - } - - - } else { - //use AssertionConsumerServiceIndex and select consumerService from metadata - Integer aIdx = authnRequest.getAssertionConsumerServiceIndex(); - int assertionidx = 0; - - if(aIdx != null) { - assertionidx = aIdx.intValue(); - - } else { - assertionidx = SAML2Utils.getDefaultAssertionConsumerServiceIndex(spSSODescriptor); - - } - consumerService = spSSODescriptor.getAssertionConsumerServices().get(assertionidx); - - if (consumerService == null) { - throw new InvalidAssertionConsumerServiceException(aIdx); - - } - } - - - //validate AuthnRequest - AuthnRequest authReq = (AuthnRequest) samlReq; - String oaURL = moaRequest.getEntityMetadata(metadataProvider).getEntityID(); - log.info("Dispatch PVP2 AuthnRequest: OAURL=" + oaURL + " Binding=" + consumerService.getBinding()); - - pendingReq.setSPEntityId(StringEscapeUtils.escapeHtml(oaURL)); - pendingReq.setOnlineApplicationConfiguration(authConfig.getServiceProviderConfiguration(pendingReq.getSPEntityId())); - pendingReq.setBinding(consumerService.getBinding()); - pendingReq.setRequest(moaRequest); - pendingReq.setConsumerURL(consumerService.getLocation()); - - //parse AuthRequest - pendingReq.setPassiv(authReq.isPassive()); - pendingReq.setForce(authReq.isForceAuthn()); - - //AuthnRequest needs authentication - pendingReq.setNeedAuthentication(true); - - //set protocol action, which should be executed after authentication - pendingReq.setAction(AuthenticationAction.class.getName()); - - // do post-processing if required +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SamlVerificationEngine; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.saml.saml2.core.NameIDType; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.core.StatusMessage; +import org.opensaml.saml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.SPSSODescriptor; +import org.opensaml.xmlsec.signature.SignableXMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public abstract class AbstractPvp2XProtocol extends AbstractController implements IModulInfo { + private static final Logger log = LoggerFactory.getLogger(AbstractPvp2XProtocol.class); + + private static final String HTTP_PARAM_SAMLREQ = "SAMLRequest"; + private static final String ERROR_INVALID_REQUEST = "Receive INVALID protocol request: {}"; + + @Autowired(required = true) + protected IPvp2BasicConfiguration pvpBasicConfiguration; + @Autowired(required = true) + protected IPvp2MetadataProvider metadataProvider; + @Autowired(required = true) + protected SamlVerificationEngine samlVerificationEngine; + @Autowired(required = false) + protected List<IAuthnRequestPostProcessor> authRequestPostProcessors; + + private IPvp2CredentialProvider pvpIdpCredentials; + + /** + * Sets a specific credential provider for PVP S-Profile IDP component. + * + * @param pvpIdpCredentials credential provider + */ + public void setPvpIdpCredentials(final IPvp2CredentialProvider pvpIdpCredentials) { + this.pvpIdpCredentials = pvpIdpCredentials; + + } + + @Override + public boolean generateErrorMessage(final Throwable e, final HttpServletRequest request, + final HttpServletResponse response, final IRequest protocolRequest) throws Throwable { + + if (protocolRequest == null) { + throw e; + } + + if (!(protocolRequest instanceof PvpSProfilePendingRequest)) { + throw e; + } + final PvpSProfilePendingRequest pvpRequest = (PvpSProfilePendingRequest) protocolRequest; + + final Response samlResponse = Saml2Utils.createSamlObject(Response.class); + final Status status = Saml2Utils.createSamlObject(Status.class); + final StatusCode statusCode = Saml2Utils.createSamlObject(StatusCode.class); + final StatusMessage statusMessage = Saml2Utils.createSamlObject(StatusMessage.class); + + String moaError = null; + + if (e instanceof NoPassivAuthenticationException) { + statusCode.setValue(StatusCode.NO_PASSIVE); + statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); + + } else if (e instanceof NameIdFormatNotSupportedException) { + statusCode.setValue(StatusCode.INVALID_NAMEID_POLICY); + statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); + + } else if (e instanceof SloException) { + // SLOExecpetions only occurs if session information is lost + return false; + + } else if (e instanceof Pvp2Exception) { + final Pvp2Exception ex = (Pvp2Exception) e; + statusCode.setValue(ex.getStatusCodeValue()); + final String statusMessageValue = ex.getStatusMessageValue(); + if (statusMessageValue != null) { + statusMessage.setMessage(StringEscapeUtils.escapeXml(statusMessageValue)); + } + moaError = statusMessager.mapInternalErrorToExternalError(ex.getErrorId()); + + } else { + statusCode.setValue(StatusCode.RESPONDER); + statusMessage.setMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())); + moaError = statusMessager.getResponseErrorCode(e); + } + + if (StringUtils.isNotEmpty(moaError)) { + final StatusCode moaStatusCode = Saml2Utils.createSamlObject(StatusCode.class); + moaStatusCode.setValue(moaError); + statusCode.setStatusCode(moaStatusCode); + } + + status.setStatusCode(statusCode); + if (statusMessage.getMessage() != null) { + status.setStatusMessage(statusMessage); + } + samlResponse.setStatus(status); + final String remoteSessionID = Saml2Utils.getSecureIdentifier(); + samlResponse.setID(remoteSessionID); + + samlResponse.setIssueInstant(new DateTime()); + final Issuer nissuer = Saml2Utils.createSamlObject(Issuer.class); + nissuer.setValue(pvpBasicConfiguration.getIdpEntityId(pvpRequest.getAuthUrl())); + nissuer.setFormat(NameIDType.ENTITY); + samlResponse.setIssuer(nissuer); + + IEncoder encoder = null; + + if (pvpRequest.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { + encoder = applicationContext.getBean("PVPRedirectBinding", RedirectBinding.class); + + } else if (pvpRequest.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { + encoder = applicationContext.getBean("PVPPOSTBinding", PostBinding.class); + + } else if (pvpRequest.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) { + encoder = applicationContext.getBean("PVPSOAPBinding", SoapBinding.class); + } + + if (encoder == null) { + // default to redirect binding + encoder = new RedirectBinding(); + } + + String relayState = null; + if (pvpRequest.getRequest() != null) { + relayState = pvpRequest.getRequest().getRelayState(); + } + + final EaafX509Credential signCred = pvpIdpCredentials.getMessageSigningCredential(); + + encoder.encodeResponse(request, response, samlResponse, pvpRequest.getConsumerUrl(), relayState, + signCred, protocolRequest); + return true; + } + + @Override + public boolean validate(final HttpServletRequest request, final HttpServletResponse response, + final IRequest pending) { + + return true; + } + + protected void pvpMetadataRequest(final HttpServletRequest req, final HttpServletResponse resp) + throws EaafException { + // create pendingRequest object + final PvpSProfilePendingRequest pendingReq = + applicationContext.getBean(PvpSProfilePendingRequest.class); + pendingReq.initialize(req, authConfig); + pendingReq.setModule(getName()); + + revisionsLogger.logEvent(pendingReq.getUniqueSessionIdentifier(), + pendingReq.getUniqueTransactionIdentifier(), EventConstants.TRANSACTION_IP, + req.getRemoteAddr()); + + final MetadataAction metadataAction = applicationContext.getBean(MetadataAction.class); + metadataAction.processRequest(pendingReq, req, resp, null); + + } + + protected void pvpIdpPostRequest(final HttpServletRequest req, final HttpServletResponse resp) + throws EaafException { + PvpSProfilePendingRequest pendingReq = null; + + try { + // create pendingRequest object + pendingReq = applicationContext.getBean(PvpSProfilePendingRequest.class); + pendingReq.initialize(req, authConfig); + pendingReq.setModule(getName()); + + revisionsLogger.logEvent(EventConstants.SESSION_CREATED, + pendingReq.getUniqueSessionIdentifier()); + revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, + pendingReq.getUniqueTransactionIdentifier()); + revisionsLogger.logEvent(pendingReq.getUniqueSessionIdentifier(), + pendingReq.getUniqueTransactionIdentifier(), EventConstants.TRANSACTION_IP, + req.getRemoteAddr()); + + // get POST-Binding decoder implementation + final InboundMessage msg = (InboundMessage) new PostBinding().decode(req, resp, + metadataProvider, SPSSODescriptor.DEFAULT_ELEMENT_NAME, + new EaafUriCompare(pvpBasicConfiguration.getIdpSsoPostService(pendingReq.getAuthUrl()))); + pendingReq.setRequest(msg); + + // preProcess Message + preProcess(req, resp, pendingReq); + + } catch (final SamlSigningException e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.warn(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); + + } catch (final Pvp2Exception e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.warn(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw new InvalidProtocolRequestException("pvp2.22", new Object[] { e.getMessage() }); + + } catch (final EaafException e) { + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw e; + + } catch (final Throwable e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.warn(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw new EaafException("pvp2.24", new Object[] { e.getMessage() }, e); + } + } + + protected void pvpIdpRedirecttRequest(final HttpServletRequest req, + final HttpServletResponse resp) throws EaafException { + PvpSProfilePendingRequest pendingReq = null; + try { + // create pendingRequest object + pendingReq = applicationContext.getBean(PvpSProfilePendingRequest.class); + pendingReq.initialize(req, authConfig); + pendingReq.setModule(getName()); + + revisionsLogger.logEvent(EventConstants.SESSION_CREATED, + pendingReq.getUniqueSessionIdentifier()); + revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, + pendingReq.getUniqueTransactionIdentifier()); + revisionsLogger.logEvent(pendingReq.getUniqueSessionIdentifier(), + pendingReq.getUniqueTransactionIdentifier(), EventConstants.TRANSACTION_IP, + req.getRemoteAddr()); + + // get POST-Binding decoder implementation + final InboundMessage msg = (InboundMessage) new RedirectBinding().decode(req, resp, + metadataProvider, SPSSODescriptor.DEFAULT_ELEMENT_NAME, new EaafUriCompare( + pvpBasicConfiguration.getIdpSsoRedirectService(pendingReq.getAuthUrl()))); + pendingReq.setRequest(msg); + + // preProcess Message + preProcess(req, resp, pendingReq); + + } catch (final SamlSigningException e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.warn(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); + + } catch (final Pvp2Exception e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.warn(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw new InvalidProtocolRequestException("pvp2.22", new Object[] { e.getMessage() }); + + } catch (final EaafException e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.info(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw e; + + } catch (final Throwable e) { + final String samlRequest = req.getParameter(HTTP_PARAM_SAMLREQ); + log.warn(ERROR_INVALID_REQUEST, samlRequest, null, e); + + // write revision log entries + if (pendingReq != null) { + revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, + pendingReq.getUniqueTransactionIdentifier()); + } + + throw new EaafException("pvp2.24", new Object[] { e.getMessage() }, e); + } + } + + /** + * Authentication request pre-processor. + * + * @param request http request + * @param response http response + * @param pendingReq current pending request + * @return true if preprocess can handle this request type, otherwise false + * @throws Throwable In case of an error + */ + protected abstract boolean childPreProcess(HttpServletRequest request, + HttpServletResponse response, PvpSProfilePendingRequest pendingReq) throws Throwable; + + protected void preProcess(final HttpServletRequest request, final HttpServletResponse response, + final PvpSProfilePendingRequest pendingReq) throws Throwable { + + final InboundMessage msg = pendingReq.getRequest(); + + if (StringUtils.isEmpty(msg.getEntityID())) { + throw new InvalidProtocolRequestException("pvp2.20", new Object[] {}); + + } + + if (!msg.isVerified()) { + samlVerificationEngine.verify(msg, + TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); + msg.setVerified(true); + + } + + revisionsLogger.logEvent(pendingReq, IRevisionLogger.AUTHPROTOCOL_TYPE, + getAuthProtocolIdentifier()); + + if (msg instanceof PvpSProfileRequest + && ((PvpSProfileRequest) msg).getSamlRequest() instanceof AuthnRequest) { + preProcessAuthRequest(request, pendingReq); + } else if (childPreProcess(request, response, pendingReq)) { + log.debug("Find protocol handler in child implementation"); + } else { + log.error("Receive unsupported PVP21 message of type: " + + ((PvpSProfileRequest) msg).getSamlRequest().getClass().getName()); + throw new InvalidPvpRequestException("pvp2.09", + new Object[] { ((PvpSProfileRequest) msg).getSamlRequest().getClass().getName() }); + } + + // switch to session authentication + protAuthService.performAuthentication(request, response, pendingReq); + } + + /** + * PreProcess Authn request. + * + * @param request http request + * @param pendingReq current pending request + * @throws Throwable in case of an error + */ + protected void preProcessAuthRequest(final HttpServletRequest request, + final PvpSProfilePendingRequest pendingReq) + throws Throwable { + + final PvpSProfileRequest moaRequest = (PvpSProfileRequest) pendingReq.getRequest(); + final SignableXMLObject samlReq = moaRequest.getSamlRequest(); + + if (!(samlReq instanceof AuthnRequest)) { + throw new InvalidPvpRequestException("Unsupported request", new Object[] {}); + } + + final EntityDescriptor metadata = moaRequest.getEntityMetadata(metadataProvider); + if (metadata == null) { + throw new NoMetadataInformationException(); + } + final SPSSODescriptor spSsoDescriptor = metadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS); + + final AuthnRequest authnRequest = (AuthnRequest) samlReq; + + if (authnRequest.getIssueInstant() == null) { + log.warn("Unsupported request: No IssueInstant Attribute found."); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] { "Unsupported request: No IssueInstant Attribute found" }, pendingReq); + + } + + if (authnRequest.getIssueInstant().minusMinutes(EaafConstants.ALLOWED_TIME_JITTER) + .isAfterNow()) { + log.warn("Unsupported request: No IssueInstant DateTime is not valid anymore."); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] { "Unsupported request: No IssueInstant DateTime is not valid anymore." }, + pendingReq); + + } + + // parse AssertionConsumerService + AssertionConsumerService consumerService = null; + if (StringUtils.isNotEmpty(authnRequest.getAssertionConsumerServiceURL()) + && StringUtils.isNotEmpty(authnRequest.getProtocolBinding())) { + // use AssertionConsumerServiceURL from request + + // check requested AssertionConsumingService URL against metadata + final List<AssertionConsumerService> metadataAssertionServiceList = + spSsoDescriptor.getAssertionConsumerServices(); + for (final AssertionConsumerService service : metadataAssertionServiceList) { + if (authnRequest.getProtocolBinding().equals(service.getBinding()) + && authnRequest.getAssertionConsumerServiceURL().equals(service.getLocation())) { + consumerService = Saml2Utils.createSamlObject(AssertionConsumerService.class); + consumerService.setBinding(authnRequest.getProtocolBinding()); + consumerService.setLocation(authnRequest.getAssertionConsumerServiceURL()); + log.debug("Requested AssertionConsumerServiceURL is valid."); + } + } + + if (consumerService == null) { + throw new InvalidAssertionConsumerServiceException( + authnRequest.getAssertionConsumerServiceURL()); + + } + + } else { + // use AssertionConsumerServiceIndex and select consumerService from metadata + final Integer aIdx = authnRequest.getAssertionConsumerServiceIndex(); + int assertionidx = 0; + + if (aIdx != null) { + assertionidx = aIdx; + + } else { + assertionidx = Saml2Utils.getDefaultAssertionConsumerServiceIndex(spSsoDescriptor); + + } + consumerService = spSsoDescriptor.getAssertionConsumerServices().get(assertionidx); + + if (consumerService == null) { + throw new InvalidAssertionConsumerServiceException(aIdx); + + } + } + + // validate AuthnRequest + final AuthnRequest authReq = (AuthnRequest) samlReq; + final String oaUrl = moaRequest.getEntityMetadata(metadataProvider).getEntityID(); + log.info( + "Dispatch PVP2 AuthnRequest: OAURL=" + oaUrl + " Binding=" + consumerService.getBinding()); + + pendingReq.setSpEntityId(StringEscapeUtils.escapeHtml(oaUrl)); + pendingReq.setOnlineApplicationConfiguration( + authConfig.getServiceProviderConfiguration(pendingReq.getSpEntityId())); + pendingReq.setBinding(consumerService.getBinding()); + pendingReq.setRequest(moaRequest); + pendingReq.setConsumerUrl(consumerService.getLocation()); + + // parse AuthRequest + pendingReq.setPassiv(authReq.isPassive()); + pendingReq.setForce(authReq.isForceAuthn()); + + // AuthnRequest needs authentication + pendingReq.setNeedAuthentication(true); + + // set protocol action, which should be executed after authentication + pendingReq.setAction(AuthenticationAction.class.getName()); + + // do post-processing if required log.trace("Starting extended AuthnRequest validation and processing ... "); if (authRequestPostProcessors != null) { for (final IAuthnRequestPostProcessor processor : authRequestPostProcessors) { log.trace("Post-process AuthnRequest with module: {}", processor.getClass().getSimpleName()); - processor.process(request, pendingReq, authReq, spSSODescriptor); + processor.process(request, pendingReq, authReq, spSsoDescriptor); } } - - log.debug("Extended AuthnRequest validation and processing finished"); - - //write revisionslog entry - revisionsLogger.logEvent(pendingReq, PVPEventConstants.AUTHPROTOCOL_PVP_REQUEST_AUTHREQUEST, authReq.getID()); - - } - - @PostConstruct - private void verifyInitialization() { - if (pvpIDPCredentials == null) { - log.error("No SAML2 credentialProvider injected!"); - throw new RuntimeException("No SAML2 credentialProvider injected!"); - - } - } - + + log.debug("Extended AuthnRequest validation and processing finished"); + + // write revisionslog entry + revisionsLogger.logEvent(pendingReq, PvpEventConstants.AUTHPROTOCOL_PVP_REQUEST_AUTHREQUEST, + authReq.getID()); + + } + + @PostConstruct + private void verifyInitialization() { + if (pvpIdpCredentials == null) { + log.error("No SAML2 credentialProvider injected!"); + throw new RuntimeException("No SAML2 credentialProvider injected!"); + + } + } + } |