/******************************************************************************* * Copyright 2017 Graz University of Technology * EAAF-Core Components has been developed in a cooperation between EGIZ, * A-SIT+, 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.core.impl.idp.auth.builder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Base64Utils; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.Node; 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.PVPAttributeDefinitions; import at.gv.egiz.eaaf.core.api.idp.IAuthenticationDataBuilder; 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.auth.data.IAuthProcessDataContainer; import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException; import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EAAFParserException; import at.gv.egiz.eaaf.core.exceptions.XPathException; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.idp.AuthenticationData; import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser; import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; public abstract class AbstractAuthenticationDataBuilder implements IAuthenticationDataBuilder { private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationDataBuilder.class); protected Collection includedToGenericAuthData = null; @Autowired protected IConfiguration basicConfig; protected void generateBasicAuthData(AuthenticationData authData, IRequest pendingReq, IAuthProcessDataContainer authProcessData) throws EAAFBuilderException, EAAFConfigurationException, XPathException, DOMException, EAAFParserException { if (authProcessData.getGenericSessionDataStorage() != null && !authProcessData.getGenericSessionDataStorage().isEmpty()) includedToGenericAuthData = authProcessData.getGenericSessionDataStorage().keySet(); else includedToGenericAuthData = new ArrayList(); //#################################################### //set general authData info's authData.setAuthenticationIssuer(pendingReq.getAuthURL()); authData.setSsoSession(pendingReq.needSingleSignOnFunctionality()); authData.setBaseIDTransferRestrication(pendingReq.getServiceProviderConfiguration().hasBaseIdTransferRestriction()); //#################################################### //parse user info's from identityLink IIdentityLink idlFromPVPAttr = null; IIdentityLink identityLink = authProcessData.getIdentityLink(); if (identityLink != null) { parseBasicUserInfosFromIDL(authData, identityLink, includedToGenericAuthData); } else { // identityLink is not direct in MOASession String pvpAttrIDL = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME, String.class); //find PVP-Attr. which contains the IdentityLink if (StringUtils.isNotEmpty(pvpAttrIDL)) { log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_IDENTITY_LINK_FRIENDLY_NAME + " --> Parse basic user info's from that attribute."); InputStream idlStream = null; try { idlStream = new ByteArrayInputStream(Base64Utils.decodeFromString(pvpAttrIDL)); idlFromPVPAttr = new SimpleIdentityLinkAssertionParser(idlStream).parseIdentityLink(); parseBasicUserInfosFromIDL(authData, idlFromPVPAttr, includedToGenericAuthData); //set identitylink into AuthProcessData authProcessData.setIdentityLink(idlFromPVPAttr);; } catch (EAAFParserException e) { log.warn("Received IdentityLink is not valid", e); } catch (Exception e) { log.warn("Received IdentityLink is not valid", e); } finally { try { includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME); if (idlStream != null) idlStream.close(); } catch (IOException e) { log.warn("Close InputStream FAILED.", e); } } } //if no basic user info's are set yet, parse info's single PVP-Attributes if (StringUtils.isEmpty(authData.getFamilyName())) { log.debug("No IdentityLink found or not parseable --> Parse basic user info's from single PVP-Attributes."); authData.setFamilyName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME, String.class)); authData.setGivenName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.GIVEN_NAME_NAME, String.class)); authData.setDateOfBirth(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.BIRTHDATE_NAME, String.class)); authData.setIdentificationValue(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME, String.class)); authData.setIdentificationType(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME, String.class)); //remove corresponding keys from genericSessionData if exists includedToGenericAuthData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME); includedToGenericAuthData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME); includedToGenericAuthData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME); includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME); includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME); } } if (authData.getIdentificationType() != null && !authData.getIdentificationType().equals(EAAFConstants.URN_PREFIX_BASEID)) { log.trace("IdentificationType is not a baseID --> clear it. "); authData.setBPK(authData.getIdentificationValue()); authData.setBPKType(authData.getIdentificationType()); authData.setIdentificationValue(null); authData.setIdentificationType(null); } //#################################################### //set QAA level includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME); String currentLoA = null; if (StringUtils.isNotEmpty(authProcessData.getQAALevel())) currentLoA = authProcessData.getQAALevel(); else { currentLoA = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, String.class); if (StringUtils.isNotEmpty(currentLoA)) { log.debug("Find PVP-Attr '" + PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME + "':" + currentLoA + " --> Parse QAA-Level from that attribute."); } } if (StringUtils.isNotEmpty(currentLoA)) { if (currentLoA.startsWith(EAAFConstants.EIDAS_LOA_PREFIX)) { authData.seteIDASLoA(currentLoA); } else log.info("Only eIDAS LoAs are supported by this implementation"); } else { log.info("No QAA level found. Set to default level " + EAAFConstants.EIDAS_LOA_LOW); authData.seteIDASLoA(EAAFConstants.EIDAS_LOA_LOW); } //#################################################### //set isForeigner flag //TODO: change to new eIDAS-token attribute identifier if (authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_STORK_TOKEN_NAME) != null) { log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_STORK_TOKEN_FRIENDLY_NAME + " --> Set 'isForeigner' flag to TRUE"); authData.setForeigner(true); } else { authData.setForeigner(authProcessData.isForeigner()); } //#################################################### //set citizen country-code includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME); String pvpCCCAttr = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME, String.class); if (StringUtils.isNotEmpty(pvpCCCAttr)) { authData.setCiticenCountryCode(pvpCCCAttr); log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_ISSUING_NATION_FRIENDLY_NAME); } else { if (authData.isForeigner()) { //TODO!!!! } else { authData.setCiticenCountryCode(basicConfig.getBasicConfiguration( IConfiguration.CONFIG_PROPS_AUTH_DEFAULT_COUNTRYCODE, EAAFConstants.COUNTRYCODE_AUSTRIA)); } } //#################################################### // set bPK and IdentityLink String pvpbPKValue = getbPKValueFromPVPAttribute(authProcessData); String pvpbPKTypeAttr = getbPKTypeFromPVPAttribute(authProcessData); Pair pvpEncbPKAttr = getEncryptedbPKFromPVPAttribute(authProcessData, authData, pendingReq.getServiceProviderConfiguration()); //check if a unique ID for this citizen exists if (StringUtils.isEmpty(authData.getIdentificationValue()) && StringUtils.isEmpty(pvpbPKValue) && StringUtils.isEmpty(authData.getBPK()) && pvpEncbPKAttr == null) { log.info("Can not build authData, because moaSession include no bPK, encrypted bPK or baseID"); throw new EAAFBuilderException("builder.08", new Object[]{"No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME}, "No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME); } // baseID is in MOASesson --> calculate bPK directly if (StringUtils.isNotEmpty(authData.getIdentificationValue())) { log.debug("Citizen baseID is in MOASession --> calculate bPK from this."); Pair result = buildOAspecificbPK(pendingReq, authData); authData.setBPK(result.getFirst()); authData.setBPKType(result.getSecond()); //check if bPK already added to AuthData matches OA } else if (StringUtils.isNotEmpty(authData.getBPK()) && matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), authData.getBPKType()) ) { log.debug("Correct bPK is already included in AuthData."); //check if bPK received by PVP-Attribute matches OA } else if (StringUtils.isNotEmpty(pvpbPKValue) && matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), pvpbPKTypeAttr)) { log.debug("Receive correct bPK from PVP-Attribute"); authData.setBPK(pvpbPKValue); authData.setBPKType(pvpbPKTypeAttr); //check if decrypted bPK exists } else if (pvpEncbPKAttr != null) { log.debug("Receive bPK as encrypted bPK and decryption was possible."); authData.setBPK(pvpEncbPKAttr.getFirst()); authData.setBPKType(pvpEncbPKAttr.getSecond()); //ask SZR to get bPK } else { String notValidbPK = authData.getBPK(); String notValidbPKType = authData.getBPKType(); if (StringUtils.isEmpty(notValidbPK) && StringUtils.isEmpty(notValidbPKType)) { notValidbPK = pvpbPKValue; notValidbPKType = pvpbPKTypeAttr; if (StringUtils.isEmpty(notValidbPK) && StringUtils.isEmpty(notValidbPKType)) { log.error("No bPK in MOASession. THIS error should not occur any more."); throw new NullPointerException("No bPK in MOASession. THIS error should not occur any more."); } } Pair baseIDFromSZR = getbaseIDFromSZR(authData, notValidbPK, notValidbPKType); if (baseIDFromSZR != null) { log.info("Receive citizen baseID from SRZ. Authentication can be completed"); authData.setIdentificationValue(baseIDFromSZR.getFirst()); authData.setIdentificationType(baseIDFromSZR.getSecond()); Pair result = buildOAspecificbPK(pendingReq, authData); authData.setBPK(result.getFirst()); authData.setBPKType(result.getSecond()); } else { log.warn("Can not build authData, because moaSession include no valid bPK, encrypted bPK or baseID"); throw new EAAFBuilderException("builder.08", new Object[]{"No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME}, "No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME); } } //build IdentityLink if (authProcessData.getIdentityLink() != null) authData.setIdentityLink(buildOAspecificIdentityLink( pendingReq.getServiceProviderConfiguration(), authProcessData.getIdentityLink(), authData.getBPK(), authData.getBPKType())); else log.info("Can NOT set IdentityLink. Msg: No IdentityLink found"); } //extract a encrypted bPK from PVP attrobute protected abstract Pair getEncryptedbPKFromPVPAttribute(IAuthProcessDataContainer authProcessDataContainer, AuthenticationData authData, ISPConfiguration spConfig) throws EAAFBuilderException; //request baseId from SRZ protected abstract Pair getbaseIDFromSZR(AuthenticationData authData, String notValidbPK, String notValidbPKType); protected Pair buildOAspecificbPK(IRequest pendingReq, AuthenticationData authData) throws EAAFBuilderException { ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); String baseID = authData.getIdentificationValue(); String baseIDType = authData.getIdentificationType(); Pair sectorSpecId = null; if (EAAFConstants.URN_PREFIX_BASEID.equals(baseIDType)) { //SAML1 legacy target parameter work-around String spTargetId = oaParam.getAreaSpecificTargetIdentifier(); log.debug("Use OA target identifier '" + spTargetId + "' from configuration"); //calculate sector specific unique identifier sectorSpecId = new BPKBuilder().generateAreaSpecificPersonIdentifier(baseID, spTargetId); } else { log.error("!!!baseID-element does not include a baseID. This should not be happen any more!!!"); sectorSpecId = Pair.newInstance(baseID, baseIDType); } log.trace("Authenticate user with bPK:" + sectorSpecId.getFirst() + " Type:" + sectorSpecId.getSecond()); return sectorSpecId; } protected IIdentityLink buildOAspecificIdentityLink(ISPConfiguration spConfig, IIdentityLink idl, String bPK, String bPKType) throws EAAFConfigurationException, XPathException, DOMException, EAAFParserException { if (spConfig.hasBaseIdTransferRestriction()) { log.debug("SP: " + spConfig.getUniqueIdentifier() + " has baseId transfer restriction. Remove baseId from IDL ..."); Element idlassertion = idl.getSamlAssertion(); //set bpk/wpbk; Node prIdentification = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH); prIdentification.getFirstChild().setNodeValue(bPK); //set bkp/wpbk type Node prIdentificationType = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_TYPE_XPATH); prIdentificationType.getFirstChild().setNodeValue(bPKType); SimpleIdentityLinkAssertionParser idlparser = new SimpleIdentityLinkAssertionParser(idlassertion); return idlparser.parseIdentityLink(); } else return idl; } /** * Check a bPK-Type against a Service-Provider configuration
* If bPK-Type is null the result is false. * * @param oaParam Service-Provider configuration, never null * @param bPKType bPK-Type to check * @return true, if bPK-Type matchs to Service-Provider configuration, otherwise false */ private boolean matchsReceivedbPKToOnlineApplication(ISPConfiguration oaParam, String bPKType) { return oaParam.getAreaSpecificTargetIdentifier().equals(bPKType); } /** * Parse information from an IdentityLink into AuthData object * * @param authData * @param identityLink * @param includedGenericSessionData */ private void parseBasicUserInfosFromIDL(AuthenticationData authData, IIdentityLink identityLink, Collection includedGenericSessionData) { authData.setIdentificationValue(identityLink.getIdentificationValue()); authData.setIdentificationType(identityLink.getIdentificationType()); authData.setGivenName(identityLink.getGivenName()); authData.setFamilyName(identityLink.getFamilyName()); authData.setDateOfBirth(identityLink.getDateOfBirth()); //remove corresponding keys from genericSessionData if exists includedGenericSessionData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME); includedGenericSessionData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME); includedGenericSessionData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME); includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME); includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME); } /** * Get bPK from PVP Attribute 'BPK_NAME', which could be exist in * MOASession as 'GenericData'
session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class)
* * @param session MOASession, but never null * @return bPK, which was received by PVP-Attribute, or null if no attribute exists */ private String getbPKValueFromPVPAttribute(IAuthProcessDataContainer session) { String pvpbPKValueAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.BPK_NAME, String.class); if (StringUtils.isNotEmpty(pvpbPKValueAttr)) { //fix a wrong bPK-value prefix, which was used in some PVP Standardportal implementations if (pvpbPKValueAttr.startsWith("bPK:")) { log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + " contains a not standardize prefix! Staring attribute value correction process ..."); pvpbPKValueAttr = pvpbPKValueAttr.substring("bPK:".length()); } String[] spitted = pvpbPKValueAttr.split(":"); if (spitted.length != 2) { log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + " has a wrong encoding and can NOT be USED!" + " Value:" + pvpbPKValueAttr); return null; } log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME); return spitted[1]; } return null; } /** * Get bPK-Type from PVP Attribute 'EID_SECTOR_FOR_IDENTIFIER_NAME', which could be exist in * MOASession as 'GenericData'
session.getGenericDataFromSession(PVPConstants.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class)
* * @param session MOASession, but never null * @return bPKType, which was received by PVP-Attribute, or null if no attribute exists */ private String getbPKTypeFromPVPAttribute(IAuthProcessDataContainer session) { String pvpbPKTypeAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class); if (StringUtils.isNotEmpty(pvpbPKTypeAttr)) { //fix a wrong bPK-Type encoding, which was used in some PVP Standardportal implementations if (pvpbPKTypeAttr.startsWith(EAAFConstants.URN_PREFIX_CDID) && !pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length(), EAAFConstants.URN_PREFIX_CDID.length() + 1).equals("+")) { log.warn("Receive uncorrect encoded bBKType attribute " + pvpbPKTypeAttr + " Starting attribute value correction ... "); pvpbPKTypeAttr = EAAFConstants.URN_PREFIX_CDID + "+" + pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length() + 1); } log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME); return pvpbPKTypeAttr; } return null; /* * INFO: This code could be used to extract the bPKType from 'PVPConstants.BPK_NAME', * because the prefix of BPK_NAME attribute contains the postfix of the bPKType * * Now, all PVP Standardportals should be able to send 'EID_SECTOR_FOR_IDENTIFIER' * PVP attributes */ // String pvpbPKValueAttr = session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class); // String[] spitted = pvpbPKValueAttr.split(":"); // if (MiscUtil.isEmpty(authData.getBPKType())) { // Logger.debug("PVP assertion contains NO bPK/wbPK target attribute. " + // "Starting target extraction from bPK/wbPK prefix ..."); // //exract bPK/wbPK type from bpk attribute value prefix if type is // //not transmitted as single attribute // Pattern pattern = Pattern.compile("[a-zA-Z]{2}(-[a-zA-Z]+)?"); // Matcher matcher = pattern.matcher(spitted[0]); // if (matcher.matches()) { // //find public service bPK // authData.setBPKType(Constants.URN_PREFIX_CDID + "+" + spitted[0]); // Logger.debug("Found bPK prefix. Set target to " + authData.getBPKType()); // // } else { // //find business service wbPK // authData.setBPKType(Constants.URN_PREFIX_WBPK+ "+" + spitted[0]); // Logger.debug("Found wbPK prefix. Set target to " + authData.getBPKType()); // // } // } } }