From 7bf7c3c03fd3a1efeaf3f8e3dd75922e2f5f9921 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Tue, 8 Mar 2022 19:06:10 +0100 Subject: refactor(core): move all project libs into sub-project 'modules' --- .../auth/idaustria/IdAustriaAuthConstants.java | 123 ++++++ ...ustriaAuthenticationSpringResourceProvider.java | 56 +++ ...IdAustriaEidasProxyAuthenticationModulImpl.java | 91 ++++ .../EidasConnecorUniqueIdAttributeBuilder.java | 52 +++ .../config/IdAustriaAuthMessageSource.java | 22 + .../config/IdAustriaAuthMetadataConfiguration.java | 462 +++++++++++++++++++++ .../IdAustriaAuthRequestBuilderConfiguration.java | 307 ++++++++++++++ .../IdAustriaAuthMetadataController.java | 150 +++++++ .../controller/IdAustriaAuthSignalController.java | 94 +++++ .../tasks/ReceiveFromIdAustriaSystemTask.java | 393 ++++++++++++++++++ .../tasks/RequestIdAustriaSystemTask.java | 205 +++++++++ .../utils/IdAustriaAuthCredentialProvider.java | 130 ++++++ .../utils/IdAustriaAuthMetadataProvider.java | 168 ++++++++ .../modules/auth/idaustria/utils/Utils.java | 44 ++ 14 files changed, 2297 insertions(+) create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthConstants.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthenticationSpringResourceProvider.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaEidasProxyAuthenticationModulImpl.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/builder/attributes/EidasConnecorUniqueIdAttributeBuilder.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMessageSource.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMetadataConfiguration.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthRequestBuilderConfiguration.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthMetadataController.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthSignalController.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/ReceiveFromIdAustriaSystemTask.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/RequestIdAustriaSystemTask.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthCredentialProvider.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthMetadataProvider.java create mode 100644 modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/Utils.java (limited to 'modules/authmodule_id-austria/src/main/java/at/asitplus') diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthConstants.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthConstants.java new file mode 100644 index 00000000..57e5c706 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthConstants.java @@ -0,0 +1,123 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.impl.data.Triple; + +/** + * Constant values for ID Austria authentication module. + * + * @author tlenz + * + */ +public class IdAustriaAuthConstants { + + private IdAustriaAuthConstants() { + + } + + public static final String ERRORTYPE_00 = "module.idaustria.00"; + public static final String ERRORTYPE_01 = "module.idaustria.01"; + public static final String ERRORTYPE_02 = "module.idaustria.02"; + public static final String ERRORTYPE_03 = "module.idaustria.03"; + public static final String ERRORTYPE_04 = "module.idaustria.04"; + public static final String ERRORTYPE_05 = "module.idaustria.05"; + public static final String ERRORTYPE_06 = "module.idaustria.06"; + public static final String ERRORTYPE_98 = "module.idaustria.98"; + public static final String ERRORTYPE_99 = "module.idaustria.99"; + + + public static final String SAML2_STATUSCODE_USERSTOP = "1005"; + + public static final int METADATA_VALIDUNTIL_IN_HOURS = 24 * 365; + public static final String MODULE_NAME_FOR_LOGGING = "ID Austria based eIDAS authentication"; + + public static final String ENDPOINT_POST = "/sp/idaustria/eidas/post"; + public static final String ENDPOINT_REDIRECT = "/sp/idaustria/eidas/redirect"; + public static final String ENDPOINT_METADATA = "/sp/idaustria/eidas/metadata"; + + public static final String CONFIG_PROPS_PREFIX = "modules.idaustriaauth."; + + public static final String CONFIG_PROPS_IDAUSTRIA_ENTITYID = CONFIG_PROPS_PREFIX + "idp.entityId"; + public static final String CONFIG_PROPS_IDAUSTRIA_METADATAURL = CONFIG_PROPS_PREFIX + "idp.metadataUrl"; + + public static final String CONFIG_PROPS_KEYSTORE_TYPE = CONFIG_PROPS_PREFIX + "keystore.type"; + public static final String CONFIG_PROPS_KEYSTORE_NAME = CONFIG_PROPS_PREFIX + "keystore.name"; + public static final String CONFIG_PROPS_KEYSTORE_PATH = CONFIG_PROPS_PREFIX + "keystore.path"; + public static final String CONFIG_PROPS_KEYSTOREPASSWORD = CONFIG_PROPS_PREFIX + "keystore.password"; + public static final String CONFIG_PROPS_SIGN_METADATA_KEY_PASSWORD = CONFIG_PROPS_PREFIX + + "metadata.sign.password"; + public static final String CONFIG_PROPS_SIGN_METADATA_ALIAS = CONFIG_PROPS_PREFIX + + "metadata.sign.alias"; + public static final String CONFIG_PROPS_SIGN_SIGNING_KEY_PASSWORD = CONFIG_PROPS_PREFIX + + "request.sign.password"; + public static final String CONFIG_PROPS_SIGN_SIGNING_ALIAS = CONFIG_PROPS_PREFIX + + "request.sign.alias"; + public static final String CONFIG_PROPS_ENCRYPTION_KEY_PASSWORD = CONFIG_PROPS_PREFIX + + "response.encryption.password"; + public static final String CONFIG_PROPS_ENCRYPTION_ALIAS = CONFIG_PROPS_PREFIX + + "response.encryption.alias"; + + public static final String CONFIG_PROPS_TRUSTSTORE_TYPE = CONFIG_PROPS_PREFIX + "truststore.type"; + public static final String CONFIG_PROPS_TRUSTSTORE_NAME = CONFIG_PROPS_PREFIX + "truststore.name"; + public static final String CONFIG_PROPS_TRUSTSTORE_PATH = CONFIG_PROPS_PREFIX + "truststore.path"; + public static final String CONFIG_PROPS_TRUSTSTORE_PASSWORD = CONFIG_PROPS_PREFIX + "truststore.password"; + + public static final String CONFIG_PROPS_REQUIRED_PVP_ATTRIBUTES_LIST = CONFIG_PROPS_PREFIX + + "required.additional.attributes"; + + + /** + * SP specific EntityId of the ID Austria. + */ + public static final String CONFIG_PROPS_APPSPECIFIC_IDAUSTRIA_NODE_URL = "auth.idaustria.entityId"; + + + public static final List> DEFAULT_REQUIRED_PVP_ATTRIBUTES = + Collections.unmodifiableList(new ArrayList>() { + private static final long serialVersionUID = 1L; + { + // add PVP Version attribute + add(Triple.newInstance(PvpAttributeDefinitions.PVP_VERSION_NAME, + PvpAttributeDefinitions.PVP_VERSION_FRIENDLY_NAME, false)); + + // entity information + add(Triple.newInstance(PvpAttributeDefinitions.GIVEN_NAME_NAME, + PvpAttributeDefinitions.GIVEN_NAME_FRIENDLY_NAME, true)); + add(Triple.newInstance(PvpAttributeDefinitions.PRINCIPAL_NAME_NAME, + PvpAttributeDefinitions.PRINCIPAL_NAME_FRIENDLY_NAME, true)); + add(Triple.newInstance(PvpAttributeDefinitions.BIRTHDATE_NAME, + PvpAttributeDefinitions.BIRTHDATE_FRIENDLY_NAME, true)); + add(Triple.newInstance(PvpAttributeDefinitions.BPK_NAME, + PvpAttributeDefinitions.BPK_FRIENDLY_NAME, true)); + + // entity metadata information + add(Triple.newInstance(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, + PvpAttributeDefinitions.EID_ISSUING_NATION_FRIENDLY_NAME, true)); + add(Triple.newInstance(PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, + PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME, false)); + + // mandate attributes + add(Triple.newInstance(PvpAttributeDefinitions.MANDATE_TYPE_NAME, + PvpAttributeDefinitions.MANDATE_TYPE_FRIENDLY_NAME, false)); + add(Triple.newInstance(PvpAttributeDefinitions.MANDATE_TYPE_OID_NAME, + PvpAttributeDefinitions.MANDATE_TYPE_OID_FRIENDLY_NAME, false)); + addAll(MsEidasNodeConstants.DEFAULT_REQUIRED_MANDATE_JUR_PVP_ATTRIBUTES); + addAll(MsEidasNodeConstants.DEFAULT_REQUIRED_MANDATE_NAT_PVP_ATTRIBUTES); + + } + }); + + public static final Set DEFAULT_REQUIRED_PVP_ATTRIBUTE_NAMES = + DEFAULT_REQUIRED_PVP_ATTRIBUTES.stream() + .filter(el -> el.getThird()) + .map(el -> el.getFirst()) + .collect(Collectors.toSet()); + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthenticationSpringResourceProvider.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthenticationSpringResourceProvider.java new file mode 100644 index 00000000..2240b843 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaAuthenticationSpringResourceProvider.java @@ -0,0 +1,56 @@ +/* + * 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.idaustria; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import at.gv.egiz.components.spring.api.SpringResourceProvider; + +public class IdAustriaAuthenticationSpringResourceProvider implements SpringResourceProvider { + + @Override + public String getName() { + return "Auth. module for ID Austria based authentication"; + } + + @Override + public String[] getPackagesToScan() { + return null; + + } + + @Override + public Resource[] getResourcesToLoad() { + final ClassPathResource idAustriaAuthConfig = + new ClassPathResource("/spring/id_austria_auth.beans.xml", + IdAustriaAuthenticationSpringResourceProvider.class); + final ClassPathResource idAustriaTaskConfig = + new ClassPathResource("/spring/id_austria_task.beans.xml", + IdAustriaAuthenticationSpringResourceProvider.class); + + return new Resource[] { idAustriaAuthConfig, idAustriaTaskConfig }; + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaEidasProxyAuthenticationModulImpl.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaEidasProxyAuthenticationModulImpl.java new file mode 100644 index 00000000..6f90569c --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/IdAustriaEidasProxyAuthenticationModulImpl.java @@ -0,0 +1,91 @@ +/* + * 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.idaustria; + +import at.asitplus.eidas.specific.modules.msproxyservice.protocol.ProxyServicePendingRequest; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import lombok.extern.slf4j.Slf4j; + +/** + * Authentication-process selection to start ID Austria authentication for eIDAS Proxy-Service requests. + * + * @author tlenz + * + */ +@Slf4j +public class IdAustriaEidasProxyAuthenticationModulImpl implements AuthModule { + + private static final String ID_AUSTRIA_EIDAS_PROXY_AUTHPROCESS_NAME = "idAustriaForEidasProxyService"; + + private int priority = 1; + + @Override + public int getPriority() { + return priority; + } + + /** + * Sets the priority of this module. Default value is {@code 0}. + * + * @param priority The priority. + */ + public void setPriority(int priority) { + this.priority = priority; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.auth.modules.AuthModule#selectProcess(at.gv. + * egovernment.moa.id.process.api.ExecutionContext) + */ + @Override + public String selectProcess(ExecutionContext context, IRequest pendingReq) { + if (pendingReq instanceof ProxyServicePendingRequest) { + log.info("Find eIDAS Proxy-Service request. Starting ID Austria based user authentication ... "); + return ID_AUSTRIA_EIDAS_PROXY_AUTHPROCESS_NAME; + + } else { + log.trace("No {} request. Ignore it for ID Austria authentication", + ProxyServicePendingRequest.class.getName()); + + } + + return null; + + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.auth.modules.AuthModule#getProcessDefinitions() + */ + @Override + public String[] getProcessDefinitions() { + return new String[] { "classpath:/process/id_austria.Authentication.process.xml" }; + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/builder/attributes/EidasConnecorUniqueIdAttributeBuilder.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/builder/attributes/EidasConnecorUniqueIdAttributeBuilder.java new file mode 100644 index 00000000..89e06991 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/builder/attributes/EidasConnecorUniqueIdAttributeBuilder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a + * cooperation between EGIZ, A-SIT Plus, 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.asitplus.eidas.specific.modules.auth.idaustria.builder.attributes; + +import at.gv.egiz.eaaf.core.api.data.ExtendedPvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.IAttributeBuilder; +import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; +import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException; + +public class EidasConnecorUniqueIdAttributeBuilder + implements IAttributeBuilder, ExtendedPvpAttributeDefinitions { + + @Override + public String getName() { + return EIDAS_CONNECTOR_UNIQUEID_NAME; + } + + @Override + public ATT build(final ISpConfiguration oaParam, final IAuthData authData, + final IAttributeGenerator g) + throws AttributeBuilderException { + return g.buildStringAttribute(EIDAS_CONNECTOR_UNIQUEID_FRIENDLY_NAME, EIDAS_CONNECTOR_UNIQUEID_NAME, + oaParam.getUniqueIdentifier()); + + } + + @Override + public ATT buildEmpty(final IAttributeGenerator g) { + return g.buildEmptyAttribute(EIDAS_CONNECTOR_UNIQUEID_FRIENDLY_NAME, EIDAS_CONNECTOR_UNIQUEID_NAME); + + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMessageSource.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMessageSource.java new file mode 100644 index 00000000..697c4496 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMessageSource.java @@ -0,0 +1,22 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.config; + +import java.util.Arrays; +import java.util.List; + +import at.gv.egiz.eaaf.core.api.logging.IMessageSourceLocation; + +/** + * i18n message-source for ID Austria authentication errors. + * + * @author tlenz + * + */ +public class IdAustriaAuthMessageSource implements IMessageSourceLocation { + + @Override + public List getMessageSourceLocation() { + return Arrays.asList("classpath:messages/idaustria_auth_messages"); + + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMetadataConfiguration.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMetadataConfiguration.java new file mode 100644 index 00000000..41990363 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthMetadataConfiguration.java @@ -0,0 +1,462 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.NameIDType; +import org.opensaml.saml.saml2.metadata.ContactPerson; +import org.opensaml.saml.saml2.metadata.Organization; +import org.opensaml.saml.saml2.metadata.RequestedAttribute; +import org.opensaml.security.credential.Credential; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.IdAustriaAuthCredentialProvider; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import at.gv.egiz.eaaf.modules.pvp2.api.IPvp2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpAttributeBuilder; +import lombok.extern.slf4j.Slf4j; + +/** + * Configuration object to generate PVP S-Profile metadata for SAML2 client. + * + * @author tlenz + * + */ +@Slf4j +public class IdAustriaAuthMetadataConfiguration implements IPvpMetadataBuilderConfiguration { + + private Collection additionalAttributes = null; + + private final String authUrl; + private final IdAustriaAuthCredentialProvider credentialProvider; + private final IPvp2BasicConfiguration pvpConfiguration; + + /** + * Configuration object to create PVP2 S-Profile metadata information. + * + * @param authUrl Public URL prefix of the application + * @param credentialProvider Credentials used by PVP2 S-Profile end-point + * @param pvpConfiguration Basic PVP2 S-Profile end-point configuration + */ + public IdAustriaAuthMetadataConfiguration(String authUrl, + IdAustriaAuthCredentialProvider credentialProvider, + IPvp2BasicConfiguration pvpConfiguration) { + this.authUrl = authUrl; + this.credentialProvider = credentialProvider; + this.pvpConfiguration = pvpConfiguration; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getMetadataValidUntil() + */ + @Override + public int getMetadataValidUntil() { + return IdAustriaAuthConstants.METADATA_VALIDUNTIL_IN_HOURS; + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * buildEntitiesDescriptorAsRootElement() + */ + @Override + public boolean buildEntitiesDescriptorAsRootElement() { + return false; + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * buildIDPSSODescriptor() + */ + @Override + public boolean buildIdpSsoDescriptor() { + return false; + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * buildSPSSODescriptor() + */ + @Override + public boolean buildSpSsoDescriptor() { + return true; + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getEntityIDPostfix() + */ + @Override + public String getEntityID() { + return authUrl + IdAustriaAuthConstants.ENDPOINT_METADATA; + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getEntityFriendlyName() + */ + @Override + public String getEntityFriendlyName() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getContactPersonInformation() + */ + @Override + public List getContactPersonInformation() { + try { + return pvpConfiguration.getIdpContacts(); + + } catch (final EaafException e) { + log.warn("Can not load Metadata entry: Contect Person", e); + return null; + + } + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getOrgansiationInformation() + */ + @Override + public Organization getOrgansiationInformation() { + try { + return pvpConfiguration.getIdpOrganisation(); + + } catch (final EaafException e) { + log.warn("Can not load Metadata entry: Organisation", e); + return null; + + } + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getMetadataSigningCredentials() + */ + @Override + public EaafX509Credential getMetadataSigningCredentials() throws CredentialsNotAvailableException { + return credentialProvider.getMetaDataSigningCredential(); + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getRequestorResponseSigningCredentials() + */ + @Override + public Credential getRequestorResponseSigningCredentials() throws CredentialsNotAvailableException { + return credentialProvider.getMessageSigningCredential(); + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getEncryptionCredentials() + */ + @Override + public Credential getEncryptionCredentials() throws CredentialsNotAvailableException { + return credentialProvider.getMessageEncryptionCredential(); + + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getIDPWebSSOPostBindingURL() + */ + @Override + public String getIdpWebSsoPostBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getIDPWebSSORedirectBindingURL() + */ + @Override + public String getIdpWebSsoRedirectBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getIDPSLOPostBindingURL() + */ + @Override + public String getIdpSloPostBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getIDPSLORedirectBindingURL() + */ + @Override + public String getIdpSloRedirectBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPAssertionConsumerServicePostBindingURL() + */ + @Override + public String getSpAssertionConsumerServicePostBindingUrl() { + return authUrl + IdAustriaAuthConstants.ENDPOINT_POST; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPAssertionConsumerServiceRedirectBindingURL() + */ + @Override + public String getSpAssertionConsumerServiceRedirectBindingUrl() { + return authUrl + IdAustriaAuthConstants.ENDPOINT_REDIRECT; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPSLOPostBindingURL() + */ + @Override + public String getSpSloPostBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPSLORedirectBindingURL() + */ + @Override + public String getSpSloRedirectBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPSLOSOAPBindingURL() + */ + @Override + public String getSpSloSoapBindingUrl() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getIDPPossibleAttributes() + */ + @Override + public List getIdpPossibleAttributes() { + return null; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getIDPPossibleNameITTypes() + */ + @Override + public List getIdpPossibleNameIdTypes() { + return null; + } + + + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPRequiredAttributes() + */ + @Override + public Collection getSpRequiredAttributes() { + final Map requestedAttributes = new HashMap<>(); + log.trace("Build required attributes for ID Austria operaton ... "); + injectDefinedAttributes(requestedAttributes, + IdAustriaAuthConstants.DEFAULT_REQUIRED_PVP_ATTRIBUTES); + + + if (additionalAttributes != null) { + log.trace("Add additional PVP attributes into metadata ... "); + for (final RequestedAttribute el : additionalAttributes) { + if (requestedAttributes.containsKey(el.getName())) { + log.debug("Attribute " + el.getName() + + " is already added by default configuration. Overwrite it by user configuration"); + } + + requestedAttributes.put(el.getName(), el); + + } + } + + return requestedAttributes.values(); + + } + + + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# + * getSPAllowedNameITTypes() + */ + @Override + public List getSpAllowedNameIdTypes() { + return Arrays.asList(NameIDType.PERSISTENT); + + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPMetadataBuilderConfiguration#getSPNameForLogging() + */ + @Override + public String getSpNameForLogging() { + return IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPMetadataBuilderConfiguration#wantAssertionSigned() + */ + @Override + public boolean wantAssertionSigned() { + return false; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPMetadataBuilderConfiguration#wantAuthnRequestSigned() + */ + @Override + public boolean wantAuthnRequestSigned() { + return true; + } + + /** + * Add additonal PVP attributes that are required by this deployment. + * + * @param additionalAttr List of PVP attribute name and isRequired flag + */ + public void setAdditionalRequiredAttributes(List> additionalAttr) { + if (additionalAttr != null && !additionalAttr.isEmpty()) { + additionalAttributes = new ArrayList<>(); + for (final Pair el : additionalAttr) { + final Attribute attributBuilder = PvpAttributeBuilder.buildEmptyAttribute(el.getFirst()); + if (attributBuilder != null) { + additionalAttributes.add( + PvpAttributeBuilder.buildReqAttribute( + attributBuilder.getName(), + attributBuilder.getFriendlyName(), + el.getSecond())); + + } else { + log.info("NO PVP attribute with name: " + el.getFirst()); + } + + } + } + } + + private void injectDefinedAttributes(Map requestedAttributes, + List> attributes) { + for (final Triple el : attributes) { + requestedAttributes.put(el.getFirst(), PvpAttributeBuilder.buildReqAttribute(el.getFirst(), el + .getSecond(), el.getThird())); + + } + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthRequestBuilderConfiguration.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthRequestBuilderConfiguration.java new file mode 100644 index 00000000..52bd1c5f --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/config/IdAustriaAuthRequestBuilderConfiguration.java @@ -0,0 +1,307 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.config; + +import java.util.List; + +import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml.saml2.core.NameIDType; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.w3c.dom.Element; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.sp.api.IPvpAuthnRequestBuilderConfiguruation; + +/** + * ID Austria client-specific implementation of an {@link IPvpAuthnRequestBuilderConfiguruation}. + * + * @author tlenz + * + */ +public class IdAustriaAuthRequestBuilderConfiguration implements IPvpAuthnRequestBuilderConfiguruation { + + private boolean isPassive; + private String spEntityId; + private String qaaLevel; + private EntityDescriptor idpEntity; + private EaafX509Credential signCred; + private String scopeRequesterId; + private String providerName; + private List requestedAttributes; + private String reqId; + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#isPassivRequest() + */ + @Override + public Boolean isPassivRequest() { + return this.isPassive; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getAssertionConsumerServiceId() + */ + @Override + public Integer getAssertionConsumerServiceId() { + return 0; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getEntityID() + */ + @Override + public String getSpEntityID() { + return this.spEntityId; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getNameIDPolicy() + */ + @Override + public String getNameIdPolicyFormat() { + return NameIDType.PERSISTENT; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getNameIDPolicy() + */ + @Override + public boolean getNameIdPolicyAllowCreation() { + return true; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getAuthnContextClassRef() + */ + @Override + public String getAuthnContextClassRef() { + return this.qaaLevel; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getAuthnContextComparison() + */ + @Override + public AuthnContextComparisonTypeEnumeration getAuthnContextComparison() { + return AuthnContextComparisonTypeEnumeration.MINIMUM; + } + + /** + * Set isPassive flag in SAML2 request. + * + * @param isPassive the isPassive to set. + */ + public void setPassive(boolean isPassive) { + this.isPassive = isPassive; + } + + /** + * Set the requester EntityId. + * + * @param spEntityId EntityId of SP + */ + public void setSpEntityID(String spEntityId) { + this.spEntityId = spEntityId; + } + + /** + * Set required LoA. + * + * @param loa the LoA to set. + */ + public void setRequestedLoA(String loa) { + qaaLevel = loa; + } + + /** + * Set EntityId of IDP. + * + * @param idpEntity the idpEntity to set. + */ + public void setIdpEntity(EntityDescriptor idpEntity) { + this.idpEntity = idpEntity; + } + + /** + * Set message signing credentials. + * + * @param signCred the signCred to set. + */ + public void setSignCred(EaafX509Credential signCred) { + this.signCred = signCred; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getAuthnRequestSigningCredential() + */ + @Override + public EaafX509Credential getAuthnRequestSigningCredential() { + return this.signCred; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getIDPEntityDescriptor() + */ + @Override + public EntityDescriptor getIdpEntityDescriptor() { + return this.idpEntity; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getSubjectNameID() + */ + @Override + public String getSubjectNameID() { + return null; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getSPNameForLogging() + */ + @Override + public String getSpNameForLogging() { + return IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getSubjectNameIDFormat() + */ + @Override + public String getSubjectNameIdFormat() { + return null; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getRequestID() + */ + @Override + public String getRequestID() { + return this.reqId; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getSubjectNameIDQualifier() + */ + @Override + public String getSubjectNameIdQualifier() { + return null; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getSubjectConformationMethode() + */ + @Override + public String getSubjectConformationMethode() { + return null; + } + + /* + * (non-Javadoc) + * + * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. + * IPVPAuthnRequestBuilderConfiguruation#getSubjectConformationDate() + */ + @Override + public Element getSubjectConformationDate() { + return null; + } + + @Override + public List getRequestedAttributes() { + return this.requestedAttributes; + + } + + @Override + public String getProviderName() { + return this.providerName; + } + + @Override + public String getScopeRequesterId() { + return this.scopeRequesterId; + } + + /** + * Set the entityId of the SP that requests the proxy for eIDAS authentication. + * + * @param scopeRequesterId RequestId in SAML2 Proxy extension + */ + public void setScopeRequesterId(String scopeRequesterId) { + this.scopeRequesterId = scopeRequesterId; + } + + /** + * Set a friendlyName for the SP that requests the proxy for eIDAS + * authentication. + * + * @param providerName SAML2 provider-name attribute-value + */ + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + /** + * Set a Set of PVP attributes that a requested by using requested attributes. + * + * @param requestedAttributes Requested SAML2 attributes + */ + public void setRequestedAttributes(List requestedAttributes) { + this.requestedAttributes = requestedAttributes; + } + + /** + * Set a RequestId for this Authn. Request. + * + * @param reqId SAML2 message requestId + */ + public void setRequestId(String reqId) { + this.reqId = reqId; + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthMetadataController.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthMetadataController.java new file mode 100644 index 00000000..ad708d30 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthMetadataController.java @@ -0,0 +1,150 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.controller; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import com.google.common.net.MediaType; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.asitplus.eidas.specific.modules.auth.idaustria.config.IdAustriaAuthMetadataConfiguration; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.IdAustriaAuthCredentialProvider; +import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.HttpUtils; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; +import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; +import at.gv.egiz.eaaf.modules.pvp2.api.IPvp2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpMetadataBuilder; +import lombok.extern.slf4j.Slf4j; + +/** + * Controller that generates SAML2 metadata for ID Austria authentication client. + * + * @author tlenz + * + */ +@Slf4j +@Controller +public class IdAustriaAuthMetadataController extends AbstractController { + + private static final String ERROR_CODE_INTERNAL_00 = "eaaf.core.00"; + + @Autowired + PvpMetadataBuilder metadatabuilder; + @Autowired + IdAustriaAuthCredentialProvider credentialProvider; + @Autowired + IPvp2BasicConfiguration pvpConfiguration; + + /** + * Default construction with logging. + * + */ + public IdAustriaAuthMetadataController() { + super(); + log.debug("Registering servlet " + getClass().getName() + + " with mappings '" + IdAustriaAuthConstants.ENDPOINT_METADATA + + "'."); + + } + + /** + * End-point that produce PVP2 metadata for ID Austria authentication client. + * + * @param req http Request + * @param resp http Response + * @throws IOException In case of an I/O error + * @throws EaafException In case of a metadata generation error + */ + @RequestMapping(value = IdAustriaAuthConstants.ENDPOINT_METADATA, + method = { RequestMethod.GET }) + public void getSpMetadata(HttpServletRequest req, HttpServletResponse resp) throws IOException, + EaafException { + // check PublicURL prefix + try { + final String authUrl = getAuthUrlFromHttpContext(req); + + // initialize metadata builder configuration + final IdAustriaAuthMetadataConfiguration metadataConfig = + new IdAustriaAuthMetadataConfiguration(authUrl, credentialProvider, pvpConfiguration); + metadataConfig.setAdditionalRequiredAttributes(getAdditonalRequiredAttributes()); + + // build metadata + final String xmlMetadata = metadatabuilder.buildPvpMetadata(metadataConfig); + + // write response + final byte[] content = xmlMetadata.getBytes("UTF-8"); + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentLength(content.length); + resp.setContentType(MediaType.XML_UTF_8.toString()); + resp.getOutputStream().write(content); + + } catch (final Exception e) { + log.warn("Build PVP metadata for ID Austria client FAILED.", e); + protAuthService.handleErrorNoRedirect(e, req, resp, false); + + } + + } + + private String getAuthUrlFromHttpContext(HttpServletRequest req) throws EaafException { + // check if End-Point is valid + final String authUrlString = HttpUtils.extractAuthUrlFromRequest(req); + URL authReqUrl; + try { + authReqUrl = new URL(authUrlString); + + } catch (final MalformedURLException e) { + log.warn("Requested URL: {} is not a valid URL.", authUrlString); + throw new EaafAuthenticationException(ERROR_CODE_INTERNAL_00, new Object[] { authUrlString }, e); + + } + + final String idpAuthUrl = authConfig.validateIdpUrl(authReqUrl); + if (idpAuthUrl == null) { + log.warn("Requested URL: {} is NOT found in configuration.", authReqUrl); + throw new EaafAuthenticationException(ERROR_CODE_INTERNAL_00, new Object[] { authUrlString }); + + } + + return idpAuthUrl; + } + + private List> getAdditonalRequiredAttributes() { + final List> result = new ArrayList<>(); + // load attributes from configuration + final Map addReqAttributes = authConfig.getBasicConfigurationWithPrefix( + IdAustriaAuthConstants.CONFIG_PROPS_REQUIRED_PVP_ATTRIBUTES_LIST); + for (final String el : addReqAttributes.values()) { + if (StringUtils.isNotEmpty(el)) { + log.trace("Parse additional attr. definition: " + el); + final List attr = KeyValueUtils.getListOfCsvValues(el.trim()); + if (attr.size() == 2) { + result.add(Pair.newInstance(attr.get(0), Boolean.parseBoolean(attr.get(1)))); + + } else { + log.info("IGNORE additional attr. definition: " + el + + " Reason: Format not valid"); + } + } + } + + return result; + + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthSignalController.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthSignalController.java new file mode 100644 index 00000000..2e7868fd --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/controller/IdAustriaAuthSignalController.java @@ -0,0 +1,94 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractProcessEngineSignalController; +import lombok.extern.slf4j.Slf4j; + +/** + * Controller that receives the response from ID Austria system. + * + * @author tlenz + * + */ +@Slf4j +@Controller +public class IdAustriaAuthSignalController extends AbstractProcessEngineSignalController { + + public static final String HTTP_PARAM_RELAYSTATE = "RelayState"; + + /** + * Default constructor with logging. + * + */ + public IdAustriaAuthSignalController() { + super(); + log.debug("Registering servlet " + getClass().getName() + + " with mappings '" + IdAustriaAuthConstants.ENDPOINT_POST + + "' and '" + IdAustriaAuthConstants.ENDPOINT_REDIRECT + "'."); + + } + + /** + * HTTP end-point for incoming SAML2 Respone from ID Austrian System. + * + * @param req HTTP request + * @param resp HTTP response + * @throws IOException In case of a HTTP communication error + * @throws EaafException In case of a state-validation problem + */ + @RequestMapping(value = { IdAustriaAuthConstants.ENDPOINT_POST, + IdAustriaAuthConstants.ENDPOINT_REDIRECT }, + method = { RequestMethod.POST, RequestMethod.GET }) + public void performEidasAuthentication(HttpServletRequest req, HttpServletResponse resp) + throws IOException, EaafException { + signalProcessManagement(req, resp); + + } + + /** + * Read the PendingRequestId from SAML2 RelayState parameter. + */ + @Override + public String getPendingRequestId(HttpServletRequest request) { + String relayState = StringEscapeUtils.escapeHtml4(request.getParameter(HTTP_PARAM_RELAYSTATE)); + if (StringUtils.isNotEmpty(relayState)) { + try { + String pendingReqId = transactionStorage.get(relayState, String.class); + if (StringUtils.isNotEmpty(pendingReqId)) { + + return pendingReqId; + + } else { + log.info("SAML2 RelayState from request is unknown. Can NOT restore session ... "); + + } + + } catch (EaafException e) { + log.error("Can NOT map SAML2 RelayState to pendingRequestId", e); + + } finally { + transactionStorage.remove(relayState); + + } + + } else { + log.info("No SAML2 relaystate. Can NOT restore session ... "); + + } + + return null; + + } +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/ReceiveFromIdAustriaSystemTask.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/ReceiveFromIdAustriaSystemTask.java new file mode 100644 index 00000000..e486b851 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/ReceiveFromIdAustriaSystemTask.java @@ -0,0 +1,393 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.tasks; + +import java.io.IOException; +import java.util.Set; + +import javax.naming.ConfigurationException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.TransformerException; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.messaging.decoder.MessageDecodingException; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.IdAustriaAuthCredentialProvider; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.IdAustriaAuthMetadataProvider; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.Utils; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException; +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.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlAssertionValidationExeption; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileResponse; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EaafUriCompare; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SamlVerificationEngine; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionValidationExeption; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnResponseValidationException; +import at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils.AssertionAttributeExtractor; +import lombok.extern.slf4j.Slf4j; + +/** + * ID Austria authentication task that receives the SAML2 response from ID + * Austria system. + * + * @author tlenz + * + */ +@Slf4j +public class ReceiveFromIdAustriaSystemTask extends AbstractAuthServletTask { + + private static final String ERROR_PVP_03 = "sp.pvp2.03"; + private static final String ERROR_PVP_05 = "sp.pvp2.05"; + private static final String ERROR_PVP_06 = "sp.pvp2.06"; + private static final String ERROR_PVP_08 = "sp.pvp2.08"; + private static final String ERROR_PVP_10 = "sp.pvp2.10"; + private static final String ERROR_PVP_11 = "sp.pvp2.11"; + private static final String ERROR_PVP_12 = "sp.pvp2.12"; + + private static final String ERROR_MSG_00 = + "Receive INVALID PVP Response from federated IDP"; + private static final String ERROR_MSG_01 = + "Processing PVP response from 'ms-specific eIDAS node' FAILED."; + private static final String ERROR_MSG_02 = + "PVP response decrytion FAILED. No credential found."; + private static final String ERROR_MSG_03 = + "PVP response validation FAILED."; + + @Autowired + private SamlVerificationEngine samlVerificationEngine; + @Autowired + private IdAustriaAuthCredentialProvider credentialProvider; + @Autowired(required = true) + IdAustriaAuthMetadataProvider metadataProvider; + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.auth.modules.AbstractAuthServletTask#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 { + InboundMessage msg = null; + + try { + + IDecoder decoder = null; + EaafUriCompare comperator = null; + // select Response Binding + if (request.getMethod().equalsIgnoreCase("POST")) { + decoder = new PostBinding(); + comperator = new EaafUriCompare(pendingReq.getAuthUrl() + IdAustriaAuthConstants.ENDPOINT_POST); + log.trace("Receive PVP Response from 'ID Austria', by using POST-Binding."); + + } else if (request.getMethod().equalsIgnoreCase("GET")) { + decoder = new RedirectBinding(); + comperator = new EaafUriCompare(pendingReq.getAuthUrl() + + IdAustriaAuthConstants.ENDPOINT_REDIRECT); + log.trace("Receive PVP Response from 'ID Austria', by using Redirect-Binding."); + + } else { + log.warn("Receive PVP Response, but Binding (" + + request.getMethod() + ") is not supported."); + throw new AuthnResponseValidationException(ERROR_PVP_03, new Object[] { + IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING }); + + } + + // decode PVP response object + msg = (InboundMessage) decoder.decode( + request, response, metadataProvider, IDPSSODescriptor.DEFAULT_ELEMENT_NAME, + comperator); + + // validate response signature + if (!msg.isVerified()) { + samlVerificationEngine.verify(msg, + TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); + msg.setVerified(true); + + } + + // validate assertion + final Pair processedMsg = + preProcessAuthResponse((PvpSProfileResponse) msg); + + // check if SAML2 response contains user-stop decision + if (processedMsg.getSecond()) { + stopProcessFromUserDecision(executionContext, request, response); + + } else { + // validate entityId of response + final String idAustriaEntityID = + Utils.getIdAustriaEntityId(pendingReq.getServiceProviderConfiguration(), authConfig); + final String respEntityId = msg.getEntityID(); + if (!idAustriaEntityID.equals(respEntityId)) { + log.warn("Response Issuer is not a 'ID Austria System'. Stopping eIDAS authentication ..."); + throw new AuthnResponseValidationException(ERROR_PVP_08, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING, + msg.getEntityID() }); + + } + + // initialize Attribute extractor + final AssertionAttributeExtractor extractor = + new AssertionAttributeExtractor(processedMsg.getFirst().getResponse()); + + getAuthDataFromInterfederation(extractor); + + // set NeedConsent to false, because user gives consont during authentication + pendingReq.setNeedUserConsent(false); + + // store pending-request + requestStoreage.storePendingRequest(pendingReq); + + // write log entries + // revisionsLogger.logEvent(pendingReq, + // EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_MDS_VALID); + log.info("Receive a valid assertion from IDP " + msg.getEntityID()); + + } + + } catch (final AuthnResponseValidationException e) { + throw new TaskExecutionException(pendingReq, ERROR_MSG_03, e); + + } catch (MessageDecodingException | SecurityException | SamlSigningException e) { + final String samlRequest = request.getParameter("SAMLRequest"); + log.debug("Receive INVALID PVP Response from 'ID Austria System': {}", + samlRequest, null, e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_00, + new AuthnResponseValidationException(ERROR_PVP_11, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING }, e)); + + } catch (IOException | MarshallingException | TransformerException e) { + log.debug("Processing PVP response from 'ID Austria System' FAILED.", e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_01, + new AuthnResponseValidationException(ERROR_PVP_12, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING, e.getMessage() }, + e)); + + } catch (final CredentialsNotAvailableException e) { + log.debug("PVP response decrytion FAILED. No credential found.", e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_02, + new AuthnResponseValidationException(ERROR_PVP_10, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING }, e)); + + } catch (final Exception e) { + log.debug("PVP response validation FAILED. Msg:" + e.getMessage(), e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_03, + new AuthnResponseValidationException(ERROR_PVP_12, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING, e.getMessage() }, e)); + + } + + } + + private void getAuthDataFromInterfederation(AssertionAttributeExtractor extractor) + throws EaafBuilderException, ConfigurationException { + + final Set requiredEidasNodeAttributes = + IdAustriaAuthConstants.DEFAULT_REQUIRED_PVP_ATTRIBUTE_NAMES; + try { + // check if all attributes are include + if (!extractor.containsAllRequiredAttributes() + || !extractor.containsAllRequiredAttributes( + requiredEidasNodeAttributes)) { + log.warn("PVP Response from 'ID Austria System' contains not all requested attributes."); + throw new AssertionValidationExeption(ERROR_PVP_06, new Object[] { + IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING }); + + } + + // copy attributes into MOASession + final AuthProcessDataWrapper session = pendingReq.getSessionData( + AuthProcessDataWrapper.class); + + // validate response attributes + validateResponseAttributes(extractor); + + // inject all attributes into session + final Set includedAttrNames = extractor.getAllIncludeAttributeNames(); + for (final String attrName : includedAttrNames) { + injectAuthInfosIntoSession(session, attrName, + extractor.getSingleAttributeValue(attrName)); + + } + + // set foreigner flag + session.setForeigner(false); + + // mark it as ID Austria process, because we have no baseId + session.setEidProcess(true); + + // set IssuerInstant from Assertion + session.setIssueInstant(extractor.getAssertionIssuingDate()); + + // set mandate flag + session.setUseMandates(checkIfMandateInformationIsAvailable(extractor)); + + + } catch (final EaafException | IOException e) { + throw new EaafBuilderException(ERROR_PVP_06, null, e.getMessage(), e); + + } + } + + + /** + * Check if mandate information is available. + * + * @param extractor Assertion from ID Austria system. + * @return true if mandate was used, otherwise false + */ + private boolean checkIfMandateInformationIsAvailable(AssertionAttributeExtractor extractor) { + boolean isMandateIncluded = extractor.containsAttribute(PvpAttributeDefinitions.MANDATE_TYPE_NAME); + log.debug("Response from ID-Austria system contains mandate information. Switch to mandate-mode ... "); + return isMandateIncluded; + + } + + private void validateResponseAttributes(AssertionAttributeExtractor extractor) + throws EaafAuthenticationException { + final String bpkTarget = extractor.getSingleAttributeValue( + PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME); + final String spTarget = pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(); + if (StringUtils.isNotEmpty(bpkTarget) && bpkTarget.equals(spTarget)) { + log.debug("Find attr: {} that matches to requested bPK target. bPK should be valid", + PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME); + + } else { + log.trace("Find not attr: {}. Validation bPK attribute ... ", + PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME); + final String bpk = extractor.getSingleAttributeValue(PvpAttributeDefinitions.BPK_NAME); + final String[] split = bpk.split(":", 2); + if (split.length == 2) { + if (!spTarget.endsWith(split[0])) { + log.error("bPK from response: {} does not match to SP target: {}", bpk, spTarget); + throw new EaafAuthenticationException(IdAustriaAuthConstants.ERRORTYPE_06, null); + + } else { + log.trace("Prefix of PVP bPK attribte matches to SP configuration. bPK looks valid"); + + } + + } else { + log.warn("Find suspect bPK that has no prefix. Use it as it is ... "); + + } + } + } + + private void injectAuthInfosIntoSession(AuthProcessDataWrapper session, + String attrName, String attrValue) throws EaafStorageException, IOException { + log.trace("Inject attribute: {} with value: {} into AuthSession", attrName, attrValue); + log.debug("Inject attribute: {} into AuthSession", attrName); + if (PvpAttributeDefinitions.BPK_NAME.equals(attrName)) { + log.trace("Find bPK attribute. Extract eIDAS identifier ... "); + session.setGenericDataToSession(MsEidasNodeConstants.ATTR_EIDAS_PERSONAL_IDENTIFIER, + extractBpkFromResponse(attrValue)); + + } else { + session.setGenericDataToSession(attrName, attrValue); + + } + + } + + private String extractBpkFromResponse(String pvpBpkAttrValue) { + final String[] split = pvpBpkAttrValue.split(":", 2); + if (split.length == 2) { + return split[1]; + + } else { + log.warn("PVP bPK attribute: {} has wrong format. Use it as it is.", pvpBpkAttrValue); + return pvpBpkAttrValue; + + } + } + + private Pair preProcessAuthResponse(PvpSProfileResponse msg) + throws IOException, MarshallingException, TransformerException, + CredentialsNotAvailableException, AuthnResponseValidationException, SamlAssertionValidationExeption { + log.debug("Start PVP-2x assertion processing... "); + final Response samlResp = (Response) msg.getResponse(); + + // check SAML2 response status-code + if (samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS)) { + // validate PVP 2.1 assertion + samlVerificationEngine.validateAssertion(samlResp, + credentialProvider.getMessageEncryptionCredential(), + pendingReq.getAuthUrl() + IdAustriaAuthConstants.ENDPOINT_METADATA, + IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING); + + msg.setSamlMessage(Saml2Utils.asDomDocument(samlResp).getDocumentElement()); + // revisionsLogger.logEvent(pendingReq, + // EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_RECEIVED, + // samlResp.getID()); + return Pair.newInstance(msg, false); + + } else { + log.info("Receive StatusCode " + samlResp.getStatus().getStatusCode().getValue() + + " from 'ms-specific eIDAS node'."); + final StatusCode subStatusCode = getSubStatusCode(samlResp); + if (subStatusCode != null + && IdAustriaAuthConstants.SAML2_STATUSCODE_USERSTOP.equals(subStatusCode.getValue())) { + log.info("Find 'User-Stop operation' in SAML2 response. Stopping authentication process ... "); + return Pair.newInstance(msg, true); + + } + + // revisionsLogger.logEvent(pendingReq, + // EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_RECEIVED_ERROR); + throw new AuthnResponseValidationException(ERROR_PVP_05, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING, + samlResp.getIssuer().getValue(), + samlResp.getStatus().getStatusCode().getValue(), + samlResp.getStatus().getStatusMessage().getValue() }); + + } + + } + + /** + * Get SAML2 Sub-StatusCode if not null. + * + * @param samlResp SAML2 response + * @return Sub-StatusCode or null if it's not set + */ + private StatusCode getSubStatusCode(Response samlResp) { + if (samlResp.getStatus().getStatusCode().getStatusCode() != null + && StringUtils.isNotEmpty(samlResp.getStatus().getStatusCode().getStatusCode().getValue())) { + return samlResp.getStatus().getStatusCode().getStatusCode(); + + } + return null; + + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/RequestIdAustriaSystemTask.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/RequestIdAustriaSystemTask.java new file mode 100644 index 00000000..66aadde6 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/tasks/RequestIdAustriaSystemTask.java @@ -0,0 +1,205 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.tasks; + +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.messaging.encoder.MessageEncodingException; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.core.config.ServiceProviderConfiguration; +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.asitplus.eidas.specific.modules.auth.idaustria.config.IdAustriaAuthRequestBuilderConfiguration; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.IdAustriaAuthCredentialProvider; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.IdAustriaAuthMetadataProvider; +import at.asitplus.eidas.specific.modules.auth.idaustria.utils.Utils; +import at.asitplus.eidas.specific.modules.msproxyservice.protocol.ProxyServicePendingRequest; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.ExtendedPvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpAttributeBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnRequestBuildException; +import at.gv.egiz.eaaf.modules.pvp2.sp.impl.PvpAuthnRequestBuilder; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy; + +/** + * eIDAS Authentication task that generates PVP2 S-Profile request to central + * Austrian MS-Connector. + * + * @author tlenz + * + */ +@Slf4j +public class RequestIdAustriaSystemTask extends AbstractAuthServletTask { + + private static final String ERROR_PVP_02 = "sp.pvp2.02"; + private static final String ERROR_PVP_13 = "sp.pvp2.13"; + + private static final String ERROR_MSG_1 = + "Requested 'ms-specific eIDAS node' {0} has no valid metadata or metadata is not found"; + private static final String ERROR_MSG_4 = + "Build PVP2.1 AuthnRequest to connect 'ms-specific eIDAS node' FAILED."; + + @Autowired PvpAuthnRequestBuilder authnReqBuilder; + @Autowired IdAustriaAuthCredentialProvider credential; + @Autowired IdAustriaAuthMetadataProvider metadataService; + @Autowired ITransactionStorage transactionStorage; + + @Override + public void execute(ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) + throws TaskExecutionException { + try { + //revisionsLogger.logEvent(pendingReq, EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_SELECTED); + + // get entityID for central ID Austria system + final String idAustriaEntityID = + Utils.getIdAustriaEntityId(pendingReq.getServiceProviderConfiguration(), authConfig); + if (StringUtils.isEmpty(idAustriaEntityID)) { + log.info("ID Austria authentication not possible -> NO EntityID for central central ID Austria System FOUND!"); + throw new EaafConfigurationException(IdAustriaAuthConstants.ERRORTYPE_00, + new Object[] { IdAustriaAuthConstants.CONFIG_PROPS_IDAUSTRIA_ENTITYID }); + + } + + // load IDP SAML2 entitydescriptor + final EntityDescriptor entityDesc = metadataService.getEntityDescriptor(idAustriaEntityID); + if (entityDesc == null) { + throw new EaafConfigurationException(IdAustriaAuthConstants.ERRORTYPE_05, + new Object[] { MessageFormat.format(ERROR_MSG_1, idAustriaEntityID) }); + + } + + // setup AuthnRequestBuilder configuration + final IdAustriaAuthRequestBuilderConfiguration authnReqConfig = + new IdAustriaAuthRequestBuilderConfiguration(); + final SecureRandomIdentifierGenerationStrategy gen = + new SecureRandomIdentifierGenerationStrategy(); + + // set basic infos + authnReqConfig.setRequestId(gen.generateIdentifier()); + authnReqConfig.setIdpEntity(entityDesc); + authnReqConfig.setPassive(false); + authnReqConfig.setSignCred(credential.getMessageSigningCredential()); + authnReqConfig.setSpEntityID(pendingReq.getAuthUrl() + IdAustriaAuthConstants.ENDPOINT_METADATA); + + // set eIDAS Proxy-Service specific information for ID Austria system + authnReqConfig.setRequestedAttributes(buildRequestedAttributes(pendingReq)); + + + /*build relayState for session synchronization, because SAML2 only allows RelayState with 80 characters + * but encrypted PendingRequestId is much longer. + */ + String relayState = Random.nextProcessReferenceValue(); + transactionStorage.put(relayState, pendingReq.getPendingRequestId(), -1); + + // build and transmit AuthnRequest + authnReqBuilder.buildAuthnRequest(pendingReq, authnReqConfig, relayState, response); + + //revisionsLogger.logEvent(pendingReq, + // EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_REQUESTED, + // authnReqConfig.getRequestID()); + + } catch (final EaafException e) { + throw new TaskExecutionException(pendingReq, e.getMessage(), e); + + } catch (final ResolverException e) { + throw new TaskExecutionException(pendingReq, + ERROR_MSG_4, + new AuthnRequestBuildException(ERROR_PVP_02, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING }, e)); + + } catch (MessageEncodingException | NoSuchAlgorithmException | SecurityException e) { + throw new TaskExecutionException(pendingReq, + e.getMessage(), + new AuthnRequestBuildException(ERROR_PVP_13, + new Object[] { IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING }, e)); + + } catch (final Exception e) { + throw new TaskExecutionException(pendingReq, e.getMessage(), e); + + } + } + + private String selectHighestLoa(List requiredLoA) { + //TODO: implement LoA selection + return requiredLoA.get(0); + + } + + private List buildRequestedAttributes(IRequest pendingReq) { + final List attributs = new ArrayList<>(); + + //build attribute that contains the unique identifier of the eIDAS-Connector + injectAttribute(attributs, ExtendedPvpAttributeDefinitions.EIDAS_CONNECTOR_UNIQUEID_NAME, + pendingReq.getServiceProviderConfiguration().getUniqueIdentifier()); + + // build EID sector for identification attribute + injectAttribute(attributs, PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, + pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()); + + // set requested LoA as attribute + injectAttribute(attributs, PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, + selectHighestLoa(pendingReq.getServiceProviderConfiguration().getRequiredLoA())); + + //set ProviderName if available + String providerName = ((ProxyServicePendingRequest)pendingReq).getEidasRequest().getProviderName(); + if (StringUtils.isNotEmpty(providerName)) { + injectAttribute(attributs, ExtendedPvpAttributeDefinitions.SP_FRIENDLYNAME_NAME, providerName); + + } + + //set ProviderName if available + String requesterId = ((ProxyServicePendingRequest)pendingReq).getEidasRequest().getRequesterId(); + if (StringUtils.isNotEmpty(requesterId)) { + injectAttribute(attributs, ExtendedPvpAttributeDefinitions.SP_UNIQUEID_NAME, requesterId); + + } + + //set mandate profiles + List mandateProfiles = + pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).getMandateProfiles(); + if (mandateProfiles != null && !mandateProfiles.isEmpty()) { + log.debug("Set mandate-profiles attribute into ID-Austria request"); + injectAttribute(attributs, ExtendedPvpAttributeDefinitions.SP_USED_MANDATE_PROFILES_NAME, + StringUtils.join(mandateProfiles, ",")); + + } + + // inject mandate mode attribute + injectAttribute(attributs, ExtendedPvpAttributeDefinitions.SP_USED_MANDATE_TYPE_NAME, + pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).getMandateMode().getMode()); + + + return attributs; + } + + private void injectAttribute(List attributs, String attributeName, String attributeValue) { + final Attribute requesterIdAttr = PvpAttributeBuilder.buildEmptyAttribute(attributeName); + final EaafRequestedAttribute requesterIdReqAttr = Saml2Utils.generateReqAuthnAttributeSimple( + requesterIdAttr, + true, + attributeValue); + attributs.add(requesterIdReqAttr); + + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthCredentialProvider.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthCredentialProvider.java new file mode 100644 index 00000000..7da306b5 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthCredentialProvider.java @@ -0,0 +1,130 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.utils; + +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +/** + * Credential provider for ID Austria PVP S-Profile client. + * + * @author tlenz + * + */ +public class IdAustriaAuthCredentialProvider extends AbstractCredentialProvider { + + @Autowired + IConfiguration authConfig; + + + @Override + public KeyStoreConfiguration getBasicKeyStoreConfig() throws EaafConfigurationException { + final KeyStoreConfiguration keyStoreConfig = new KeyStoreConfiguration(); + keyStoreConfig.setFriendlyName(IdAustriaAuthConstants.MODULE_NAME_FOR_LOGGING); + keyStoreConfig.setKeyStoreType( + authConfig.getBasicConfiguration(IdAustriaAuthConstants.CONFIG_PROPS_KEYSTORE_TYPE, + KeyStoreType.PKCS12.getKeyStoreType())); + keyStoreConfig.setKeyStoreName( + authConfig.getBasicConfiguration(IdAustriaAuthConstants.CONFIG_PROPS_KEYSTORE_NAME)); + keyStoreConfig.setSoftKeyStoreFilePath(getKeyStoreFilePath()); + keyStoreConfig.setSoftKeyStorePassword( + authConfig.getBasicConfiguration(IdAustriaAuthConstants.CONFIG_PROPS_KEYSTOREPASSWORD)); + + return keyStoreConfig; + + } + + private String getKeyStoreFilePath() throws EaafConfigurationException { + final String path = authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_KEYSTORE_PATH); + if (path == null) { + throw new EaafConfigurationException(IdAustriaAuthConstants.ERRORTYPE_00, + new Object[] { IdAustriaAuthConstants.CONFIG_PROPS_KEYSTORE_PATH }); + + } + return path; + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# + * getMetadataKeyAlias() + */ + @Override + public String getMetadataKeyAlias() { + return authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_SIGN_METADATA_ALIAS); + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# + * getMetadataKeyPassword() + */ + @Override + public String getMetadataKeyPassword() { + return authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_SIGN_METADATA_KEY_PASSWORD); + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# + * getSignatureKeyAlias() + */ + @Override + public String getSignatureKeyAlias() { + return authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_SIGN_SIGNING_ALIAS); + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# + * getSignatureKeyPassword() + */ + @Override + public String getSignatureKeyPassword() { + return authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_SIGN_SIGNING_KEY_PASSWORD); + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# + * getEncryptionKeyAlias() + */ + @Override + public String getEncryptionKeyAlias() { + return authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_ENCRYPTION_ALIAS); + } + + /* + * (non-Javadoc) + * + * @see + * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# + * getEncryptionKeyPassword() + */ + @Override + public String getEncryptionKeyPassword() { + return authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_ENCRYPTION_KEY_PASSWORD); + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthMetadataProvider.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthMetadataProvider.java new file mode 100644 index 00000000..72c52488 --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/IdAustriaAuthMetadataProvider.java @@ -0,0 +1,168 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.utils; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.CertificateException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml.metadata.resolver.MetadataResolver; +import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; +import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.AbstractChainingMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.PvpMetadataResolverFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.SchemaValidationFilter; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.SimpleMetadataSignatureVerificationFilter; +import lombok.extern.slf4j.Slf4j; + +/** + * SAML2 metadata-provider implementation for ID Austria client. + * + * @author tlenz + * + */ +@Slf4j +public class IdAustriaAuthMetadataProvider extends AbstractChainingMetadataProvider { + + private static final String FRIENDLYNAME_METADATA_TRUSTSTORE = "'ID Austria client-metadata truststore'"; + private static final String PROVIDER_ID_PATTERN = "ID Austria resolver: {0}"; + public static final String PROVIDER_ID = "ID Austria_client metadata provider'"; + + @Autowired + private IConfiguration basicConfig; + + @Autowired + private PvpMetadataResolverFactory metadataProviderFactory; + @Autowired + private IHttpClientFactory httpClientFactory; + + @Autowired + private EaafKeyStoreFactory keyStoreFactory; + + private Pair metadataSigningTrustStore; + + @Override + protected String getMetadataUrl(String entityId) throws EaafConfigurationException { + log.trace("ID Austria uses SAML2 well-known location approach. EntityId is Metadata-URL"); + return entityId; + + } + + @Override + protected MetadataResolver createNewMetadataProvider(String entityId) throws EaafConfigurationException, + IOException, CertificateException { + final List filterList = new ArrayList<>(); + filterList.add(new SchemaValidationFilter(true)); + filterList.add(new SimpleMetadataSignatureVerificationFilter( + metadataSigningTrustStore.getFirst(), entityId)); + + final MetadataFilterChain filter = new MetadataFilterChain(); + filter.setFilters(filterList); + + try { + return metadataProviderFactory.createMetadataProvider(getMetadataUrl(entityId), + filter, + MessageFormat.format(PROVIDER_ID_PATTERN, entityId), + httpClientFactory.getHttpClient()); + + } catch (final Pvp2MetadataException e) { + log.info("Can NOT build metadata provider for entityId: {}", entityId); + throw new EaafConfigurationException(IdAustriaAuthConstants.ERRORTYPE_04, + new Object[] { entityId, e.getMessage() }, e); + + } + } + + @Override + protected List getAllMetadataUrlsFromConfiguration() throws EaafConfigurationException { + return Collections.emptyList(); + + } + + @Override + protected String getMetadataProviderId() { + return PROVIDER_ID; + + } + + @Override + public void runGarbageCollector() { + log.trace("Garbage collection is NOT supported by: {}", getId()); + } + + @Override + public void doDestroy() { + super.fullyDestroy(); + + } + + @PostConstruct + private void initialize() throws EaafException { + // initialize truststore to validate metadata signing certificates + initializeTrustStore(); + + // load metadata with metadataURL, as backup + initializeFileSystemMetadata(); + + } + + private void initializeFileSystemMetadata() { + try { + final String metadataUrl = basicConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_IDAUSTRIA_METADATAURL); + if (StringUtils.isNotEmpty(metadataUrl)) { + log.info("Use not recommended metadata-provider initialization!" + + " SAML2 'Well-Known-Location' is the preferred methode."); + log.info("Initialize 'ID Austria' metadata-provider with URL: {}", metadataUrl); + + addMetadataResolverIntoChain(createNewMetadataProvider(metadataUrl)); + } + + } catch (final EaafConfigurationException | CertificateException | IOException e) { + log.warn("Can NOT inject static eIDAS Node metadata-soure.", e); + log.warn("Communication with ID Austria can be FAIL."); + + } + } + + private void initializeTrustStore() throws EaafException { + // set configuration + final KeyStoreConfiguration trustStoreConfig = new KeyStoreConfiguration(); + trustStoreConfig.setFriendlyName(FRIENDLYNAME_METADATA_TRUSTSTORE); + trustStoreConfig.setKeyStoreType(basicConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_TRUSTSTORE_TYPE, + KeyStoreType.JKS.getKeyStoreType())); + trustStoreConfig.setKeyStoreName(basicConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_TRUSTSTORE_NAME)); + trustStoreConfig.setSoftKeyStoreFilePath(basicConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_TRUSTSTORE_PATH)); + trustStoreConfig.setSoftKeyStorePassword(basicConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_TRUSTSTORE_PASSWORD)); + + // validate configuration + trustStoreConfig.validate(); + + // open new TrustStore + metadataSigningTrustStore = keyStoreFactory.buildNewKeyStore(trustStoreConfig); + + } + +} diff --git a/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/Utils.java b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/Utils.java new file mode 100644 index 00000000..1de6c33f --- /dev/null +++ b/modules/authmodule_id-austria/src/main/java/at/asitplus/eidas/specific/modules/auth/idaustria/utils/Utils.java @@ -0,0 +1,44 @@ +package at.asitplus.eidas.specific.modules.auth.idaustria.utils; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.StringUtils; + +import at.asitplus.eidas.specific.modules.auth.idaustria.IdAustriaAuthConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; + +public class Utils { + + /** + * Get the EntityId of the ID Austria system that + * should be used in this process. + * + * @param spConfiguration Service-Provider configuration that can include + * {@linkplain IdAustriaAuthConstants.CONFIG_PROPS_APPSPECIFIC_IDAUSTRIA_NODE_URL} + * @param authConfig Basic application configuration that include + * {@linkplain IdAustriaAuthConstants.CONFIG_PROPS_IDAUSTRIA_ENTITYID} + * @return EntityId, or null if no EntityId was found in configuration + */ + @Nullable + public static String getIdAustriaEntityId( + ISpConfiguration spConfiguration, IConfiguration authConfig) { + // load from service-provider configuration + String msNodeEntityID = spConfiguration.getConfigurationValue( + IdAustriaAuthConstants.CONFIG_PROPS_APPSPECIFIC_IDAUSTRIA_NODE_URL); + + if (StringUtils.isEmpty(msNodeEntityID)) { + msNodeEntityID = authConfig.getBasicConfiguration( + IdAustriaAuthConstants.CONFIG_PROPS_IDAUSTRIA_ENTITYID); + + } + + return msNodeEntityID; + } + + private Utils() { + //hide constructor of private class + + } + +} -- cgit v1.2.3