diff options
Diffstat (limited to 'eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks')
3 files changed, 820 insertions, 0 deletions
diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java new file mode 100644 index 00000000..8f49d57d --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java @@ -0,0 +1,406 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, 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 "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import at.asitplus.eidas.specific.connector.MsConnectorEventCodes; +import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.szr.SzrClient; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; +import at.gv.e_government.reference.namespace.persondata._20020228.AlternativeNameType; +import at.gv.e_government.reference.namespace.persondata._20020228.PersonNameType; +import at.gv.e_government.reference.namespace.persondata._20020228.PhysicalPersonType; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.auth.builder.BpkBuilder; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; +import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.AttributeValue; +import eu.eidas.auth.commons.light.ILightResponse; +import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; +import szrservices.IdentityLinkType; +import szrservices.PersonInfoType; +import szrservices.TravelDocumentType; + +/** + * Task that creates the IdentityLink for an eIDAS authenticated person. + * + * @author tlenz + * + */ +@Component("CreateIdentityLinkTask") +public class CreateIdentityLinkTask extends AbstractAuthServletTask { + private static final Logger log = LoggerFactory.getLogger(CreateIdentityLinkTask.class); + + @Autowired + private IConfiguration basicConfig; + @Autowired + private SzrClient szrClient; + @Autowired + private ICcSpecificEidProcessingService eidPostProcessor; + + /* + * (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 { + final AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); + final ILightResponse eidasResponse = authProcessData.getGenericDataFromSession( + Constants.DATA_FULL_EIDAS_RESPONSE, ILightResponse.class); + + final Map<String, Object> simpleAttrMap = convertEidasAttrToSimpleMap(eidasResponse.getAttributes() + .getAttributeMap()); + + IIdentityLink identityLink = null; + String bpk = null; + + // post-process eIDAS attributes + final ErnbEidData eidData = eidPostProcessor.postProcess(simpleAttrMap); + + // write MDS into technical log and revision log + writeMdsLogInformation(eidData); + + // connect SZR-Gateway + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USEDUMMY, false)) { + log.warn("SZR-Dummy IS ACTIVE! IdentityLink is NOT VALID!!!!"); + // create fake IdL + // - fetch IdL template from resources + final InputStream s = CreateIdentityLinkTask.class.getResourceAsStream( + "/resources/xmldata/fakeIdL_IdL_template.xml"); + final Element idlTemplate = DomUtils.parseXmlValidating(s); + + identityLink = new SimpleIdentityLinkAssertionParser(idlTemplate).parseIdentityLink(); + + // replace data + final Element idlassertion = identityLink.getSamlAssertion(); + + // - set fake baseID; + final Node prIdentification = XPathUtils.selectSingleNode(idlassertion, + SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH); + prIdentification.getFirstChild().setNodeValue(eidData.getPseudonym()); + + // - set last name + final Node prFamilyName = XPathUtils.selectSingleNode(idlassertion, + SimpleIdentityLinkAssertionParser.PERSON_FAMILY_NAME_XPATH); + prFamilyName.getFirstChild().setNodeValue(eidData.getFamilyName()); + + // - set first name + final Node prGivenName = XPathUtils.selectSingleNode(idlassertion, + SimpleIdentityLinkAssertionParser.PERSON_GIVEN_NAME_XPATH); + prGivenName.getFirstChild().setNodeValue(eidData.getGivenName()); + + // - set date of birth + final Node prDateOfBirth = XPathUtils.selectSingleNode(idlassertion, + SimpleIdentityLinkAssertionParser.PERSON_DATE_OF_BIRTH_XPATH); + + prDateOfBirth.getFirstChild().setNodeValue(eidData.getFormatedDateOfBirth()); + + identityLink = new SimpleIdentityLinkAssertionParser(idlassertion).parseIdentityLink(); + + new BpkBuilder(); + final Pair<String, String> bpkCalc = BpkBuilder.generateAreaSpecificPersonIdentifier( + identityLink.getIdentificationValue(), + identityLink.getIdentificationType(), + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()); + bpk = bpkCalc.getFirst(); + + } else { + // contact SZR Gateway + log.debug("Starting connecting SZR Gateway"); + final PersonInfoType personInfo = new PersonInfoType(); + final PersonNameType personName = new PersonNameType(); + final PhysicalPersonType naturalPerson = new PhysicalPersonType(); + final TravelDocumentType eDocument = new TravelDocumentType(); + + naturalPerson.setName(personName); + personInfo.setPerson(naturalPerson); + personInfo.setTravelDocument(eDocument); + + // person information + personName.setFamilyName(eidData.getFamilyName()); + personName.setGivenName(eidData.getGivenName()); + naturalPerson.setDateOfBirth(eidData.getFormatedDateOfBirth()); + eDocument.setIssuingCountry(eidData.getCitizenCountryCode()); + eDocument.setDocumentNumber(eidData.getPseudonym()); + + // eID document information + eDocument.setDocumentType(basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_EDOCUMENTTYPE, + Constants.SZR_CONSTANTS_DEFAULT_DOCUMENT_TYPE)); + + // set PlaceOfBirth if available + if (eidData.getPlaceOfBirth() != null) { + log.trace("Find 'PlaceOfBirth' attribute: " + eidData.getPlaceOfBirth()); + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETPLACEOFBIRTHIFAVAILABLE, + true)) { + naturalPerson.setPlaceOfBirth(eidData.getPlaceOfBirth()); + log.trace("Adding 'PlaceOfBirth' to ERnB request ... "); + + } + } + + // set BirthName if available + if (eidData.getBirthName() != null) { + log.trace("Find 'BirthName' attribute: " + eidData.getBirthName()); + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETBIRTHNAMEIFAVAILABLE, + true)) { + final AlternativeNameType alternativeName = new AlternativeNameType(); + naturalPerson.setAlternativeName(alternativeName); + alternativeName.setFamilyName(eidData.getBirthName()); + log.trace("Adding 'BirthName' to ERnB request ... "); + + } + } + + final IdentityLinkType result = szrClient.getIdentityLinkInRawMode(personInfo); + + final Element idlFromSzr = (Element) result.getAssertion(); + identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink(); + + // write ERnB inputdata into revisionlog + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE, false)) { + revisionsLogger.logEvent(pendingReq, + MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, + (String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)); + revisionsLogger.logEvent(pendingReq, + MsConnectorEventCodes.SZR_ERNB_EIDAS_ERNB_ID, eidData.getPseudonym()); + + } + + // get bPK from SZR + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true)) { + bpk = szrClient.getBpk( + personInfo, + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), + basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, + "no VKZ defined")); + + } else { + log.debug("Calculating bPK from baseId ... "); + new BpkBuilder(); + final Pair<String, String> bpkCalc = BpkBuilder.generateAreaSpecificPersonIdentifier( + identityLink.getIdentificationValue(), + identityLink.getIdentificationType(), + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()); + bpk = bpkCalc.getFirst(); + + } + + } + + if (identityLink == null) { + log.error("ERnB did not return an identity link."); + throw new SzrCommunicationException("ernb.00", null); + + } + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_IDL_RECEIVED, + identityLink.getSamlAssertion().getAttribute(SimpleIdentityLinkAssertionParser.ASSERTIONID)); + + if (bpk == null) { + log.error("ERnB did not return a bPK for target: " + pendingReq.getServiceProviderConfiguration() + .getAreaSpecificTargetIdentifier()); + throw new SzrCommunicationException("ernb.01", null); + + } + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_BPK_RECEIVED); + + log.debug("ERnB communication was successfull"); + + authProcessData.setForeigner(true); + authProcessData.setIdentityLink(identityLink); + authProcessData.setGenericDataToSession( + PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, + EidasResponseUtils.parseEidasPersonalIdentifier((String) simpleAttrMap.get( + Constants.eIDAS_ATTR_PERSONALIDENTIFIER)).getFirst()); + + // set bPK and bPKType into auth session + authProcessData.setGenericDataToSession( + PvpAttributeDefinitions.BPK_NAME, + extendBpkByPrefix( + bpk, + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier())); + authProcessData.setGenericDataToSession( + PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()); + + // store pending-request + requestStoreage.storePendingRequest(pendingReq); + + } catch (final EidasAttributeException e) { + throw new TaskExecutionException(pendingReq, "Minimum required eIDAS attributeset not found.", e); + + } catch (final EaafException e) { + throw new TaskExecutionException(pendingReq, "IdentityLink generation for foreign person FAILED.", e); + + } catch (final Exception e) { + log.error("IdentityLink generation for foreign person FAILED.", e); + throw new TaskExecutionException(pendingReq, "IdentityLink generation for foreign person FAILED.", e); + + } + } + + private String extendBpkByPrefix(String bpk, String type) { + String bpkType = null; + + if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) { + bpkType = type.substring(EaafConstants.URN_PREFIX_WBPK.length()); + } else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) { + bpkType = type.substring(EaafConstants.URN_PREFIX_CDID.length()); + } else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { + bpkType = type.substring(EaafConstants.URN_PREFIX_EIDAS.length()); + } + + if (bpkType != null) { + log.trace("Authenticate user with bPK/wbPK " + bpk + " and Type=" + bpkType); + return bpkType + ":" + bpk; + + } else { + log.warn("Service Provider Target with: " + type + " is NOT supported. Set bPK as it is ..."); + return bpk; + + } + + } + + private Map<String, Object> convertEidasAttrToSimpleMap( + ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> attributeMap) { + final Map<String, Object> result = new HashMap<>(); + + for (final AttributeDefinition<?> el : attributeMap.keySet()) { + + final Class parameterizedType = el.getParameterizedType(); + if (DateTime.class.equals(parameterizedType)) { + final DateTime attribute = EidasResponseUtils.translateDateAttribute(el, attributeMap.get(el) + .asList()); + if (attribute != null) { + result.put(el.getFriendlyName(), attribute); + log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + attribute.toString()); + + } else { + log.info("Ignore empty 'DateTime' attribute"); + } + + } else if (PostalAddress.class.equals(parameterizedType)) { + final PostalAddress addressAttribute = EidasResponseUtils.translateAddressAttribute(el, attributeMap + .get(el).asList()); + if (addressAttribute != null) { + result.put(el.getFriendlyName(), addressAttribute); + log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + addressAttribute.toString()); + + } else { + log.info("Ignore empty 'PostalAddress' attribute"); + } + + } else { + final List<String> natPersonIdObj = EidasResponseUtils.translateStringListAttribute(el, attributeMap + .get(el).asList()); + final String stringAttr = natPersonIdObj.get(0); + if (StringUtils.isNotEmpty(stringAttr)) { + result.put(el.getFriendlyName(), stringAttr); + log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + stringAttr); + + } else { + log.info("Ignore empty 'String' attribute"); + } + + } + } + + log.debug("Receive #" + result.size() + " attributes with names: " + result.keySet().toString()); + + return result; + } + + private void writeMdsLogInformation(ErnbEidData eidData) { + // log MDS and country code into technical log + if (basicConfig.getBasicConfigurationBoolean( + MsEidasNodeConstants.PROP_CONFIG_TECHNICALLOG_WRITE_MDS_INTO_TECH_LOG, false)) { + log.info("eIDAS Auth. for user: " + + eidData.getGivenName() + " " + + eidData.getFamilyName() + " " + + eidData.getFormatedDateOfBirth() + " " + + "from " + eidData.getCitizenCountryCode()); + } + + // log MDS and country code into revision log + if (basicConfig.getBasicConfigurationBoolean( + MsEidasNodeConstants.PROP_CONFIG_REVISIONLOG_WRITE_MDS_INTO_REVISION_LOG, false)) { + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_MDSDATA, + "{" + eidData.getGivenName() + "," + + eidData.getFamilyName() + "," + + eidData.getFormatedDateOfBirth() + "," + + eidData.getCitizenCountryCode() + "}"); + } + + } + +} diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java new file mode 100644 index 00000000..da849c8f --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java @@ -0,0 +1,288 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, 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 "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import java.util.UUID; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import at.asitplus.eidas.specific.connector.MsConnectorEventCodes; +import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; +import at.asitplus.eidas.specific.connector.gui.StaticGuiBuilderConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import eu.eidas.auth.commons.EidasParameterKeys; +import eu.eidas.auth.commons.light.ILightRequest; +import eu.eidas.auth.commons.light.impl.LightRequest; +import eu.eidas.auth.commons.tx.BinaryLightToken; +import eu.eidas.specificcommunication.BinaryLightTokenHelper; +import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; +import eu.eidas.specificcommunication.exception.SpecificCommunicationException; +import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; + +/** + * Authentication-process task that generates the Authn. Request to eIDAS Node. + * + * @author tlenz + * + */ +@Component("ConnecteIDASNodeTask") +public class GenerateAuthnRequestTask extends AbstractAuthServletTask { + private static final Logger log = LoggerFactory.getLogger(GenerateAuthnRequestTask.class); + + @Autowired + IConfiguration basicConfig; + @Autowired + ApplicationContext context; + @Autowired + ITransactionStorage transactionStore; + @Autowired + ISpringMvcGuiFormBuilder guiBuilder; + @Autowired + ICcSpecificEidProcessingService ccSpecificProcessing; + + @Override + public void execute(ExecutionContext executionContext, + HttpServletRequest request, HttpServletResponse response) + throws TaskExecutionException { + + try { + // get service-provider configuration + final ISpConfiguration spConfig = pendingReq.getServiceProviderConfiguration(); + + // get target, environment and validate citizen countryCode + final String citizenCountryCode = (String) executionContext.get( + MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); + final String environment = (String) executionContext.get( + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT); + + if (StringUtils.isEmpty(citizenCountryCode)) { + // illegal state; task should not have been executed without a selected country + throw new EidasSAuthenticationException("eidas.03", new Object[] { "" }); + + } + + // TODO: maybe add countryCode validation before request ref. impl. eIDAS node + log.info("Request eIDAS auth. for citizen of country: " + citizenCountryCode); + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.COUNTRY_SELECTED, citizenCountryCode); + + // build eIDAS AuthnRequest + final LightRequest.Builder authnRequestBuilder = LightRequest.builder(); + authnRequestBuilder.id(UUID.randomUUID().toString()); + + final String issur = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID); + if (StringUtils.isEmpty(issur)) { + log.error("Found NO 'eIDAS node issuer' in configuration. Authentication NOT possible!"); + throw new EaafConfigurationException("config.27", + new Object[] { "Application config containts NO " + Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID }); + + } + authnRequestBuilder.issuer(issur); + + // TODO: set matching mode if eIDAS ref. impl. support this method + + // TODO: update if eIDAS ref. impl. supports exact matching for non-notified LoA + // schemes + String loa = EaafConstants.EIDAS_LOA_HIGH; + if (spConfig.getRequiredLoA() != null) { + if (spConfig.getRequiredLoA().isEmpty()) { + log.info("No eIDAS LoA requested. Use LoA HIGH as default"); + } else { + if (spConfig.getRequiredLoA().size() > 1) { + log.info( + "Currently only ONE requested LoA is supported for service provider. Use first one ... "); + } + + loa = spConfig.getRequiredLoA().get(0); + + } + } + + log.debug("Request eIdAS node with LoA: " + loa); + authnRequestBuilder.levelOfAssurance(loa); + + // set nameIDFormat + authnRequestBuilder.nameIdFormat(Constants.eIDAS_REQ_NAMEID_FORMAT); + + // set citizen country code for foreign uses + authnRequestBuilder.citizenCountryCode(citizenCountryCode); + + // set relay state + /* + * TODO: SecureToken PendingRequestId generates a validation exception in + * eIDASNode because eIDASNode implements limit on size for RelayState + * (80characaters) + */ + // authnRequestBuilder.relayState(pendingReq.getPendingRequestId()); + + // Add country-specific informations into eIDAS request + ccSpecificProcessing.preProcess(citizenCountryCode, pendingReq, authnRequestBuilder); + + // build request + final LightRequest lightAuthnReq = authnRequestBuilder.build(); + + // put request into Hazelcast cache + final BinaryLightToken token = putRequestInCommunicationCache(lightAuthnReq); + final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); + + // Workaround, because eIDAS node ref. impl. does not return relayState + if (basicConfig.getBasicConfigurationBoolean( + Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, + false)) { + log.trace("Put lightRequestId into transactionstore as session-handling backup"); + transactionStore.put(lightAuthnReq.getId(), pendingReq.getPendingRequestId(), -1); + + } + + // select forward URL regarding the selected environment + String forwardUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL); + if (StringUtils.isNotEmpty(environment)) { + forwardUrl = selectedForwardUrlForEnvironment(environment); + } + + if (StringUtils.isEmpty(forwardUrl)) { + log.warn("NO ForwardURL defined in configuration. Can NOT forward to eIDAS node! Process stops"); + throw new EaafConfigurationException("config.08", new Object[] { + environment == null ? Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + : Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + "." + environment + }); + + } + log.debug("ForwardURL: " + forwardUrl + " selected to forward eIDAS request"); + + if (basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD, + Constants.FORWARD_METHOD_GET).equals(Constants.FORWARD_METHOD_GET)) { + + log.debug("Use http-redirect for eIDAS node forwarding ... "); + // send redirect + final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(forwardUrl); + redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); + response.sendRedirect(redirectUrl.build().encode().toString()); + + } else { + log.debug("Use http-post for eIDAS node forwarding ... "); + final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( + basicConfig, + pendingReq, + Constants.TEMPLATE_POST_FORWARD_NAME, + null, + resourceLoader); + + config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardUrl); + config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_NAME, + EidasParameterKeys.TOKEN.toString()); + config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_VALUE, + tokenBase64); + + guiBuilder.build(request, response, config, "Forward to eIDASNode form"); + + } + + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.EIDAS_NODE_CONNECTED, lightAuthnReq.getId()); + + } catch (final EidasSAuthenticationException e) { + throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", e); + + } catch (final Exception e) { + log.warn("eIDAS AuthnRequest generation FAILED.", e); + throw new TaskExecutionException(pendingReq, e.getMessage(), e); + + } + + } + + /** + * Select a forward URL from configuration for a specific environment <br> + * <br> + * <b>Info: </b> This method is needed, because eIDAS Ref. Impl only supports + * one countrycode on each instance. In consequence, more than one eIDAS Ref. + * Impl nodes are required to support producation, testing, or QS stages for one + * country by using one ms-specific eIDAS connector + * + * @param environment Environment selector from CountrySlection page + * @return + */ + private String selectedForwardUrlForEnvironment(String environment) { + log.trace("Starting endpoint selection process for environment: " + environment + " ... "); + if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_PRODUCTION)) { + return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL); + } else if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS)) { + return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS); + } else if (environment.equalsIgnoreCase( + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING)) { + return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING); + } else if (environment.equalsIgnoreCase( + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT)) { + return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT); + } + + log.info("Environment selector: " + environment + " is not supported"); + return null; + + } + + private BinaryLightToken putRequestInCommunicationCache(ILightRequest lightRequest) + throws ServletException { + final BinaryLightToken binaryLightToken; + try { + final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = + (SpecificCommunicationService) context.getBean( + SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); + + binaryLightToken = springManagedSpecificConnectorCommunicationService.putRequest(lightRequest); + + } catch (final SpecificCommunicationException e) { + log.error("Unable to process specific request"); + throw new ServletException(e); + + } + + return binaryLightToken; + } + +} diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java new file mode 100644 index 00000000..60049323 --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java @@ -0,0 +1,126 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, 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 "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.connector.MsConnectorEventCodes; +import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.validator.EidasResponseValidator; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import eu.eidas.auth.commons.light.ILightResponse; + +@Component("ReceiveResponseFromeIDASNodeTask") +public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { + private static final Logger log = LoggerFactory.getLogger(ReceiveAuthnResponseTask.class); + + @Autowired + private IConfiguration basicConfig; + @Autowired + private EidasAttributeRegistry attrRegistry; + + @Override + public void execute(ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) throws TaskExecutionException { + try { + final ILightResponse eidasResponse = (ILightResponse) request.getAttribute( + Constants.DATA_FULL_EIDAS_RESPONSE); + if (eidasResponse == null) { + log.warn("NO eIDAS response-message found."); + throw new EidasSAuthenticationException("eidas.01", null); + + } + + log.debug("Receive eIDAS response with RespId:" + eidasResponse.getId() + " for ReqId:" + eidasResponse + .getInResponseToId()); + log.trace("Full eIDAS-Resp: " + eidasResponse.toString()); + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse + .getId()); + + // check response StatusCode + if (!eidasResponse.getStatus().getStatusCode().equals(Constants.SUCCESS_URI)) { + log.info("Receice eIDAS Response with StatusCode:" + eidasResponse.getStatus().getStatusCode() + + " Subcode:" + eidasResponse.getStatus().getSubStatusCode() + " Msg:" + eidasResponse.getStatus() + .getStatusMessage()); + throw new EidasSAuthenticationException("eidas.02", new Object[] { eidasResponse.getStatus() + .getStatusCode(), eidasResponse.getStatus().getStatusMessage() }); + + } + + // extract all Attributes from response + + // ********************************************************** + // ******* MS-specificresponse validation ********** + // ********************************************************** + final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, + "AT"); + final String citizenCountryCode = (String) executionContext.get( + MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); + EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, + attrRegistry); + + // ********************************************************** + // ******* Store resonse infos into session object ********** + // ********************************************************** + + // update MOA-Session data with received information + log.debug("Store eIDAS response information into pending-request."); + final AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); + authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); + authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); + + // store MOA-session to database + requestStoreage.storePendingRequest(pendingReq); + + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); + + } catch (final EaafException e) { + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); + throw new TaskExecutionException(pendingReq, "eIDAS Response processing FAILED.", e); + + } catch (final Exception e) { + log.warn("eIDAS Response processing FAILED.", e); + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); + throw new TaskExecutionException(pendingReq, e.getMessage(), + new EidasSAuthenticationException("eidas.05", new Object[] { e.getMessage() }, e)); + + } + + } + +} |