/* * 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.impl.binding; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.gui.IGuiBuilderConfigurationFactory; import at.gv.egiz.eaaf.core.api.gui.IVelocityGuiBuilderConfiguration; import at.gv.egiz.eaaf.core.api.gui.IVelocityGuiFormBuilder; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.impl.gui.velocity.VelocityProvider; import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; 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.message.PvpSProfileResponse; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.HttpPostEncoderWithOwnTemplate; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EaafDefaultSaml2Bootstrap; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PvpSignedRequestPolicyRule; import org.apache.commons.lang3.StringUtils; import org.opensaml.common.SAMLObject; import org.opensaml.common.binding.BasicSAMLMessageContext; import org.opensaml.common.binding.decoding.URIComparator; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.binding.decoding.HTTPPostDecoder; import org.opensaml.saml2.core.RequestAbstractType; import org.opensaml.saml2.core.StatusResponseType; import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.saml2.metadata.SingleSignOnService; import org.opensaml.saml2.metadata.impl.SingleSignOnServiceBuilder; import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.security.SecurityPolicyResolver; import org.opensaml.ws.security.provider.BasicSecurityPolicy; import org.opensaml.ws.security.provider.StaticSecurityPolicyResolver; import org.opensaml.ws.transport.http.HttpServletRequestAdapter; import org.opensaml.ws.transport.http.HttpServletResponseAdapter; import org.opensaml.xml.parse.BasicParserPool; import org.opensaml.xml.security.SecurityException; import org.opensaml.xml.security.credential.Credential; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("PVPPOSTBinding") public class PostBinding implements IDecoder, IEncoder { private static final Logger log = LoggerFactory.getLogger(PostBinding.class); @Autowired(required = true) IConfiguration authConfig; @Autowired(required = true) IVelocityGuiFormBuilder guiBuilder; @Autowired(required = true) IGuiBuilderConfigurationFactory guiConfigFactory; @Override public void encodeRequest(final HttpServletRequest req, final HttpServletResponse resp, final RequestAbstractType request, final String targetLocation, final String relayState, final Credential credentials, final IRequest pendingReq) throws MessageEncodingException, SecurityException { try { // load default PVP security configurations EaafDefaultSaml2Bootstrap.initializeDefaultPvpConfiguration(); // initialize POST binding encoder with template decoration final IVelocityGuiBuilderConfiguration guiConfig = guiConfigFactory.getSpSpecificSaml2PostConfiguration(pendingReq, "pvp_postbinding_template.html", authConfig.getConfigurationRootDirectory()); final HttpPostEncoderWithOwnTemplate encoder = new HttpPostEncoderWithOwnTemplate(guiConfig, guiBuilder, VelocityProvider.getClassPathVelocityEngine()); // set OpenSAML2 process parameter into binding context dao final HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter(resp, true); final BasicSAMLMessageContext context = new BasicSAMLMessageContext<>(); final SingleSignOnService service = new SingleSignOnServiceBuilder().buildObject(); service.setBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); service.setLocation(targetLocation); context.setOutboundSAMLMessageSigningCredential(credentials); context.setPeerEntityEndpoint(service); context.setOutboundSAMLMessage(request); context.setOutboundMessageTransport(responseAdapter); context.setRelayState(relayState); encoder.encode(context); } catch (final Exception e) { log.warn("Can not encode SAML2 request", e); throw new SecurityException(e); } } @Override public void encodeRespone(final HttpServletRequest req, final HttpServletResponse resp, final StatusResponseType response, final String targetLocation, final String relayState, final Credential credentials, final IRequest pendingReq) throws MessageEncodingException, SecurityException { try { // load default PVP security configurations EaafDefaultSaml2Bootstrap.initializeDefaultPvpConfiguration(); log.debug("create SAML POSTBinding response"); // initialize POST binding encoder with template decoration final IVelocityGuiBuilderConfiguration guiConfig = guiConfigFactory.getSpSpecificSaml2PostConfiguration(pendingReq, "pvp_postbinding_template.html", authConfig.getConfigurationRootDirectory()); final HttpPostEncoderWithOwnTemplate encoder = new HttpPostEncoderWithOwnTemplate(guiConfig, guiBuilder, VelocityProvider.getClassPathVelocityEngine()); // set OpenSAML2 process parameter into binding context dao final HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter(resp, true); final BasicSAMLMessageContext context = new BasicSAMLMessageContext<>(); final SingleSignOnService service = new SingleSignOnServiceBuilder().buildObject(); service.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); service.setLocation(targetLocation); context.setOutboundSAMLMessageSigningCredential(credentials); context.setPeerEntityEndpoint(service); // context.setOutboundMessage(authReq); context.setOutboundSAMLMessage(response); context.setOutboundMessageTransport(responseAdapter); context.setRelayState(relayState); encoder.encode(context); } catch (final Exception e) { log.warn("Can not encode SAML2 response", e); throw new SecurityException(e); } } @Override public InboundMessageInterface decode(final HttpServletRequest req, final HttpServletResponse resp, final MetadataProvider metadataProvider, final boolean isSpEndPoint, final URIComparator comparator) throws MessageDecodingException, SecurityException { final HTTPPostDecoder decode = new HTTPPostDecoder(new BasicParserPool()); final BasicSAMLMessageContext messageContext = new BasicSAMLMessageContext<>(); messageContext.setInboundMessageTransport(new HttpServletRequestAdapter(req)); // set metadata descriptor type if (isSpEndPoint) { messageContext.setPeerEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); decode.setURIComparator(comparator); } else { messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); decode.setURIComparator(comparator); } messageContext.setMetadataProvider(metadataProvider); // set security policy context final BasicSecurityPolicy policy = new BasicSecurityPolicy(); policy.getPolicyRules() .add(new PvpSignedRequestPolicyRule(metadataProvider, TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider), messageContext.getPeerEntityRole())); final SecurityPolicyResolver secResolver = new StaticSecurityPolicyResolver(policy); messageContext.setSecurityPolicyResolver(secResolver); decode.decode(messageContext); InboundMessage msg = null; if (messageContext.getInboundMessage() instanceof RequestAbstractType) { final RequestAbstractType inboundMessage = (RequestAbstractType) messageContext.getInboundMessage(); msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); msg.setEntityID(inboundMessage.getIssuer().getValue()); } else if (messageContext.getInboundMessage() instanceof StatusResponseType) { final StatusResponseType inboundMessage = (StatusResponseType) messageContext.getInboundMessage(); msg = new PvpSProfileResponse(inboundMessage); msg.setEntityID(inboundMessage.getIssuer().getValue()); } else { // create empty container if request type is unknown msg = new InboundMessage(); } if (messageContext.getPeerEntityMetadata() != null) { msg.setEntityID(messageContext.getPeerEntityMetadata().getEntityID()); } else { if (StringUtils.isEmpty(msg.getEntityID())) { log.info( "No Metadata found for OA with EntityID " + messageContext.getInboundMessageIssuer()); } } msg.setVerified(true); msg.setRelayState(messageContext.getRelayState()); return msg; } @Override public boolean handleDecode(final String action, final HttpServletRequest req) { return req.getMethod().equals("POST") && action.equals(PvpConstants.POST); } @Override public String getSaml2BindingName() { return SAMLConstants.SAML2_POST_BINDING_URI; } }