/* * 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.sp.impl; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.opensaml.messaging.encoder.MessageEncodingException; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.AuthnContextClassRef; import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration; import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.Extensions; import org.opensaml.saml.saml2.core.Issuer; import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.NameIDPolicy; import org.opensaml.saml.saml2.core.NameIDType; import org.opensaml.saml.saml2.core.RequestedAuthnContext; import org.opensaml.saml.saml2.core.RequesterID; import org.opensaml.saml.saml2.core.Scoping; import org.opensaml.saml.saml2.core.Subject; import org.opensaml.saml.saml2.core.SubjectConfirmation; import org.opensaml.saml.saml2.core.SubjectConfirmationData; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.saml2.metadata.SingleSignOnService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; 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.exception.Pvp2Exception; 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.builder.reqattr.EaafRequestExtensionBuilder; import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import at.gv.egiz.eaaf.modules.pvp2.sp.api.IPvpAuthnRequestBuilderConfiguruation; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnRequestBuildException; import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy; /** * PVP2 S-Profil Authentication-Request builder-implementation. * * @author tlenz * */ public class PvpAuthnRequestBuilder { private static final Logger log = LoggerFactory.getLogger(PvpAuthnRequestBuilder.class); @Autowired(required = true) ApplicationContext springContext; /** * Build a PVP2.x specific authentication request * * @param pendingReq Currently processed pendingRequest * @param config AuthnRequest builder configuration, never null * @param httpResp http response object * @throws NoSuchAlgorithmException In case of error * @throws SecurityException In case of error * @throws Pvp2Exception In case of error * @throws MessageEncodingException In case of error */ public void buildAuthnRequest(final IRequest pendingReq, final IPvpAuthnRequestBuilderConfiguruation config, final HttpServletResponse httpResp) throws NoSuchAlgorithmException, MessageEncodingException, Pvp2Exception, SecurityException { buildAuthnRequest(pendingReq, config, pendingReq.getPendingRequestId(), httpResp); } /** * Build a PVP2.x specific authentication request * * @param pendingReq Currently processed pendingRequest * @param config AuthnRequest builder configuration, never null * @param relayState RelayState that should used for communication * @param httpResp http response object * @throws NoSuchAlgorithmException In case of error * @throws SecurityException In case of error * @throws Pvp2Exception In case of error * @throws MessageEncodingException In case of error */ public void buildAuthnRequest(final IRequest pendingReq, final IPvpAuthnRequestBuilderConfiguruation config, String relayState, final HttpServletResponse httpResp) throws NoSuchAlgorithmException, MessageEncodingException, Pvp2Exception, SecurityException { // get IDP Entity element from config final EntityDescriptor idpEntity = config.getIdpEntityDescriptor(); final AuthnRequest authReq = Saml2Utils.createSamlObject(AuthnRequest.class); // select SingleSignOn Service endpoint from IDP metadata SingleSignOnService endpoint = null; for (final SingleSignOnService sss : idpEntity.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) .getSingleSignOnServices()) { // use POST binding as default if it exists if (sss.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { endpoint = sss; } else if (sss.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI) && endpoint == null) { endpoint = sss; } } if (endpoint == null) { log.warn("Building AuthnRequest FAILED: > Requested IDP " + idpEntity.getEntityID() + " does not support POST or Redirect Binding."); throw new AuthnRequestBuildException("sp.pvp2.00", new Object[] { config.getSpNameForLogging(), idpEntity.getEntityID() }); } else { authReq.setDestination(endpoint.getLocation()); } // set basic AuthnRequest information final String reqID = config.getRequestID(); if (StringUtils.isNotEmpty(reqID)) { authReq.setID(reqID); } else { final SecureRandomIdentifierGenerationStrategy gen = new SecureRandomIdentifierGenerationStrategy(); authReq.setID(gen.generateIdentifier()); } authReq.setIssueInstant(Instant.now()); // set isPassive flag if (config.isPassivRequest() == null) { authReq.setIsPassive(false); } else { authReq.setIsPassive(config.isPassivRequest()); } // set EntityID of the service provider final Issuer issuer = Saml2Utils.createSamlObject(Issuer.class); issuer.setFormat(NameIDType.ENTITY); issuer.setValue(config.getSpEntityID()); authReq.setIssuer(issuer); // set AssertionConsumerService ID if (config.getAssertionConsumerServiceId() != null) { authReq.setAssertionConsumerServiceIndex(config.getAssertionConsumerServiceId()); } // set NameIDPolicy if (config.getNameIdPolicyFormat() != null) { final NameIDPolicy policy = Saml2Utils.createSamlObject(NameIDPolicy.class); policy.setAllowCreate(config.getNameIdPolicyAllowCreation()); policy.setFormat(config.getNameIdPolicyFormat()); authReq.setNameIDPolicy(policy); } // set requested QAA level if (config.getAuthnContextClassRef() != null) { final RequestedAuthnContext reqAuthContext = Saml2Utils.createSamlObject(RequestedAuthnContext.class); final AuthnContextClassRef authnClassRef = Saml2Utils.createSamlObject(AuthnContextClassRef.class); authnClassRef.setURI(config.getAuthnContextClassRef()); if (config.getAuthnContextComparison() == null) { reqAuthContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM); } else { reqAuthContext.setComparison(config.getAuthnContextComparison()); } reqAuthContext.getAuthnContextClassRefs().add(authnClassRef); authReq.setRequestedAuthnContext(reqAuthContext); } // set request Subject element if (StringUtils.isNotEmpty(config.getSubjectNameID())) { final Subject reqSubject = Saml2Utils.createSamlObject(Subject.class); final NameID subjectNameID = Saml2Utils.createSamlObject(NameID.class); subjectNameID.setValue(config.getSubjectNameID()); if (StringUtils.isNotEmpty(config.getSubjectNameIdQualifier())) { subjectNameID.setNameQualifier(config.getSubjectNameIdQualifier()); } if (StringUtils.isNotEmpty(config.getSubjectNameIdFormat())) { subjectNameID.setFormat(config.getSubjectNameIdFormat()); } else { subjectNameID.setFormat(NameIDType.TRANSIENT); } reqSubject.setNameID(subjectNameID); if (config.getSubjectConformationDate() != null) { final SubjectConfirmation subjectConformation = Saml2Utils.createSamlObject(SubjectConfirmation.class); final SubjectConfirmationData subjectConformDate = Saml2Utils.createSamlObject(SubjectConfirmationData.class); subjectConformation.setSubjectConfirmationData(subjectConformDate); reqSubject.getSubjectConfirmations().add(subjectConformation); if (config.getSubjectConformationMethode() != null) { subjectConformation.setMethod(config.getSubjectConformationMethode()); } subjectConformDate.setDOM(config.getSubjectConformationDate()); } authReq.setSubject(reqSubject); } // set ProviderName if (StringUtils.isNotEmpty(config.getProviderName())) { authReq.setProviderName(config.getProviderName()); } // set RequesterId in case of proxy mode if (StringUtils.isNotEmpty(config.getScopeRequesterId())) { final Scoping scope = Saml2Utils.createSamlObject(Scoping.class); final RequesterID requesterId = Saml2Utils.createSamlObject(RequesterID.class); requesterId.setURI(config.getScopeRequesterId()); scope.getRequesterIDs().add(requesterId); authReq.setScoping(scope); } // add optional requested attributes if (config.getRequestedAttributes() != null) { final List reqAttr = config.getRequestedAttributes(); final Extensions extenstions = new EaafRequestExtensionBuilder().buildObject(); final EaafRequestedAttributes reqAttributs = Saml2Utils.createSamlObject(EaafRequestedAttributes.class); reqAttributs.getAttributes().addAll(reqAttr); extenstions.getUnknownXMLObjects().add(reqAttributs); authReq.setExtensions(extenstions); } // select message encoder IEncoder binding = null; if (endpoint.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { binding = springContext.getBean("PvpRedirectBinding", RedirectBinding.class); } else if (endpoint.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { binding = springContext.getBean("PvpPostBinding", PostBinding.class); } else { log.warn("Binding: {} is not supported", endpoint.getBinding()); throw new AuthnRequestBuildException("sp.pvp2.00", new Object[] { config.getSpNameForLogging(), idpEntity.getEntityID() }); } // encode message binding.encodeRequest(null, httpResp, authReq, endpoint.getLocation(), relayState, config.getAuthnRequestSigningCredential(), pendingReq); } }