/* * 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.handler; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; 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 com.google.common.collect.ImmutableSortedSet; 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.EidPostProcessingException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; import at.gv.egiz.eaaf.core.impl.data.Trible; import edu.umd.cs.findbugs.annotations.NonNull; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; import eu.eidas.auth.commons.light.impl.LightRequest.Builder; import eu.eidas.auth.commons.protocol.eidas.SpType; import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; public abstract class AbstractEidProcessor implements INationalEidProcessor { private static final Logger log = LoggerFactory.getLogger(AbstractEidProcessor.class); @Autowired protected EidasAttributeRegistry attrRegistry; @Autowired protected IConfigurationWithSP basicConfig; @Override public final void preProcess(IRequest pendingReq, Builder authnRequestBuilder) { buildProviderNameAttribute(pendingReq, authnRequestBuilder); buildRequestedAttributes(authnRequestBuilder); } @Override public final ErnbEidData postProcess(Map eidasAttrMap) throws EidPostProcessingException, EidasAttributeException { final ErnbEidData result = new ErnbEidData(); final Object eIdentifierObj = eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); final Trible eIdentifier = EidasResponseUtils.parseEidasPersonalIdentifier((String) eIdentifierObj); result.setCitizenCountryCode(eIdentifier.getFirst()); // MDS attributes result.setPseudonym(processPseudonym(eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER))); result.setFamilyName(processFamilyName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTFAMILYNAME))); result.setGivenName(processGivenName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTGIVENNAME))); result.setDateOfBirth(processDateOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_DATEOFBIRTH))); // additional attributes result.setPlaceOfBirth(processPlaceOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_PLACEOFBIRTH))); result.setBirthName(processBirthName(eidasAttrMap.get(Constants.eIDAS_ATTR_BIRTHNAME))); result.setAddress(processAddress(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTADDRESS))); return result; } /** * Get a Map of country-specific requested attributes. * * @return */ @NonNull protected abstract Map getCountrySpecificRequestedAttributes(); /** * Post-Process the eIDAS CurrentAddress attribute. * * @param currentAddressObj eIDAS current address information * @return current address or null if no attribute is available * @throws EidPostProcessingException if post-processing fails * @throws EidasAttributeException if eIDAS attribute is of a wrong type */ protected PostalAddressType processAddress(Object currentAddressObj) throws EidPostProcessingException, EidasAttributeException { if (currentAddressObj != null) { if (currentAddressObj instanceof PostalAddress) { final PostalAddressType result = new PostalAddressType(); result.setPostalCode(((PostalAddress) currentAddressObj).getPostCode()); result.setMunicipality(((PostalAddress) currentAddressObj).getPostName()); // TODO: add more mappings return result; } else { log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_CURRENTADDRESS + " is of WRONG type"); throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTADDRESS); } } else { log.debug("NO '" + Constants.eIDAS_ATTR_CURRENTADDRESS + "' attribute. Post-Processing skipped ... "); } return null; } /** * Post-Process the eIDAS BirthName attribute. * * @param birthNameObj eIDAS birthname information * @return birthName or null if no attribute is available * @throws EidPostProcessingException if post-processing fails * @throws EidasAttributeException if eIDAS attribute is of a wrong type */ protected String processBirthName(Object birthNameObj) throws EidPostProcessingException, EidasAttributeException { if (birthNameObj != null) { if (birthNameObj instanceof String) { return (String) birthNameObj; } else { log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_BIRTHNAME + " is of WRONG type"); throw new EidasAttributeException(Constants.eIDAS_ATTR_BIRTHNAME); } } else { log.debug("NO '" + Constants.eIDAS_ATTR_BIRTHNAME + "' attribute. Post-Processing skipped ... "); } return null; } /** * Post-Process the eIDAS PlaceOfBirth attribute. * * @param placeOfBirthObj eIDAS Place-of-Birth information * @return place of Birth or null if no attribute is available * @throws EidPostProcessingException if post-processing fails * @throws EidasAttributeException if eIDAS attribute is of a wrong type */ protected String processPlaceOfBirth(Object placeOfBirthObj) throws EidPostProcessingException, EidasAttributeException { if (placeOfBirthObj != null) { if (placeOfBirthObj instanceof String) { return (String) placeOfBirthObj; } else { log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_PLACEOFBIRTH + " is of WRONG type"); throw new EidasAttributeException(Constants.eIDAS_ATTR_PLACEOFBIRTH); } } else { log.debug("NO '" + Constants.eIDAS_ATTR_PLACEOFBIRTH + "' attribute. Post-Processing skipped ... "); } return null; } /** * Post-Process the eIDAS DateOfBirth attribute. * * @param dateOfBirthObj eIDAS date-of-birth attribute information * @return formated user's date-of-birth * @throws EidasAttributeException if NO attribute is available * @throws EidPostProcessingException if post-processing fails */ protected DateTime processDateOfBirth(Object dateOfBirthObj) throws EidPostProcessingException, EidasAttributeException { if (dateOfBirthObj == null || !(dateOfBirthObj instanceof DateTime)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); } return (DateTime) dateOfBirthObj; } /** * Post-Process the eIDAS GivenName attribute. * * @param givenNameObj eIDAS givenName attribute information * @return formated user's givenname * @throws EidasAttributeException if NO attribute is available * @throws EidPostProcessingException if post-processing fails */ protected String processGivenName(Object givenNameObj) throws EidPostProcessingException, EidasAttributeException { if (givenNameObj == null || !(givenNameObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTGIVENNAME); } return (String) givenNameObj; } /** * Post-Process the eIDAS FamilyName attribute. * * @param familyNameObj eIDAS familyName attribute information * @return formated user's familyname * @throws EidasAttributeException if NO attribute is available * @throws EidPostProcessingException if post-processing fails */ protected String processFamilyName(Object familyNameObj) throws EidPostProcessingException, EidasAttributeException { if (familyNameObj == null || !(familyNameObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTFAMILYNAME); } return (String) familyNameObj; } /** * Post-Process the eIDAS pseudonym to ERnB unique identifier. * * @param personalIdObj eIDAS PersonalIdentifierAttribute * @return Unique personal identifier without country-code information * @throws EidasAttributeException if NO attribute is available * @throws EidPostProcessingException if post-processing fails */ protected String processPseudonym(Object personalIdObj) throws EidPostProcessingException, EidasAttributeException { if (personalIdObj == null || !(personalIdObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); } final Trible eIdentifier = EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); return eIdentifier.getThird(); } private void buildRequestedAttributes(Builder authnRequestBuilder) { // build and add requested attribute set final Map ccSpecificReqAttr = getCountrySpecificRequestedAttributes(); log.debug("Get #{} country-specific requested attributes", ccSpecificReqAttr.size()); final Map mdsReqAttr = attrRegistry.getDefaultAttributeSetFromConfiguration(); log.trace("Get #{} default requested attributes", mdsReqAttr.size()); // put it together ccSpecificReqAttr.putAll(mdsReqAttr); // convert it to eIDAS attributes final ImmutableAttributeMap reqAttrMap = translateToEidasAttributes(ccSpecificReqAttr); authnRequestBuilder.requestedAttributes(reqAttrMap); } private ImmutableAttributeMap translateToEidasAttributes(final Map requiredAttributes) { final ImmutableAttributeMap.Builder builder = ImmutableAttributeMap.builder(); for (final Map.Entry attribute : requiredAttributes.entrySet()) { final String name = attribute.getKey(); final ImmutableSortedSet> byFriendlyName = attrRegistry .getCoreAttributeRegistry().getByFriendlyName(name); if (!byFriendlyName.isEmpty()) { final AttributeDefinition attributeDefinition = byFriendlyName.first(); builder.put(AttributeDefinition.builder(attributeDefinition).required(attribute.getValue()).build()); } else { log.warn("Can NOT request UNKNOWN attribute: " + attribute.getKey() + " Ignore it!"); } } return builder.build(); } private void buildProviderNameAttribute(IRequest pendingReq, Builder authnRequestBuilder) { final ISpConfiguration spConfig = pendingReq.getServiceProviderConfiguration(); // set correct SPType for requested target sector final String publicSectorTargetSelector = basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_NODE_PUBLICSECTOR_TARGETS, Constants.POLICY_DEFAULT_ALLOWED_TARGETS); final Pattern p = Pattern.compile(publicSectorTargetSelector); final Matcher m = p.matcher(spConfig.getAreaSpecificTargetIdentifier()); if (m.matches()) { log.debug("Map " + spConfig.getAreaSpecificTargetIdentifier() + " to 'PublicSector'"); authnRequestBuilder.spType(SpType.PUBLIC.getValue()); if (basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP, false)) { authnRequestBuilder.providerName(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP, Constants.DEFAULT_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP)); } else { // TODO: only for eIDAS ref. node 2.0 and 2.1 because it need 'Providername' for // any SPType final String providerName = pendingReq.getRawData(Constants.DATA_PROVIDERNAME, String.class); if (StringUtils.isNotEmpty(providerName) && basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_ADD_ALWAYS_PROVIDERNAME, false)) { authnRequestBuilder.providerName(providerName); } } } else { log.debug("Map " + spConfig.getAreaSpecificTargetIdentifier() + " to 'PrivateSector'"); authnRequestBuilder.spType(SpType.PRIVATE.getValue()); // TODO: switch to RequesterId in further version // set provider name for private sector applications final String providerName = pendingReq.getRawData(Constants.DATA_PROVIDERNAME, String.class); if (StringUtils.isNotEmpty(providerName)) { authnRequestBuilder.providerName(providerName); } } } }