/*
* 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 extends AttributeValue>> 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 extends AttributeValue>> 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 extends AttributeValue>> 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;
}
}