/* * 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.utils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType; import at.gv.egiz.eaaf.core.impl.data.Triple; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.attribute.AttributeValueMarshaller; import eu.eidas.auth.commons.attribute.AttributeValueMarshallingException; import eu.eidas.auth.commons.attribute.AttributeValueTransliterator; import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; public class EidasResponseUtils { private static final Logger log = LoggerFactory.getLogger(EidasResponseUtils.class); public static final String PERSONALIDENIFIER_VALIDATION_PATTERN = "^[A-Z,a-z]{2}/[A-Z,a-z]{2}/.*"; /** * Validate a eIDAS PersonalIdentifier attribute value This validation is done * according to eIDAS SAML Attribute Profile - Section 2.2.3 Unique Identifier * * @param uniqueID eIDAS attribute value of a unique identifier * @return true if the uniqueID matches to eIDAS to Unique Identifier * specification, otherwise false */ public static boolean validateEidasPersonalIdentifier(String uniqueID) { final Pattern pattern = Pattern.compile(PERSONALIDENIFIER_VALIDATION_PATTERN); final Matcher matcher = pattern.matcher(uniqueID); return matcher.matches(); } /** * Parse an eIDAS PersonalIdentifier attribute value into it components. This * processing is done according to eIDAS SAML Attribute Profile - Section 2.2.3 * Unique Identifier * * @param uniqueID eIDAS attribute value of a unique identifier * @return {@link Triple} that contains:
* First : citizen country
* Second: destination country
* Third : unique identifier
* or null if the attribute value has a wrong format */ public static Triple parseEidasPersonalIdentifier(String uniqueID) { if (!validateEidasPersonalIdentifier(uniqueID)) { log.error("eIDAS attribute value for {} looks wrong formated. Value: {}", Constants.eIDAS_ATTR_PERSONALIDENTIFIER, uniqueID); return null; } return Triple.newInstance(uniqueID.substring(0, 2), uniqueID.substring(3, 5), uniqueID.substring(6)); } /** * Get eIDAS attribute-values from eIDAS Node attributes. * * @param attributeDefinition eIDAS attribute definition * @param attributeValues Attributes from eIDAS response * @return Set of attribute values. If more then one value than the first value * contains the 'Latin' value. */ // TODO: check possible problem with nonLatinCharacters public static List translateStringListAttribute(AttributeDefinition attributeDefinition, @Nullable ImmutableSet> attributeValues) { final List stringListAttribute = new ArrayList<>(); if (attributeValues == null) { log.info("Can not extract infos from 'null' attribute value"); } else { final AttributeValueMarshaller attributeValueMarshaller = attributeDefinition.getAttributeValueMarshaller(); for (final AttributeValue attributeValue : attributeValues) { String valueString = null; try { valueString = attributeValueMarshaller.marshal((AttributeValue) attributeValue); log.trace("Find attr: {} with value: {} nonLatinFlag: {} needTransliteration: {}", attributeDefinition.getFriendlyName(), attributeValue.toString(), attributeValue.isNonLatinScriptAlternateVersion(), AttributeValueTransliterator.needsTransliteration(valueString)); // if (attributeValue.isNonLatinScriptAlternateVersion()) { if (!AttributeValueTransliterator.needsTransliteration(valueString)) { stringListAttribute.add(0, valueString); } else { log.trace("Find 'needsTransliteration' flag. Setting this value at last list element ... "); stringListAttribute.add(valueString); log.trace("Find attr: {} with value: {} nonLatinFlag: {} needTransliteration: {}", attributeDefinition.getFriendlyName(), attributeValue.toString(), attributeValue.isNonLatinScriptAlternateVersion(), AttributeValueTransliterator.needsTransliteration(valueString)); // if (attributeValue.isNonLatinScriptAlternateVersion()) { if (!AttributeValueTransliterator.needsTransliteration(valueString)) { stringListAttribute.add(0, valueString); } else { log.trace("Find 'needsTransliteration' flag. Setting this value at last list element ... "); stringListAttribute.add(valueString); } } } catch (final AttributeValueMarshallingException e) { throw new IllegalStateException(e); } } log.trace("Extract values: {} for attr: {}", StringUtils.join(stringListAttribute, ","), attributeDefinition.getFriendlyName()); } return stringListAttribute; } /** * Convert eIDAS DateTime attribute to Java Object. * * @param attributeDefinition eIDAS attribute definition. * @param attributeValues eIDAS attribute value * @return */ @Nullable public static DateTime translateDateAttribute(AttributeDefinition attributeDefinition, ImmutableList> attributeValues) { if (attributeValues.size() != 0) { final AttributeValue firstAttributeValue = attributeValues.get(0); return (DateTime) firstAttributeValue.getValue(); } return null; } /** * Concert eIDAS Address attribute to Java object. * * @param attributeDefinition eIDAS attribute definition * @param attributeValues eIDAS attribute value * @return */ @Nullable public static PostalAddress translateAddressAttribute(AttributeDefinition attributeDefinition, ImmutableList> attributeValues) { final AttributeValue firstAttributeValue = attributeValues.get(0); return (PostalAddress) firstAttributeValue.getValue(); } /** * Post-Process the eIDAS CurrentAddress attribute. * * @param currentAddressObj eIDAS current address information * @return current address or null if no attribute is available * @throws EidasAttributeException if eIDAS attribute is of a wrong type */ public static PostalAddressType processAddress(Object currentAddressObj) throws 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 EidasAttributeException if eIDAS attribute is of a wrong type */ public static String processBirthName(Object birthNameObj) throws 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 EidasAttributeException if eIDAS attribute is of a wrong type */ public static String processPlaceOfBirth(Object placeOfBirthObj) throws 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 */ public static DateTime processDateOfBirth(Object dateOfBirthObj) throws EidasAttributeException { if (!(dateOfBirthObj instanceof DateTime)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); } return (DateTime) dateOfBirthObj; } /** * Post-Process the eIDAS DateOfBirth attribute to a string. * * @param dateOfBirthObj eIDAS date-of-birth attribute information * @return formated user's date-of-birth as string * @throws EidasAttributeException if NO attribute is available */ public static String processDateOfBirthToString(Object dateOfBirthObj) throws EidasAttributeException { if (dateOfBirthObj instanceof String) { try { new SimpleDateFormat("yyyy-MM-dd").parse((String) dateOfBirthObj); return (String) dateOfBirthObj; } catch (ParseException e) { throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); } } if (!(dateOfBirthObj instanceof DateTime)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); } return new SimpleDateFormat("yyyy-MM-dd").format(((DateTime) dateOfBirthObj).toDate()); } /** * Post-Process the eIDAS GivenName attribute. * * @param givenNameObj eIDAS givenName attribute information * @return formated user's givenname * @throws EidasAttributeException if NO attribute is available */ public static String processGivenName(Object givenNameObj) throws EidasAttributeException { if (!(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 */ public static String processFamilyName(Object familyNameObj) throws EidasAttributeException { if (!(familyNameObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTFAMILYNAME); } return (String) familyNameObj; } /** * Post-Process the eIDAS personal identifier attribute. * * @param personalIdentifierObj eIDAS personal identifier attribute-information * @return formated user's full personal identifier * @throws EidasAttributeException if NO attribute is available */ public static String processPersonalIdentifier(Object personalIdentifierObj) throws EidasAttributeException { if (!(personalIdentifierObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); } return (String) personalIdentifierObj; } /** * 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 */ public static String processPseudonym(Object personalIdObj) throws EidasAttributeException { if (!(personalIdObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); } final Triple eIdentifier = EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); if (eIdentifier == null || eIdentifier.getThird() == null) { throw new EidasAttributeException("Error processing eIdentifier"); } return eIdentifier.getThird(); } /** * Post-Process the eIDAS pseudonym to citizen country code. * * @param personalIdObj eIDAS PersonalIdentifierAttribute * @return Citizen Country Code * @throws EidasAttributeException if NO attribute is available */ public static String processCountryCode(Object personalIdObj) throws EidasAttributeException { if (!(personalIdObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); } final Triple eIdentifier = EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); if (eIdentifier == null || eIdentifier.getFirst() == null) { throw new EidasAttributeException("Error processing eIdentifier"); } return eIdentifier.getFirst(); } /** * Post-Process the eIDAS TaxReference attribute. * * @param taxReferenceObj eIDAS TaxReference attribute information * @return formated user's TaxReference * @throws EidasAttributeException if NO attribute is available */ public static String processTaxReference(Object taxReferenceObj) throws EidasAttributeException { if (!(taxReferenceObj instanceof String)) { throw new EidasAttributeException(Constants.eIDAS_ATTR_TAXREFERENCE); } return (String) taxReferenceObj; } }