/*
* 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.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.Nullable;
import org.jose4j.lang.JoseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Element;
import com.fasterxml.jackson.core.JsonProcessingException;
import at.asitplus.eidas.specific.core.MsConnectorEventCodes;
import at.asitplus.eidas.specific.core.MsEidasNodeConstants;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.szr.SzrClient;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.MatchedPersonResult;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.AuthBlockSigningService;
import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils;
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.EaafStorageException;
import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
import at.gv.egiz.eaaf.core.impl.builder.BpkBuilder;
import at.gv.egiz.eaaf.core.impl.data.Pair;
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 lombok.Data;
import lombok.extern.slf4j.Slf4j;
import szrservices.IdentityLinkType;
/**
* Task that creates the IdentityLink for an eIDAS authenticated person.
* Input:
*
* - {@link Constants#DATA_SIMPLE_EIDAS} initial login data from user
* - {@link Constants#DATA_PERSON_MATCH_RESULT} the data of the matched entry in a register
*
* Output:
*
* - {@link Constants#EIDAS_BIND} the binding block
* - {@link Constants#SZR_AUTHBLOCK} the auth block
*
* Transitions:
*
* - {@link at.gv.egiz.eaaf.core.impl.idp.controller.tasks.FinalizeAuthenticationTask}
*
* @author tlenz
*/
@Slf4j
@Component("CreateIdentityLinkTask")
public class CreateIdentityLinkTask extends AbstractAuthServletTask {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
private IConfiguration basicConfig;
@Autowired
private SzrClient szrClient;
@Autowired
private AuthBlockSigningService authBlockSigner;
private static final String EID_STATUS = "urn:eidgvat:eid.status.eidas";
/*
* (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 {
/*TODO: needs more re-factoring if we finalize CreateNewErnpEntryTask and we know how add entries into ERnP
* Maybe, we can fully replace eidData by matchedPersonData,
* because matchedPersonData holds the result after a successful matching process.
*
* Currently, we only add a work-around to operate without new ERnP implementation.
*/
final SimpleEidasData eidData = MatchingTaskUtils.getInitialEidasData(pendingReq);
MatchedPersonResult matchedPersonData = MatchingTaskUtils.getFinalMatchingResult(pendingReq);
// write log information based on current configuration
writeMdsLogInformation(eidData);
//request SZR based on IDL or E-ID mode
if (pendingReq.getServiceProviderConfiguration()
.isConfigurationValue(MsEidasNodeConstants.PROP_CONFIG_SP_NEW_EID_MODE, false)) {
executeEidMode(eidData, matchedPersonData);
} else {
executeIdlMode(eidData, matchedPersonData);
}
storeGenericInfoToSession(eidData);
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 void storeGenericInfoToSession(SimpleEidasData eidData) throws EaafStorageException {
AuthProcessDataWrapper authProcessData = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq);
authProcessData.setForeigner(true);
authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME,
eidData.getCitizenCountryCode());
}
private void executeIdlMode(SimpleEidasData eidData, MatchedPersonResult matchedPersonData) throws EaafException {
//request SZR
SzrResultHolder idlResult = requestSzrForIdentityLink(matchedPersonData);
//write revision-Log entry for personal-identifier mapping
writeExtendedRevisionLogEntry(eidData, eidData.getPersonalIdentifier());
//check result-data and write revision-log based on current state
checkStateAndWriteRevisionLog(idlResult);
//inject personal-data into session
AuthProcessDataWrapper authProcessDataWrapper = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq);
authProcessDataWrapper.setIdentityLink(idlResult.getIdentityLink());
authProcessDataWrapper.setEidProcess(false);
// set bPK and bPKType into auth session
authProcessDataWrapper.setGenericDataToSession(PvpAttributeDefinitions.BPK_NAME, extendBpkByPrefix(
idlResult.getBpK(), pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()));
authProcessDataWrapper.setGenericDataToSession(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME,
pendingReq.getServiceProviderConfiguration()
.getAreaSpecificTargetIdentifier());
}
private void executeEidMode(SimpleEidasData eidData, MatchedPersonResult matchedPersonData)
throws JsonProcessingException, EaafException, JoseException {
// get encrypted baseId
log.debug("Requesting encrypted baseId by already matched person information ... ");
String vsz = szrClient.getEncryptedStammzahl(matchedPersonData);
//write revision-Log entry and extended infos personal-identifier mapping
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_VSZ_RECEIVED);
writeExtendedRevisionLogEntry(eidData, eidData.getPersonalIdentifier());
// get eIDAS bind
String signedEidasBind = szrClient
.getEidasBind(vsz, authBlockSigner.getBase64EncodedPublicKey(), EID_STATUS, matchedPersonData);
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_EIDASBIND_RECEIVED);
AuthProcessDataWrapper authProcessDataWrapper = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq);
authProcessDataWrapper.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_EIDAS_BIND, signedEidasBind);
//get signed AuthBlock
String jwsSignature = authBlockSigner.buildSignedAuthBlock(pendingReq);
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.TECH_AUCHBLOCK_CREATED);
authProcessDataWrapper.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_SZR_AUTHBLOCK, jwsSignature);
//inject personal-data into session
authProcessDataWrapper.setEidProcess(true);
}
private void writeExtendedRevisionLogEntry(SimpleEidasData eidData, String personalIdentifier) {
// write ERnP input-data into revision-log
if (basicConfig.getBasicConfigurationBoolean(
Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE, false)) {
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, personalIdentifier);
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_ERNB_ID, eidData.getPseudonym());
}
}
private SzrResultHolder requestSzrForIdentityLink(MatchedPersonResult matchedPersonData) throws EaafException {
//request IdentityLink from SZR
log.debug("Requesting encrypted baseId by already matched person information ... ");
IdentityLinkType result = szrClient.getIdentityLinkInRawMode(matchedPersonData);
final Element idlFromSzr = (Element) result.getAssertion();
final IIdentityLink identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink();
// get bPK from SZR
String bpk = null;
String targetId = pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier();
boolean debugUseSzrForBpk = basicConfig
.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true);
if (debugUseSzrForBpk) {
String vkz = basicConfig
.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, "no VKZ defined");
List bpkList = szrClient.getBpk(matchedPersonData, targetId, vkz);
if (!bpkList.isEmpty()) {
bpk = bpkList.get(0);
}
} else {
log.debug("Calculating bPK from baseId ... ");
String idValue = identityLink.getIdentificationValue();
String idType = identityLink.getIdentificationType();
final Pair bpkCalc = BpkBuilder.generateAreaSpecificPersonIdentifier(idValue, idType, targetId);
bpk = bpkCalc.getFirst();
}
return new SzrResultHolder(identityLink, bpk);
}
private void checkStateAndWriteRevisionLog(SzrResultHolder idlResult) throws SzrCommunicationException {
// write some infos into revision log
if (idlResult.getIdentityLink() == null) {
log.error("ERnB did not return an identity link.");
throw new SzrCommunicationException("ernb.00", null);
}
String assertionId = idlResult.getIdentityLink().getSamlAssertion()
.getAttribute(SimpleIdentityLinkAssertionParser.ASSERTIONID);
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_IDL_RECEIVED, assertionId);
if (idlResult.getBpK() == 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");
}
private String extendBpkByPrefix(String bpk, String type) {
String bpkType = getBpkType(type);
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;
}
}
@Nullable
private String getBpkType(String type) {
if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) {
return type.substring(EaafConstants.URN_PREFIX_WBPK.length());
} else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) {
return type.substring(EaafConstants.URN_PREFIX_CDID.length());
} else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) {
return type.substring(EaafConstants.URN_PREFIX_EIDAS.length());
} else {
return null;
}
}
/**
* write MDS into technical log and revision log.
*/
private void writeMdsLogInformation(SimpleEidasData eidData) {
boolean writeMdsInTechLog = basicConfig
.getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_TECHNICALLOG_WRITE_MDS_INTO_TECH_LOG, false);
if (writeMdsInTechLog) {
log.info("eIDAS Auth. for user: " + eidData.getGivenName() + " " + eidData.getFamilyName() + " " + eidData
.getDateOfBirth() + " " + "from " + eidData.getCitizenCountryCode());
}
boolean writeMdsInRevLog = basicConfig
.getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_REVISIONLOG_WRITE_MDS_INTO_REVISION_LOG,
false);
if (writeMdsInRevLog) {
revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_MDSDATA,
"{" + eidData.getGivenName() + "," + eidData.getFamilyName() + "," + eidData
.getDateOfBirth() + "," + eidData.getCitizenCountryCode() + "}");
}
}
@Data
private static class SzrResultHolder {
final IIdentityLink identityLink;
final String bpK;
}
}