/* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 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: * http://www.osor.eu/eupl/ * * 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.egovernment.moa.id.auth.modules.eidas.tasks; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.BooleanUtils; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.SingleSignOnService; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.google.common.net.MediaType; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.gui.velocity.VelocityProvider; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.auth.modules.eidas.Constants; import at.gv.egovernment.moa.id.auth.modules.eidas.engine.MOAeIDASChainingMetadataProvider; import at.gv.egovernment.moa.id.auth.modules.eidas.exceptions.EIDASEngineException; import at.gv.egovernment.moa.id.auth.modules.eidas.utils.SAMLEngineUtils; import at.gv.egovernment.moa.id.commons.MOAIDAuthConstants; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; import at.gv.egovernment.moa.id.commons.api.data.CPEPS; import at.gv.egovernment.moa.id.commons.api.data.StorkAttribute; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; import eu.eidas.auth.commons.EidasStringUtil; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeDefinition.Builder; import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; import eu.eidas.auth.commons.protocol.IRequestMessage; import eu.eidas.auth.commons.protocol.eidas.LevelOfAssurance; import eu.eidas.auth.commons.protocol.eidas.LevelOfAssuranceComparison; import eu.eidas.auth.commons.protocol.eidas.SpType; import eu.eidas.auth.commons.protocol.eidas.impl.EidasAuthenticationRequest; import eu.eidas.auth.engine.ProtocolEngineI; import eu.eidas.engine.exceptions.EIDASSAMLEngineException; /** * @author tlenz * */ @Component("GenerateAuthnRequestTask") public class GenerateAuthnRequestTask extends AbstractAuthServletTask { @Autowired(required=true) MOAeIDASChainingMetadataProvider eIDASMetadataProvider; /* (non-Javadoc) * @see at.gv.egovernment.moa.id.process.springweb.MoaIdTask#execute(at.gv.egovernment.moa.id.process.api.ExecutionContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try{ //get service-provider configuration IOAAuthParameters oaConfig = pendingReq.getServiceProviderConfiguration(IOAAuthParameters.class); // get target and validate citizen countryCode String citizenCountryCode = (String) executionContext.get(MOAIDAuthConstants.PARAM_CCC); if (StringUtils.isEmpty(citizenCountryCode)) { // illegal state; task should not have been executed without a selected country throw new AuthenticationException("eIDAS.03", new Object[] { "" }); } CPEPS cpeps = ((AuthConfiguration)authConfig).getStorkConfig().getCPEPSWithFullName(citizenCountryCode); if(null == cpeps) { Logger.error("PEPS unknown for country: " + citizenCountryCode); throw new AuthenticationException("eIDAS.04", new Object[] {citizenCountryCode}); } Logger.debug("Found eIDaS Node/C-PEPS configuration for citizen of country: " + citizenCountryCode); // select SingleSignOnService Endpoint from eIDAS-node metadata SingleSignOnService authnReqEndpoint = null; String metadataUrl = cpeps.getPepsURL().toString().split(";")[0].trim(); try { EntityDescriptor eIDASNodeMetadata = eIDASMetadataProvider.getEntityDescriptor(metadataUrl); if (eIDASNodeMetadata != null) { SingleSignOnService ssoDescr = selectSingleSignOnServiceFromMetadata(eIDASNodeMetadata); if (ssoDescr != null) { authnReqEndpoint = ssoDescr; Logger.debug("Use destination URL:" + authnReqEndpoint.getLocation() + " from eIDAS metadata:" + metadataUrl); } else Logger.warn("eIDAS metadata for node:" + metadataUrl + " has no IDPSSODescriptor or no SingleSignOnService information."); } else Logger.warn("No eIDAS metadata for node:" + metadataUrl + " "); } catch (MetadataProviderException e) { Logger.warn("Load eIDAS metadata from node:" + metadataUrl + " FAILED with an error.", e); } // load SingleSignOnService Endpoint from configuration, if Metadata contains no information // FIXME convenience function for not standard conform metadata if (authnReqEndpoint == null) { String destination = null; String[] splitString = cpeps.getPepsURL().toString().split(";"); if (splitString.length > 1) destination = cpeps.getPepsURL().toString().split(";")[1].trim(); if (MiscUtil.isNotEmpty(destination)) { Logger.debug("Use eIDAS node destination URL:" + destination + " from configuration"); //set POST binding as default binding, if Authn. request endpoint from config is used authnReqEndpoint = SAML2Utils.createSAMLObject(SingleSignOnService.class); authnReqEndpoint.setLocation(destination); authnReqEndpoint.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); } else { Logger.error("No eIDAS-node destination URL FOUND. Request eIDAS node not possible."); throw new MOAIDException("eIDAS.02", new Object[]{"No eIDAS-node Destination-URL FOUND"}); } } //TODO: switch to entityID revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_PEPS_SELECTED, metadataUrl); // assemble requested attributes Collection attributesFromConfig = oaConfig.getRequestedSTORKAttributes(); // - prepare attribute list ProtocolEngineI engine = SAMLEngineUtils.createSAMLEngine(eIDASMetadataProvider); // - fill container List> reqAttrList = new ArrayList>(); for (StorkAttribute current : attributesFromConfig) { AttributeDefinition newAttribute = SAMLEngineUtils.getMapOfAllAvailableAttributes().get(current.getName()); if (newAttribute == null) { Logger.warn("eIDAS attribute with friendlyName:" + current.getName() + " is not supported."); } else { boolean globallyMandatory = false; for (StorkAttribute currentGlobalAttribute : ((AuthConfiguration)authConfig).getStorkConfig().getStorkAttributes()) if (current.getName().equals(currentGlobalAttribute.getName())) { globallyMandatory = BooleanUtils.isTrue(currentGlobalAttribute.getMandatory()); break; } Builder attrBuilder = AttributeDefinition.builder(newAttribute).required(current.getMandatory() || globallyMandatory); reqAttrList.add(attrBuilder.build()); } } //request if (reqAttrList.isEmpty()) { Logger.info("No attributes requested by OA:" + pendingReq.getServiceProviderConfiguration().getUniqueIdentifier() + " --> Request attr:" + Constants.eIDAS_ATTR_PERSONALIDENTIFIER + " by default"); AttributeDefinition newAttribute = SAMLEngineUtils.getMapOfAllAvailableAttributes().get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); Builder attrBuilder = AttributeDefinition.builder(newAttribute).required(true); reqAttrList.add(attrBuilder.build()); } //build requested attribute set ImmutableAttributeMap reqAttrMap = new ImmutableAttributeMap.Builder().putAll(reqAttrList).build(); //build eIDAS AuthnRequest EidasAuthenticationRequest.Builder authnRequestBuilder = new EidasAuthenticationRequest.Builder(); authnRequestBuilder.id(eu.eidas.auth.engine.xml.opensaml.SAMLEngineUtils.generateNCName()); authnRequestBuilder.providerName(pendingReq.getAuthURL()); String issur = pendingReq.getAuthURL() + Constants.eIDAS_HTTP_ENDPOINT_METADATA; authnRequestBuilder.issuer(issur); authnRequestBuilder.destination(authnReqEndpoint.getLocation()); authnRequestBuilder.nameIdFormat(Constants.eIDAS_REQ_NAMEID_FORMAT); //set minimum required eIDAS LoA from OA config String LoA = oaConfig.getQaaLevel(); if (MiscUtil.isNotEmpty(LoA)) authnRequestBuilder.levelOfAssurance(LevelOfAssurance.fromString(oaConfig.getQaaLevel())); else authnRequestBuilder.levelOfAssurance(LevelOfAssurance.HIGH); authnRequestBuilder.levelOfAssuranceComparison(LevelOfAssuranceComparison.MINIMUM); //set correct SPType for this online application if (oaConfig.hasBaseIdTransferRestriction()) authnRequestBuilder.spType(SpType.PRIVATE.getValue()); else authnRequestBuilder.spType(SpType.PUBLIC.getValue()); //set service provider (eIDAS node) countryCode authnRequestBuilder.serviceProviderCountryCode( authConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, "AT")); //set citizen country code for foreign uses authnRequestBuilder.citizenCountryCode(cpeps.getCountryCode()); //add requested attributes authnRequestBuilder.requestedAttributes(reqAttrMap); IRequestMessage authnRequest = engine.generateRequestMessage(authnRequestBuilder.build(), issur); //encode AuthnRequest byte[] token = authnRequest.getMessageBytes(); String SAMLRequest = EidasStringUtil.encodeToBase64(token); if (SAMLConstants.SAML2_POST_BINDING_URI.equals(authnReqEndpoint.getBinding())) buildPostBindingRequest(pendingReq, authnReqEndpoint, SAMLRequest, authnRequest, response); //TODO: redirect Binding is not completely implemented //else if (SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(authnReqEndpoint.getBinding())) //buildRedirecttBindingRequest(pendingReq, authnReqEndpoint, token, authnRequest, response); else { Logger.error("eIDAS-node use an unsupported binding (" + authnReqEndpoint.getBinding() + "). Request eIDAS node not possible."); throw new MOAIDException("eIDAS.02", new Object[]{"eIDAS-node use an unsupported binding"}); } }catch (EIDASSAMLEngineException e){ throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", new EIDASEngineException("eIDAS.00", new Object[]{e.getMessage()}, e)); } catch (MOAIDException e) { throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", e); } catch (Exception e) { Logger.error("eIDAS AuthnRequest generation FAILED.", e); throw new TaskExecutionException(pendingReq, e.getMessage(), e); } } /** * Encode the eIDAS request with POST binding * * @param pendingReq * @param authnReqEndpoint * @param SAMLRequest * @param authnRequest * @param response * @throws MOAIDException */ private void buildPostBindingRequest(IRequest pendingReq, SingleSignOnService authnReqEndpoint, String SAMLRequest, IRequestMessage authnRequest, HttpServletResponse response) throws MOAIDException { //send try { VelocityEngine velocityEngine = VelocityProvider.getClassPathVelocityEngine(); Template template = velocityEngine.getTemplate("/resources/templates/eidas_postbinding_template.vm"); VelocityContext context = new VelocityContext(); String actionType = "SAMLRequest"; context.put(actionType, SAMLRequest); context.put("RelayState", pendingReq.getPendingRequestId()); context.put("action", authnReqEndpoint.getLocation()); Logger.debug("Using SingleSignOnService url as action: " + authnReqEndpoint.getLocation()); Logger.debug("Encoded " + actionType + " original: " + SAMLRequest); Logger.trace("Starting template merge"); StringWriter writer = new StringWriter(); Logger.trace("Doing template merge"); template.merge(context, writer); Logger.trace("Template merge done"); Logger.trace("Sending html content: " + writer.getBuffer().toString()); byte[] content = writer.getBuffer().toString().getBytes("UTF-8"); response.setContentType(MediaType.HTML_UTF_8.toString()); response.setContentLength(content.length); response.getOutputStream().write(content); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_PEPS_REQUESTED, authnRequest.getRequest().getId()); } catch (Exception e) { Logger.error("Velocity general error: " + e.getMessage()); throw new MOAIDException("eIDAS.02", new Object[]{e.getMessage()}, e); } } /** * Select a SingleSignOnService endPoint from eIDAS node metadata. * This endPoint receives the Authn. request * * @param idpEntity * @return */ private SingleSignOnService selectSingleSignOnServiceFromMetadata(EntityDescriptor idpEntity) { //select SingleSignOn Service endpoint from IDP metadata SingleSignOnService endpoint = null; if (idpEntity.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) == null) { return null; } for (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; //TODO: redirect Binding is not completely implemented // use Redirect binding as backup // else if ( sss.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI) // && endpoint == null ) // endpoint = sss; } return endpoint; } }