diff options
author | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2018-07-26 10:30:14 +0200 |
---|---|---|
committer | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2018-07-26 10:30:14 +0200 |
commit | 6d09f43225ba2e0f6d7b0583f843c858a1015807 (patch) | |
tree | e02827bd0ba88085efaaf28f56e86f06fa99f197 /connector/src/main/java/at/asitplus | |
parent | 9bf823366d5e8d0d9323b0dfddee2e2dc85c3b82 (diff) | |
download | National_eIDAS_Gateway-6d09f43225ba2e0f6d7b0583f843c858a1015807.tar.gz National_eIDAS_Gateway-6d09f43225ba2e0f6d7b0583f843c858a1015807.tar.bz2 National_eIDAS_Gateway-6d09f43225ba2e0f6d7b0583f843c858a1015807.zip |
namespace refactoring
Diffstat (limited to 'connector/src/main/java/at/asitplus')
25 files changed, 2148 insertions, 0 deletions
diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/MSSpecificeIDASNodeSpringResourceProvider.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/MSSpecificeIDASNodeSpringResourceProvider.java new file mode 100644 index 00000000..aa1a71e4 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/MSSpecificeIDASNodeSpringResourceProvider.java @@ -0,0 +1,29 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import at.gv.egiz.components.spring.api.SpringResourceProvider; + +public class MSSpecificeIDASNodeSpringResourceProvider implements SpringResourceProvider { + + @Override + public Resource[] getResourcesToLoad() { + ClassPathResource mseIDASNode = new ClassPathResource("/specific_eIDAS_connector.beans.xml", MSSpecificeIDASNodeSpringResourceProvider.class); + ClassPathResource mseIDASNodeStorage = new ClassPathResource("/specific_eIDAS_connector.storage.beans.xml", MSSpecificeIDASNodeSpringResourceProvider.class); + return new Resource[] {mseIDASNode, mseIDASNodeStorage}; + } + + @Override + public String[] getPackagesToScan() { + return null; + } + + @Override + public String getName() { + return "MS-specific eIDAS Node SpringResourceProvider"; + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/SpringInitializer.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/SpringInitializer.java new file mode 100644 index 00000000..2f77f9be --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/SpringInitializer.java @@ -0,0 +1,172 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector; + +import java.util.Arrays; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.request.RequestContextListener; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.context.support.ServletContextResource; +import org.springframework.web.servlet.DispatcherServlet; + +import at.gv.egiz.components.spring.api.SpringLoader; +import at.gv.egiz.eaaf.core.api.IStatusMessenger; +import at.gv.egiz.eaaf.core.impl.logging.LogMessageProviderFactory; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EAAFDefaultSAML2Bootstrap; + +/** + * Web application initializer + * + * @author Thomas Lenz + */ +public class SpringInitializer implements WebApplicationInitializer { + + private static final Logger log = LoggerFactory.getLogger(SpringInitializer.class); + + private String[] rootServletContexts = null; + private String[] servletContexts = null; + private String[] activeProfiles = null; + + public SpringInitializer() { + this.rootServletContexts = null; + this.servletContexts = new String[] { + "/applicationContext.xml", + + }; + this.activeProfiles = null; + } + + + /* (non-Javadoc) + * @see org.springframework.web.WebApplicationInitializer#onStartup(javax.servlet.ServletContext) + */ + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + try { + log.info("=============== Loading Config Root Context! ==============="); + ApplicationContext cfgRootContext = + new ClassPathXmlApplicationContext(new String[] { + "/applicationContext.xml" + }); + + + log.info("=============== Loading Root Context! ==============="); + GenericWebApplicationContext rootContext = new GenericWebApplicationContext(); + rootContext.setServletContext(servletContext); + rootContext.setParent(cfgRootContext); + +// log.info("=============== Setting active profiles! ==============="); +// if (this.activeProfiles != null) { +// for (String profile : this.activeProfiles) { +// rootContext.getEnvironment().addActiveProfile(profile); +// } +// } + + log.info("Spring-context was initialized with active profiles: " + + Arrays.asList(rootContext.getEnvironment().getActiveProfiles())); + + log.info("=============== Loading Local Contexts! ==============="); + XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader( + rootContext); + if (rootServletContexts != null) { + for (String rootServletContext : rootServletContexts) { + log.debug("Loading: "+ rootServletContext); + xmlReader.loadBeanDefinitions(new ServletContextResource( + servletContext, rootServletContext)); + } + } + // Manage the lifecycle of the root application context + servletContext.addListener(new ContextLoaderListener(rootContext)); + + // log.debug("Beans after logAMQP in {}", rootContext); + // dumpBeanDefinitions(rootContext); + + log.info("=============== Loading SPI Context! ==============="); + if (rootContext instanceof BeanDefinitionRegistry) { + log.debug("Loading modules and components"); + SpringLoader.loadSpringServices(rootContext); + + } else + log.warn("Failed to load external Spring since no BeanDefinitionRegistry"); + + log.trace("Beans after SPI in "+ rootContext); + dumpBeanDefinitions(rootContext); + + log.debug("Loading servlet config in "+ rootContext); + if (servletContexts != null) { + for (String servletContextString : servletContexts) + xmlReader.loadBeanDefinitions(new ClassPathResource(servletContextString, SpringInitializer.class)); + + } + + log.debug("Refreshing context "+ rootContext); + rootContext.refresh(); + + log.info("=============== Register Dispatcher Servlet! ==============="); + + log.trace("Final Beans in "+ rootContext); + dumpBeanDefinitions(rootContext); + + log.info("Registering dispatcher configuration"); + ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext)); + if (dispatcher != null) { + dispatcher.setLoadOnStartup(1); + dispatcher.addMapping("/"); + dispatcher.setAsyncSupported(true); + + } else + log.error("Failed to register dispatcher server in servlet context!"); + + + log.info("=============== Register RequestContextListener! ==============="); + servletContext.addListener(new RequestContextListener()); + + //initialize status messenger + LogMessageProviderFactory.setStatusMessager(rootContext.getBean(IStatusMessenger.class)); + + log.info("Bootstrap openSAML .... "); + EAAFDefaultSAML2Bootstrap.bootstrap(); + + log.info("Seed random number generator ... "); + Random.seedRandom(); + + log.info("Initialization of MS-specific eIDAS-connector finished."); + + + } catch (Throwable e) { + log.error("MS-specific eIDAS-connector initialization FAILED!", e); + + } + + } + + private void dumpBeanDefinitions(GenericApplicationContext context) { + log.trace("Registered Bean in context " + context.toString()); + + String[] registeredBeans = context.getBeanDefinitionNames(); + for (String registeredBean : registeredBeans) { + BeanDefinition beanDefinition = context + .getBeanDefinition(registeredBean); + log.trace(registeredBean + " -> " + beanDefinition.getBeanClassName()); + + } + + log.trace("Registered Bean in context --"+ context); + } +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/auth/AuthenticationManager.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/auth/AuthenticationManager.java new file mode 100644 index 00000000..7eee2388 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/auth/AuthenticationManager.java @@ -0,0 +1,38 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.auth; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.idp.slo.ISLOInformationContainer; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.impl.idp.auth.AbstractAuthenticationManager; +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; + +@Service("AuthenticationManager") +public class AuthenticationManager extends AbstractAuthenticationManager { + private static final Logger log = LoggerFactory.getLogger(AuthenticationManager.class); + + @Override + public ISLOInformationContainer performSingleLogOut(HttpServletRequest httpReq, HttpServletResponse httpResp, + IRequest pendingReq, String internalSSOId) throws EAAFException { + throw new RuntimeException("Single LogOut is NOT supported by this implementation"); + + } + + @Override + protected void populateExecutionContext(ExecutionContext executionContext, + RequestImpl pendingReq, HttpServletRequest httpReq) + throws EAAFException { + log.trace("No implementation-specific population of execution-context required ... "); + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/builder/AuthenticationDataBuilder.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/builder/AuthenticationDataBuilder.java new file mode 100644 index 00000000..35f0cc0e --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/builder/AuthenticationDataBuilder.java @@ -0,0 +1,77 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.builder; + +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.w3c.dom.DOMException; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IAuthProcessDataContainer; +import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException; +import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EAAFParserException; +import at.gv.egiz.eaaf.core.exceptions.XPathException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.AuthenticationData; +import at.gv.egiz.eaaf.core.impl.idp.auth.builder.AbstractAuthenticationDataBuilder; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; + +@Service("AuthenticationDataBuilder") +public class AuthenticationDataBuilder extends AbstractAuthenticationDataBuilder { + private static final Logger log = LoggerFactory.getLogger(AuthenticationDataBuilder.class); + + @Override + public IAuthData buildAuthenticationData(IRequest pendingReq) throws EAAFAuthenticationException { + + IAuthProcessDataContainer authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); + AuthenticationData authData = new AuthenticationData(); + + try { + generateBasicAuthData(authData, pendingReq, authProcessData); + + //set specific informations + authData.setSsoSessionValidTo(new Date(new Date().getTime() + + MSeIDASNodeConstants.DEFAULT_PVP_ASSERTION_VALIDITY * 60 * 1000)); + + } catch (EAAFBuilderException | EAAFParserException | EAAFConfigurationException + | XPathException | DOMException e) { + log.warn("Can not build authentication data from auth. process information"); + throw new EAAFAuthenticationException("builder.11", new Object[]{e.getMessage()}, e); + + } + + return authData; + + } + + @Override + protected Pair<String, String> buildOAspecificbPK(IRequest pendingReq, AuthenticationData authData) throws EAAFBuilderException { + //TODO: check if bPK already exists + + + return super.buildOAspecificbPK(pendingReq, authData); + + } + + @Override + protected Pair<String, String> getEncryptedbPKFromPVPAttribute(IAuthProcessDataContainer arg0, + AuthenticationData arg1, ISPConfiguration arg2) throws EAAFBuilderException { + return null; + + } + + @Override + protected Pair<String, String> getbaseIDFromSZR(AuthenticationData arg0, String arg1, String arg2) { + return null; + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/builder/PVPSubjectNameGenerator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/builder/PVPSubjectNameGenerator.java new file mode 100644 index 00000000..9fe80f67 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/builder/PVPSubjectNameGenerator.java @@ -0,0 +1,18 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.builder; + +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.idp.api.builder.ISubjectNameIdGenerator; + +public class PVPSubjectNameGenerator implements ISubjectNameIdGenerator { + + @Override + public Pair<String, String> generateSubjectNameId(IAuthData authData, ISPConfiguration spConfig) throws PVP2Exception { + return Pair.newInstance(authData.getBPK(), authData.getBPKType()); + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/config/PVPEndPointConfiguration.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/config/PVPEndPointConfiguration.java new file mode 100644 index 00000000..35e5669d --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/config/PVPEndPointConfiguration.java @@ -0,0 +1,66 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.config; + +import java.util.List; + +import org.opensaml.saml2.metadata.ContactPerson; +import org.opensaml.saml2.metadata.Organization; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; + +@Service("PVPEndPointConfiguration") +public class PVPEndPointConfiguration implements IPVP2BasicConfiguration { + private static final Logger log = LoggerFactory.getLogger(PVPEndPointConfiguration.class); + + @Autowired(required=true) IConfiguration basicConfiguration; + + @Override + public String getIDPEntityId(String authURL) throws EAAFException { + return removePostFix(authURL) + MSeIDASNodeConstants.ENDPOINT_PVP_METADATA; + + } + + @Override + public String getIDPSSOPostService(String authURL) throws EAAFException { + return removePostFix(authURL) + MSeIDASNodeConstants.ENDPOINT_PVP_POST; + + } + + @Override + public String getIDPSSORedirectService(String authURL) throws EAAFException { + return removePostFix(authURL) + MSeIDASNodeConstants.ENDPOINT_PVP_REDIRECT; + + } + + @Override + public Object getIDPSSOSOAPService(String extractAuthURLFromRequest) throws EAAFException { + log.warn("PVP S-Profile End-Point does NOT support SOAP Binding"); + return null; + + } + + @Override + public List<ContactPerson> getIDPContacts() throws EAAFException { + return null; + } + + @Override + public Organization getIDPOrganisation() throws EAAFException { + return null; + } + + private String removePostFix(String url) { + if (url != null && url.endsWith("/")) + return url.substring(0, url.length() - 1); + else + return url; + } +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/config/PVPMetadataConfiguration.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/config/PVPMetadataConfiguration.java new file mode 100644 index 00000000..a1d534cf --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/config/PVPMetadataConfiguration.java @@ -0,0 +1,240 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.config; + +import java.util.Arrays; +import java.util.List; + +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.NameIDType; +import org.opensaml.saml2.metadata.ContactPerson; +import org.opensaml.saml2.metadata.Organization; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.opensaml.xml.security.credential.Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; +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 at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +public class PVPMetadataConfiguration implements IPVPMetadataBuilderConfiguration{ + private static final Logger log = LoggerFactory.getLogger(PVPMetadataConfiguration.class); + + private IConfiguration basicConfig; + private String authUrl; + private AbstractCredentialProvider pvpIDPCredentials; + private IPVP2BasicConfiguration pvpBasicConfig; + + public PVPMetadataConfiguration(IConfiguration basicConfig, String authURL, IPVP2BasicConfiguration pvpBasicConfig, AbstractCredentialProvider pvpIDPCredentials) { + this.authUrl = authURL; + this.pvpIDPCredentials = pvpIDPCredentials; + this.basicConfig = basicConfig; + this.pvpBasicConfig = pvpBasicConfig; + + } + + @Override + public String getSPNameForLogging() { + return "PVP2 S-Profile IDP"; + } + + @Override + public int getMetadataValidUntil() { + return Integer.valueOf(basicConfig.getBasicConfiguration( + MSeIDASNodeConstants.PROP_CONFIG_PVP2_METADATA_VALIDITY, + String.valueOf(MSeIDASNodeConstants.DEFAULT_PVP_METADATA_VALIDITY))); + + } + + @Override + public boolean buildEntitiesDescriptorAsRootElement() { + return false; + + } + + @Override + public boolean buildIDPSSODescriptor() { + return true; + + } + + @Override + public boolean buildSPSSODescriptor() { + return false; + + } + + @Override + public String getEntityID() { + try { + return pvpBasicConfig.getIDPEntityId(authUrl); + + } catch (EAAFException e) { + log.error("Can NOT build PVP metadata configuration.", e); + throw new RuntimeException("Can NOT build PVP metadata configuration."); + + } + + } + + @Override + public String getEntityFriendlyName() { + return null; + + } + + @Override + public List<ContactPerson> getContactPersonInformation() { + try { + return pvpBasicConfig.getIDPContacts(); + + } catch (EAAFException e) { + log.error("Can NOT build PVP metadata configuration.", e); + throw new RuntimeException("Can NOT build PVP metadata configuration."); + + } + + } + + @Override + public Organization getOrgansiationInformation() { + try { + return pvpBasicConfig.getIDPOrganisation(); + + } catch (EAAFException e) { + log.error("Can NOT build PVP metadata configuration.", e); + throw new RuntimeException("Can NOT build PVP metadata configuration."); + + } + } + + @Override + public Credential getMetadataSigningCredentials() throws CredentialsNotAvailableException { + return pvpIDPCredentials.getIDPMetaDataSigningCredential(); + + } + + @Override + public Credential getRequestorResponseSigningCredentials() throws CredentialsNotAvailableException { + return pvpIDPCredentials.getIDPAssertionSigningCredential(); + + } + + @Override + public Credential getEncryptionCredentials() throws CredentialsNotAvailableException { + return null; + + + } + + @Override + public String getIDPWebSSOPostBindingURL() { + try { + return pvpBasicConfig.getIDPSSOPostService(authUrl); + + } catch (EAAFException e) { + log.error("Can NOT build PVP metadata configuration.", e); + throw new RuntimeException("Can NOT build PVP metadata configuration."); + + } + + } + + @Override + public String getIDPWebSSORedirectBindingURL() { + try { + return pvpBasicConfig.getIDPSSORedirectService(authUrl); + + } catch (EAAFException e) { + log.error("Can NOT build PVP metadata configuration.", e); + throw new RuntimeException("Can NOT build PVP metadata configuration."); + + } + } + + @Override + public String getIDPSLOPostBindingURL() { + return null; + + } + + @Override + public String getIDPSLORedirectBindingURL() { + return null; + + } + + @Override + public String getSPAssertionConsumerServicePostBindingURL() { + return null; + + } + + @Override + public String getSPAssertionConsumerServiceRedirectBindingURL() { + return null; + + } + + @Override + public String getSPSLOPostBindingURL() { + return null; + + } + + @Override + public String getSPSLORedirectBindingURL() { + return null; + + } + + @Override + public String getSPSLOSOAPBindingURL() { + return null; + + } + + @Override + public List<Attribute> getIDPPossibleAttributes() { + return PVPAttributeBuilder.buildSupportedEmptyAttributes(); + + } + + @Override + public List<String> getIDPPossibleNameITTypes() { + return Arrays.asList(NameIDType.PERSISTENT, + NameIDType.TRANSIENT, + NameIDType.UNSPECIFIED); + } + + @Override + public List<RequestedAttribute> getSPRequiredAttributes() { + return null; + + } + + @Override + public List<String> getSPAllowedNameITTypes() { + return null; + + } + + @Override + public boolean wantAssertionSigned() { + return false; + + } + + @Override + public boolean wantAuthnRequestSigned() { + return true; + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/PVP2SProfileEndpoint.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/PVP2SProfileEndpoint.java new file mode 100644 index 00000000..8a6fcb3d --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/PVP2SProfileEndpoint.java @@ -0,0 +1,59 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.modules.pvp2.idp.impl.AbstractPVP2XProtocol; +import at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest; + +@Controller +public class PVP2SProfileEndpoint extends AbstractPVP2XProtocol{ + + public static final String NAME = PVP2SProfileEndpoint.class.getName(); + public static final String PROTOCOL_ID = "pvp2-s"; + + @RequestMapping(value = MSeIDASNodeConstants.ENDPOINT_PVP_METADATA, method = {RequestMethod.POST, RequestMethod.GET}) + public void PVPMetadataRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { + super.pvpMetadataRequest(req, resp); + + } + + @RequestMapping(value = MSeIDASNodeConstants.ENDPOINT_PVP_POST, method = {RequestMethod.POST}) + public void PVPIDPPostRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { + super.PVPIDPPostRequest(req, resp); + + } + + @RequestMapping(value = MSeIDASNodeConstants.ENDPOINT_PVP_REDIRECT, method = {RequestMethod.GET}) + public void PVPIDPRedirecttRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { + super.PVPIDPRedirecttRequest(req, resp); + + } + + + @Override + public String getAuthProtocolIdentifier() { + return PROTOCOL_ID; + } + + @Override + public String getName() { + return NAME; + } + + @Override + protected boolean childPreProcess(HttpServletRequest arg0, HttpServletResponse arg1, PVPSProfilePendingRequest arg2) + throws Throwable { + return false; + } + + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/ProcessEngineSignalController.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/ProcessEngineSignalController.java new file mode 100644 index 00000000..febdbcce --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/controller/ProcessEngineSignalController.java @@ -0,0 +1,30 @@ +package at.asitplus.eidas.specific.connector.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractProcessEngineSignalController; + +/** + * @author tlenz + * + */ +@Controller +public class ProcessEngineSignalController extends AbstractProcessEngineSignalController { + + @RequestMapping(value = {MSeIDASNodeConstants.ENDPOINT_COUNTRYSELECTION + }, + method = {RequestMethod.POST, RequestMethod.GET}) + public void performGenericAuthenticationProcess(HttpServletRequest req, HttpServletResponse resp) throws IOException { + signalProcessManagement(req, resp); + + } +} +
\ No newline at end of file diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/logger/RevisionLogger.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/logger/RevisionLogger.java new file mode 100644 index 00000000..1d29d96b --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/logger/RevisionLogger.java @@ -0,0 +1,69 @@ +package at.asitplus.eidas.specific.connector.logger; + +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.components.eventlog.api.Event; +import at.gv.egiz.components.eventlog.api.EventLogFactory; +import at.gv.egiz.components.eventlog.api.EventLoggingException; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; + +public class RevisionLogger extends EventLogFactory implements IRevisionLogger { + private static final Logger log = LoggerFactory.getLogger(RevisionLogger.class); + + @Override + public void logEvent(ISPConfiguration oaConfig, int eventCode, String message) { + logEvent(createNewEvent(new Date().getTime(), eventCode, message)); + + } + + @Override + public void logEvent(IRequest pendingRequest, int eventCode) { + logEvent(createNewEvent(new Date().getTime(), eventCode, + pendingRequest.getUniqueSessionIdentifier(), pendingRequest.getUniqueTransactionIdentifier())); + + } + + @Override + public void logEvent(IRequest pendingRequest, int eventCode, String message) { + logEvent(createNewEvent(new Date().getTime(), eventCode, message, + pendingRequest.getUniqueSessionIdentifier(), pendingRequest.getUniqueTransactionIdentifier())); + + } + + @Override + public void logEvent(int eventCode, String message) { + logEvent(createNewEvent(new Date().getTime(), eventCode, message)); + + } + + @Override + public void logEvent(String sessionID, String transactionID, int eventCode, String message) { + logEvent(createNewEvent(new Date().getTime(), eventCode, message, sessionID, transactionID)); + + } + + @Override + public void logEvent(String sessionID, String transactionID, int eventCode) { + logEvent(createNewEvent(new Date().getTime(), eventCode, sessionID, transactionID)); + + } + + private void logEvent(Event event) { + try { + if (event.getEventCode() >= 1100) + getEventLog().logEvent(event); + else + log.trace("Ignore Event: " + event.getEventCode() + " because session functionallity is not implemented"); + + } catch (EventLoggingException e) { + log.warn("Event logging FAILED! Reason: " + e.getMessage()); + + } + + } +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/logger/StatisticLogger.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/logger/StatisticLogger.java new file mode 100644 index 00000000..6ff92b53 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/logger/StatisticLogger.java @@ -0,0 +1,116 @@ +package at.asitplus.eidas.specific.connector.logger; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.logging.IStatisticLogger; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; + +public class StatisticLogger implements IStatisticLogger { + + private static final Logger log = LoggerFactory.getLogger(StatisticLogger.class); + + private static final String DATEFORMATER = "yyyy.MM.dd-HH:mm:ss+z"; + private static final String STATUS_SUCCESS = "success"; + private static final String STATUS_ERROR = "error"; + + @Override + public void logSuccessOperation(IRequest protocolRequest, IAuthData authData, boolean isSSOSession) { + log.info(buildLogMessage( + protocolRequest.getUniqueTransactionIdentifier(), + protocolRequest.getSPEntityId(), + protocolRequest.getRawData(MSeIDASNodeConstants.DATA_REQUESTERID), + protocolRequest.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), + authData.getCiticenCountryCode(), + STATUS_SUCCESS , + StringUtils.EMPTY, + StringUtils.EMPTY)); + + + } + + @Override + public void logErrorOperation(Throwable throwable) { + String errorId = "TODO"; + if (throwable instanceof EAAFException) + errorId = ((EAAFException) throwable).getErrorId(); + + log.info(buildLogMessage( + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + STATUS_ERROR, + errorId, + throwable.getMessage())); + + + } + + @Override + public void logErrorOperation(Throwable throwable, IRequest errorRequest) { + String errorId = "TODO"; + if (throwable instanceof EAAFException) + errorId = ((EAAFException) throwable).getErrorId(); + + if (errorRequest != null) + log.info(buildLogMessage( + errorRequest.getUniqueTransactionIdentifier(), + errorRequest.getSPEntityId(), + errorRequest.getRawData(MSeIDASNodeConstants.DATA_REQUESTERID), + errorRequest.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), + StringUtils.EMPTY, + STATUS_ERROR, + errorId, + throwable.getMessage())); + + else + log.info(buildLogMessage( + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + STATUS_ERROR, + errorId, + throwable.getMessage())); + + } + + @Override + public void internalTesting() throws Exception { + log.trace("Not implemented for a File-based logger"); + + } + + private String buildLogMessage(String tId, String moaIDEntityId, Object requesterId, String target, String cc, + String status, String errorCode, String errorMsg) { + String logMsg = StringUtils.EMPTY; + + //data,tId,MOAID-Id,SP-Id,bPKTarget,CC,status,error-code,error-msg + + logMsg += DateTime.now().toString(DATEFORMATER ) + ","; + logMsg += tId + ","; + logMsg += moaIDEntityId + ","; + + if (requesterId instanceof String && StringUtils.isNotEmpty((String)requesterId)) + logMsg += (String)requesterId + ","; + else + logMsg += StringUtils.EMPTY + ","; + + logMsg += target + ","; + logMsg += cc + ","; + + logMsg += status + ","; + logMsg += errorCode + ","; + logMsg += errorMsg; + + return logMsg; + } +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/mapper/LoALevelMapper.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/mapper/LoALevelMapper.java new file mode 100644 index 00000000..2eae0126 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/mapper/LoALevelMapper.java @@ -0,0 +1,34 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.mapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.data.ILoALevelMapper; + +@Service("LoALevelMapper") +public class LoALevelMapper implements ILoALevelMapper{ + private static final Logger log = LoggerFactory.getLogger(LoALevelMapper.class); + + @Override + public String mapToSecClass(String LoA) { + log.info("Mapping to PVP SecClass is NOT supported"); + return null; + } + + @Override + public String mapToeIDASLoA(String LoA) { + if (LoA.startsWith(EAAFConstants.EIDAS_LOA_PREFIX)) + return LoA; + + else + log.info("Can NOT map '" + LoA + "' to eIDAS LoA"); + + return null; + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/CountrySelectionProcessImpl.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/CountrySelectionProcessImpl.java new file mode 100644 index 00000000..2ab5df03 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/CountrySelectionProcessImpl.java @@ -0,0 +1,42 @@ +package at.asitplus.eidas.specific.connector.processes; + +import org.apache.commons.lang3.StringUtils; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; + +/** + * @author tlenz + * + */ +public class CountrySelectionProcessImpl implements AuthModule { + + @Override + public int getPriority() { + return 0; + + } + + @Override + public String selectProcess(ExecutionContext context) { + Object selectedCountryObj = context.get(MSeIDASNodeConstants.REQ_PARAM_SELECTED_COUNTRY); + if (selectedCountryObj != null && selectedCountryObj instanceof String) { + String selectedCountry = (String) selectedCountryObj; + if (StringUtils.isNotEmpty(selectedCountry)) + return null; + + } + + return "CountrySelectionProcess"; + + + } + + @Override + public String[] getProcessDefinitions() { + return new String[] { "classpath:processes/CountrySelection.process.xml" }; + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/EvaluateCountrySelectionTask.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/EvaluateCountrySelectionTask.java new file mode 100644 index 00000000..0f73aded --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/EvaluateCountrySelectionTask.java @@ -0,0 +1,67 @@ +package at.asitplus.eidas.specific.connector.processes.tasks; + +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.connector.MSConnectorEventCodes; +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; + +/** + * @author tlenz + * + */ +@Component("EvaluateCountrySelectionTask") +public class EvaluateCountrySelectionTask extends AbstractAuthServletTask { + private static final Logger log = LoggerFactory.getLogger(EvaluateCountrySelectionTask.class); + + + @Override + public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) + throws TaskExecutionException { + try { + + String stopAuthFlag = request.getParameter(MSeIDASNodeConstants.REQ_PARAM_STOP_PROCESS); + if (StringUtils.isNotEmpty(stopAuthFlag) && Boolean.parseBoolean(stopAuthFlag)) { + log.info("Authentication process WAS stopped by entity. Stopping auth. process ... "); + revisionsLogger.logEvent(pendingReq, MSConnectorEventCodes.PROCESS_STOPPED_BY_USER); + pendingReq.setAbortedByUser(true); + pendingReq.setAuthenticated(false); + + } else { + // set parameter execution context + Enumeration<String> reqParamNames = request.getParameterNames(); + while(reqParamNames.hasMoreElements()) { + String paramName = reqParamNames.nextElement(); + if (StringUtils.isNotEmpty(paramName) && + !EAAFConstants.PROCESS_ENGINE_PENDINGREQUESTID.equalsIgnoreCase(paramName)) { + for (String el : MSeIDASNodeConstants.COUNTRY_SELECTION_PARAM_WHITELIST) { + if (el.equalsIgnoreCase(paramName)) + executionContext.put(paramName, + StringEscapeUtils.escapeHtml(request.getParameter(paramName))); + } + } + } + log.info("Country selection finished. Starting auth. process for country ... "); + + } + + } catch (Exception e) { + log.warn("EvaluateBKUSelectionTask has an internal error", e); + throw new TaskExecutionException(pendingReq, e.getMessage(), e); + + } + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java new file mode 100644 index 00000000..fb1f7f53 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/processes/tasks/GenerateCountrySelectionFrameTask.java @@ -0,0 +1,62 @@ +package at.asitplus.eidas.specific.connector.processes.tasks; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.connector.MSConnectorEventCodes; +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.asitplus.eidas.specific.connector.gui.StaticGuiBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.GUIBuildException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; + +/** + * @author tlenz + * + */ +@Component("GenerateCountrySelectionFrameTask") +public class GenerateCountrySelectionFrameTask extends AbstractAuthServletTask { + private static final Logger log = LoggerFactory.getLogger(GenerateCountrySelectionFrameTask.class); + + @Autowired IGUIFormBuilder guiBuilder; + @Autowired IConfiguration basicConfig; + + @Override + public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) + throws TaskExecutionException { + try { + revisionsLogger.logEvent(pendingReq, MSConnectorEventCodes.STARTING_COUNTRY_SELECTION); + + IGUIBuilderConfiguration config = new StaticGuiBuilderConfiguration( + basicConfig, + pendingReq, + MSeIDASNodeConstants.TEMPLATE_HTML_COUNTRYSELECTION, + MSeIDASNodeConstants.ENDPOINT_COUNTRYSELECTION); + + guiBuilder.build(response, config, "BKU-Selection form"); + + } catch (GUIBuildException e) { + log.warn("Can not build GUI:'BKU-Selection'. Msg:" + e.getMessage()); + throw new TaskExecutionException(pendingReq, + "Can not build GUI. Msg:" + e.getMessage(), + new EAAFException("gui.00", new Object[]{e.getMessage()}, e)); + + } catch (Exception e) { + log.warn("FinalizeAuthenticationTask has an internal error", e); + throw new TaskExecutionException(pendingReq, e.getMessage(), e); + + } + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPEndPointCredentialProvider.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPEndPointCredentialProvider.java new file mode 100644 index 00000000..47279aff --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPEndPointCredentialProvider.java @@ -0,0 +1,94 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.provider; + +import java.net.MalformedURLException; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +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.utils.FileUtils; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +public class PVPEndPointCredentialProvider extends AbstractCredentialProvider { + private static final Logger log = LoggerFactory.getLogger(PVPEndPointCredentialProvider.class); + + @Autowired(required=true) IConfiguration basicConfiguration; + + @Override + public String getFriendlyName() { + return "PVP2 S-Profile EndPoint"; + } + + @Override + public String getKeyStoreFilePath() throws EAAFException { + try { + String path = basicConfiguration.getBasicConfiguration(MSeIDASNodeConstants.PROP_CONFIG_PVP2_KEYSTORE_PATH); + if (StringUtils.isEmpty(path)) { + log.error(getFriendlyName() + " | Path to keyStore is NULL or EMPTY"); + throw new EAAFConfigurationException("config.27", + new Object[] {getFriendlyName() + " | Path to keyStore is NULL or EMPTY"}); + + } + + return FileUtils.makeAbsoluteURL( + path, + basicConfiguration.getConfigurationRootDirectory()); + + } catch (MalformedURLException e) { + log.error(getFriendlyName() + " | Path to keyStore NOT valid.", e); + throw new EAAFConfigurationException("config.27", + new Object[] {getFriendlyName() + " | Path to keyStore NOT valid."}, e); + + } + + } + + @Override + public String getKeyStorePassword() { + return basicConfiguration.getBasicConfiguration(MSeIDASNodeConstants.PROP_CONFIG_PVP2_KEYSTORE_PASSWORD); + + } + + @Override + public String getMetadataKeyAlias() { + return basicConfiguration.getBasicConfiguration(MSeIDASNodeConstants.PROP_CONFIG_PVP2_KEY_METADATA_ALIAS); + } + + @Override + public String getMetadataKeyPassword() { + return basicConfiguration.getBasicConfiguration(MSeIDASNodeConstants.PROP_CONFIG_PVP2_KEY_METADATA_PASSWORD); + + } + + @Override + public String getSignatureKeyAlias() { + return basicConfiguration.getBasicConfiguration(MSeIDASNodeConstants.PROP_CONFIG_PVP2_KEY_SIGNING_ALIAS); + + } + + @Override + public String getSignatureKeyPassword() { + return basicConfiguration.getBasicConfiguration(MSeIDASNodeConstants.PROP_CONFIG_PVP2_KEY_SIGNING_PASSWORD); + + } + + @Override + public String getEncryptionKeyAlias() { + return null; + + } + + @Override + public String getEncryptionKeyPassword() { + return null; + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPMetadataConfigurationFactory.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPMetadataConfigurationFactory.java new file mode 100644 index 00000000..c6200b31 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPMetadataConfigurationFactory.java @@ -0,0 +1,28 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.provider; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import at.asitplus.eidas.specific.connector.config.PVPMetadataConfiguration; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPVPMetadataConfigurationFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +@Service("PVPMetadataConfigurationFactory") +public class PVPMetadataConfigurationFactory implements IPVPMetadataConfigurationFactory { + + @Autowired private IConfiguration basicConfig; + @Autowired private IPVP2BasicConfiguration pvpBasicConfig; + + @Override + public IPVPMetadataBuilderConfiguration generateMetadataBuilderConfiguration(String authURL, + AbstractCredentialProvider pvpIDPCredentials) { + return new PVPMetadataConfiguration(basicConfig, authURL, pvpBasicConfig, pvpIDPCredentials); + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPMetadataProvider.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPMetadataProvider.java new file mode 100644 index 00000000..ce16da49 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/PVPMetadataProvider.java @@ -0,0 +1,124 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.provider; + +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.List; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.xml.parse.BasicParserPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.asitplus.eidas.specific.connector.verification.MetadataSignatureVerificationFilter; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +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.MetadataFilterChain; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.PVPEntityCategoryFilter; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.SchemaValidationFilter; + +@Service("PVPMetadataProvider") +public class PVPMetadataProvider extends AbstractChainingMetadataProvider{ + private static final Logger log = LoggerFactory.getLogger(PVPMetadataProvider.class); + + @Autowired(required=true) IConfiguration basicConfig; + + @Override + protected String getMetadataURL(String entityId) throws EAAFConfigurationException { + ISPConfiguration spConfig = basicConfig.getServiceProviderConfiguration(entityId); + if (spConfig != null) { + String metadataURL = entityId; + + String metadataURLFromConfig = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_URL); + if (StringUtils.isNotEmpty(metadataURLFromConfig)) { + log.debug("Use metdataURL from configuration for EntityId: " + entityId); + metadataURL = metadataURLFromConfig; + + } + + return metadataURL; + + } else + log.info("No ServiceProvider with entityId: " + entityId + " in configuration."); + + return null; + } + + @Override + protected MetadataProvider createNewMetadataProvider(String entityId) + throws EAAFConfigurationException, IOException, CertificateException { + ISPConfiguration spConfig = basicConfig.getServiceProviderConfiguration(entityId); + if (spConfig != null) { + try { + String metadataURL = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_URL); + if (StringUtils.isEmpty(metadataURL)) { + log.debug("Use EntityId: " + entityId + " instead of explicite metadataURL ... "); + metadataURL = entityId; + + } + String trustStoreUrl = FileUtils.makeAbsoluteURL( + spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_TRUSTSTORE), + authConfig.getConfigurationRootDirectory()); + String trustStorePassword = spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_PVP2_METADATA_TRUSTSTORE_PASSWORD); + + return createNewSimpleMetadataProvider(metadataURL, + buildMetadataFilterChain(spConfig, metadataURL, trustStoreUrl, trustStorePassword), + spConfig.getConfigurationValue(MSeIDASNodeConstants.PROP_CONFIG_SP_UNIQUEIDENTIFIER), + getTimer(), + new BasicParserPool(), + createHttpClient(metadataURL)); + + } catch (PVP2MetadataException e) { + log.info("Can NOT initialize Metadata signature-verification filter. Reason: " + e.getMessage()); + throw new EAAFConfigurationException("config.27", + new Object[] {"Can NOT initialize Metadata signature-verification filter. Reason: " + e.getMessage()}, e); + + } + + } else + log.info("No ServiceProvider with entityId: " + entityId + " in configuration."); + + return null; + } + + @Override + protected List<String> getAllMetadataURLsFromConfiguration() throws EAAFConfigurationException { + // TODO Auto-generated method stub + return null; + } + + private HttpClient createHttpClient(String metadataURL) { + HttpClient httpClient = new HttpClient(); + HttpClientParams httpClientParams = new HttpClientParams(); + httpClientParams.setSoTimeout(MSeIDASNodeConstants.METADATA_SOCKED_TIMEOUT); + httpClient.setParams(httpClientParams); + return httpClient; + + } + + private MetadataFilterChain buildMetadataFilterChain(ISPConfiguration oaParam, String metadataURL, String trustStoreUrl, String trustStorePassword) throws CertificateException, PVP2MetadataException{ + MetadataFilterChain filterChain = new MetadataFilterChain(); + filterChain.getFilters().add(new SchemaValidationFilter( + basicConfig.getBasicMOAIDConfigurationBoolean(MSeIDASNodeConstants.PROP_CONFIG_PVP_SCHEME_VALIDATION, true))); + + filterChain.getFilters().add( + new MetadataSignatureVerificationFilter( + trustStoreUrl, trustStorePassword, metadataURL)); + + filterChain.getFilters().add(new PVPEntityCategoryFilter( + basicConfig.getBasicMOAIDConfigurationBoolean(MSeIDASNodeConstants.PROP_CONFIG_PVP_ENABLE_ENTITYCATEGORIES, true))); + + return filterChain; + } +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/StatusMessageProvider.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/StatusMessageProvider.java new file mode 100644 index 00000000..5f359ea1 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/provider/StatusMessageProvider.java @@ -0,0 +1,104 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.provider; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IStatusMessenger; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; + +@Service("StatusMessageProvider") +public class StatusMessageProvider implements IStatusMessenger { + private static final Logger log = LoggerFactory.getLogger(StatusMessageProvider.class); + + private static final String ERROR_MESSAGES_UNAVAILABLE = "Error messages can NOT be load from application. Only errorCode: {0} is availabe" ; + private static final String ERROR_NO_MESSAGE = "No errormesseage for error with number.={0}"; + + private static final String ERROR_EXTERNALERROR_CODES_UNAVAILABLE = "External error-codes can NOT be load from application. Only internal errorCode: {0} is availabe" ; + private static final String ERROR_NO_EXTERNALERROR_CODE = "No external error for internal error with number.={0}"; + + //internal messanges + private static final String DEFAULT_MESSAGE_RESOURCES = "properties/status_messages_en"; + private static final Locale DEFAULT_MESSAGE_LOCALES = new Locale("en", "GB"); + private ResourceBundle messages; + + //external error codes + private static final String DEFAULT_EXTERNALERROR_RESOURCES = "properties/external_statuscodes_map"; + private static final Locale DEFAULT_EXTERNALERROR_LOCALES = new Locale("en", "GB"); + private ResourceBundle externalError = null; + + @Override + public String getMessage(String messageId, Object[] parameters) { + // initialize messages + if (messages == null) { + this.messages = PropertyResourceBundle.getBundle( + DEFAULT_MESSAGE_RESOURCES, + DEFAULT_MESSAGE_LOCALES); + + } + + // create the message + if (messages == null) { + return MessageFormat.format(ERROR_MESSAGES_UNAVAILABLE, new Object[] { messageId }); + + } else { + try { + String rawMessage = messages.getString(messageId); + return MessageFormat.format(rawMessage, parameters); + + } catch (MissingResourceException e2) { + return MessageFormat.format(ERROR_NO_MESSAGE, new Object[] { messageId }); + + } + } + } + + @Override + public String getResponseErrorCode(Throwable throwable) { + String errorCode = IStatusMessenger.CODES_EXTERNAL_ERROR_GENERIC; + if (throwable instanceof EAAFException) { + errorCode = ((EAAFException) throwable).getErrorId(); + + } + //TODO: maybe more internal switches are required + + return errorCode; + + } + + @Override + public String mapInternalErrorToExternalError(String intErrorCode) { + // initialize messages + if (externalError == null) { + this.externalError = PropertyResourceBundle.getBundle( + DEFAULT_EXTERNALERROR_RESOURCES, + DEFAULT_EXTERNALERROR_LOCALES); + + } + + // create the message + if (externalError == null) { + log.warn(MessageFormat.format(ERROR_EXTERNALERROR_CODES_UNAVAILABLE, new Object[] { intErrorCode })); + return IStatusMessenger.CODES_EXTERNAL_ERROR_GENERIC; + + } else { + try { + return externalError.getString(intErrorCode); + + } catch (MissingResourceException e2) { + log.info(MessageFormat.format(ERROR_NO_EXTERNALERROR_CODE, new Object[] { intErrorCode })); + return IStatusMessenger.CODES_EXTERNAL_ERROR_GENERIC; + + } + } + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/CacheWitheIDASBackend.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/CacheWitheIDASBackend.java new file mode 100644 index 00000000..ac0abda0 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/CacheWitheIDASBackend.java @@ -0,0 +1,33 @@ +package at.asitplus.eidas.specific.connector.storage; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import eu.eidas.auth.commons.cache.ConcurrentMapService; +import eu.eidas.auth.commons.tx.AbstractCorrelationMap; + +public class CacheWitheIDASBackend extends AbstractCorrelationMap<TransactionStoreElement> { + + protected CacheWitheIDASBackend(ConcurrentMapService concurrentMapService) { + super(concurrentMapService); + } + + public List<String> clean(Date now, long dataTimeOut) { + List<String> result = new ArrayList<String>(); + Iterator<String> iterator = map.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + if (map.containsKey(key)) { + TransactionStoreElement element = map.get(key); + if (now.getTime() - element.getCreated().getTime() > dataTimeOut) + result.add(key); + } + } + + return result; + + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/SimpleInMemoryTransactionStorage.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/SimpleInMemoryTransactionStorage.java new file mode 100644 index 00000000..80b0e965 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/SimpleInMemoryTransactionStorage.java @@ -0,0 +1,141 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.storage; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; + +@Service("SimpleInMemoryTransactionStorage") +public class SimpleInMemoryTransactionStorage implements ITransactionStorage{ + private static final Logger log = LoggerFactory.getLogger(SimpleInMemoryTransactionStorage.class); + + private Map<String, TransactionStoreElement> storage = new ConcurrentHashMap<String, TransactionStoreElement>(); + + @Override + public void changeKey(String oldKey, String newKey, Object value) throws EAAFException { + if (containsKey(oldKey)) { + TransactionStoreElement el = storage.get(oldKey); + el.setKey(newKey); + storage.put(newKey, el); + storage.remove(oldKey); + + } else + throw new EAAFStorageException("No element in TransactionStorage with key: " + oldKey); + + } + + @Override + public List<String> clean(Date now, long dataTimeOut) { + List<String> result = new ArrayList<String>(); + Iterator<String> iterator = storage.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + synchronized (storage) { + if (storage.containsKey(key)) { + TransactionStoreElement element = storage.get(key); + if (now.getTime() - element.getCreated().getTime() > dataTimeOut) + result.add(key); + } + } + } + + return result; + + } + + @Override + public boolean containsKey(String key) { + if (key != null) + return storage.containsKey(key); + else + return false; + + } + + @Override + public Object get(String key) throws EAAFException { + if (key != null && containsKey(key)) { + TransactionStoreElement element = storage.get(key); + return element.getData(); + + } else + return null; + } + + @Override + public <T> T get(String key, Class<T> type) throws EAAFException { + return get(key, type, -1); + + } + + @Override + public <T> T get(String key, Class<T> type, long dataTimeOut) throws EAAFException { + if (key != null && containsKey(key)) { + TransactionStoreElement value = storage.get(key); + + if (dataTimeOut > -1) { + long now = new Date().getTime(); + if (now - value.getCreated().getTime() > dataTimeOut) { + log.info("Transaction-Data with key: " + key + " is out of time."); + throw new EAAFStorageException("Transaction-Data with key: " + key + " is out of time."); + + } + } + + if (type.isAssignableFrom(value.getData().getClass())) { + return (T) value.getData(); + + } else + log.warn("Can NOT cast '" + value.getClass() + "' to '" + type + "'"); + + } + + return null; + } + + @Override + public Object getRaw(String key) throws EAAFException { + return storage.get(key); + + } + + @Override + public void put(String key, Object value, int dataTimeOut) throws EAAFException { + TransactionStoreElement element = new TransactionStoreElement(); + element.setKey(key); + element.setData(value); + storage.put(key, element); + + } + + @Override + public void putRaw(String key, Object value) throws EAAFException { + if (value instanceof TransactionStoreElement) + storage.put(((TransactionStoreElement) value).getKey(), (TransactionStoreElement) value); + else + log.info(value.getClass().getName() + " is NOT a RAW element of " + ITransactionStorage.class.getName()); + + } + + @Override + public void remove(String key) { + if (containsKey(key)) { + log.debug("Remove element with key: " + key + " from " + ITransactionStorage.class.getName()); + storage.remove(key); + + } + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/TransactionStoreElement.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/TransactionStoreElement.java new file mode 100644 index 00000000..b9012376 --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/TransactionStoreElement.java @@ -0,0 +1,36 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.storage; + +import java.io.Serializable; +import java.util.Date; + +public class TransactionStoreElement implements Serializable{ + + private static final long serialVersionUID = 1L; + private String key = null; + private Object data = null; + private Date created; + + public String getKey() { + return key; + } + public void setKey(String key) { + this.key = key; + } + public Object getData() { + return data; + } + public void setData(Object data) { + this.data = data; + } + public Date getCreated() { + return created; + } + public void setCreated(Date created) { + this.created = created; + } + + + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/eIDASCacheTransactionStoreDecorator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/eIDASCacheTransactionStoreDecorator.java new file mode 100644 index 00000000..f1ffba6f --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/storage/eIDASCacheTransactionStoreDecorator.java @@ -0,0 +1,124 @@ +package at.asitplus.eidas.specific.connector.storage; + +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; + +public class eIDASCacheTransactionStoreDecorator implements ITransactionStorage{ + private static final Logger log = LoggerFactory.getLogger(eIDASCacheTransactionStoreDecorator.class); + + @Autowired(required=true) private CacheWitheIDASBackend storage; + + @Override + public void changeKey(String oldKey, String newKey, Object value) throws EAAFException { + if (containsKey(oldKey)) { + TransactionStoreElement el = storage.get(oldKey); + el.setKey(newKey); + storage.put(newKey, el); + storage.remove(oldKey); + + } else + throw new EAAFStorageException("No element in TransactionStorage with key: " + oldKey); + + } + + @Override + public List<String> clean(Date now, long dataTimeOut) { + return storage.clean(now, dataTimeOut); + + } + + @Override + public boolean containsKey(String key) { + if (key != null) { + if (storage.get(key) != null) + return true; + + } + + return false; + + } + + @Override + public Object get(String key) throws EAAFException { + if (key != null && containsKey(key)) { + TransactionStoreElement element = storage.get(key); + return element.getData(); + + } else + return null; + } + + @Override + public <T> T get(String key, Class<T> type) throws EAAFException { + return get(key, type, -1); + + } + + @Override + public <T> T get(String key, Class<T> type, long dataTimeOut) throws EAAFException { + if (key != null && containsKey(key)) { + TransactionStoreElement value = storage.get(key); + + if (dataTimeOut > -1) { + long now = new Date().getTime(); + if (now - value.getCreated().getTime() > dataTimeOut) { + log.info("Transaction-Data with key: " + key + " is out of time."); + throw new EAAFStorageException("Transaction-Data with key: " + key + " is out of time."); + + } + } + + if (type.isAssignableFrom(value.getData().getClass())) { + return (T) value.getData(); + + } else + log.warn("Can NOT cast '" + value.getClass() + "' to '" + type + "'"); + + } + + return null; + } + + @Override + public Object getRaw(String key) throws EAAFException { + return storage.get(key); + + } + + @Override + public void put(String key, Object value, int dataTimeOut) throws EAAFException { + TransactionStoreElement element = new TransactionStoreElement(); + element.setKey(key); + element.setData(value); + storage.put(key, element); + + } + + @Override + public void putRaw(String key, Object value) throws EAAFException { + if (value instanceof TransactionStoreElement) + storage.put(((TransactionStoreElement) value).getKey(), (TransactionStoreElement) value); + else + log.info(value.getClass().getName() + " is NOT a RAW element of " + ITransactionStorage.class.getName()); + + } + + @Override + public void remove(String key) { + if (containsKey(key)) { + log.debug("Remove element with key: " + key + " from " + ITransactionStorage.class.getName()); + storage.remove(key); + + } + } + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java new file mode 100644 index 00000000..607f42df --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/AuthnRequestValidator.java @@ -0,0 +1,203 @@ +package at.asitplus.eidas.specific.connector.verification; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.NameIDPolicy; +import org.opensaml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml2.core.Scoping; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; +import at.asitplus.eidas.specific.connector.config.ServiceProviderConfiguration; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions; +import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EAAFRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EAAFRequestedAttributes; +import at.gv.egiz.eaaf.modules.pvp2.api.validation.IAuthnRequestValidator; +import at.gv.egiz.eaaf.modules.pvp2.exception.NameIDFormatNotSupportedException; + +public class AuthnRequestValidator implements IAuthnRequestValidator { + + private static final Logger log = LoggerFactory.getLogger(AuthnRequestValidator.class); + + @Override + public void validate(HttpServletRequest httpReq, IRequest pendingReq, AuthnRequest authnReq, + SPSSODescriptor spSSODescriptor) throws AuthnRequestValidatorException { + try { + //validate NameIDPolicy + NameIDPolicy nameIDPolicy = authnReq.getNameIDPolicy(); + if (nameIDPolicy != null) { + String nameIDFormat = nameIDPolicy.getFormat(); + if (nameIDFormat != null) { + if ( !(NameID.TRANSIENT.equals(nameIDFormat) || + NameID.PERSISTENT.equals(nameIDFormat)) ) { + + throw new NameIDFormatNotSupportedException(nameIDFormat); + + } + + } else + log.trace("Find NameIDPolicy, but NameIDFormat is 'null'"); + } else + log.trace("AuthnRequest includes no 'NameIDPolicy'"); + + + //post-process RequesterId + String spEntityId = extractScopeRequsterId(authnReq); + if (StringUtils.isEmpty(spEntityId)) { + log.info("NO service-provider entityID in Authn. request. Stop authn. process ... "); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] {"NO relaying-party entityID in Authn. request"}, pendingReq); + + } else + pendingReq.setRawDataToTransaction(MSeIDASNodeConstants.DATA_REQUESTERID, spEntityId); + + + //post-process ProviderName + String providerName = authnReq.getProviderName(); + if (StringUtils.isEmpty(providerName)) + log.info("Authn. request contains NO SP friendlyName"); + else + pendingReq.setRawDataToTransaction(MSeIDASNodeConstants.DATA_PROVIDERNAME, spEntityId); + + //post-process requested LoA + List<String> reqLoA = extractLoA(authnReq); + pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setRequiredLoA(reqLoA); + + //post-process requested LoA comparison-level + String reqLoAComperison = extractComparisonLevel(authnReq); + pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class).setLoAMachtingMode(reqLoAComperison); + + //validate and process requested attributes + boolean sectorDetected = false; + List<XMLObject> requestedAttributes = authnReq.getExtensions().getUnknownXMLObjects(); + for (XMLObject reqAttrObj : requestedAttributes) { + if (reqAttrObj instanceof EAAFRequestedAttributes) { + EAAFRequestedAttributes reqAttr = (EAAFRequestedAttributes)reqAttrObj; + if (reqAttr.getAttributes() != null && reqAttr.getAttributes().size() != 0 ) { + for (EAAFRequestedAttribute el : reqAttr.getAttributes()) { + log.trace("Processing req. attribute '" + el.getName() + "' ... "); + if (el.getName().equals(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME)) { + if (el.getAttributeValues() != null && el.getAttributeValues().size() == 1) { + String sectorId = el.getAttributeValues().get(0).getDOM().getTextContent(); + ServiceProviderConfiguration spConfig = pendingReq.getServiceProviderConfiguration(ServiceProviderConfiguration.class); + + try { + spConfig.setbPKTargetIdentifier(sectorId); + sectorDetected = true; + + } catch (EAAFException e) { + log.info("Requested sector: " + sectorId + " DOES NOT match to allowed sectors for SP: " + spConfig.getUniqueIdentifier()); + } + + } else + log.info("Req. attribute '" + el.getName() + "' contains NO or MORE THEN ONE attribute-values. Ignore full req. attribute"); + + } else + log.debug("Ignore req. attribute: " + el.getName()); + + } + + } else + log.debug("No requested Attributes in Authn. Request"); + + } else + log.info("Ignore unknown requested attribute: " + reqAttrObj.getElementQName().toString()); + + } + + if (!sectorDetected) { + log.info("Authn.Req validation FAILED. Reason: Contains NO or NO VALID target-sector information."); + throw new AuthnRequestValidatorException("pvp2.22", new Object[] {"NO or NO VALID target-sector information"}); + + } + + } catch (EAAFStorageException e) { + log.info("Can NOT store Authn. Req. data into pendingRequest." , e); + throw new AuthnRequestValidatorException("internal.02", null, e); + + } + + } + + private String extractComparisonLevel(AuthnRequest authnReq) { + if (authnReq.getRequestedAuthnContext() != null) { + RequestedAuthnContext authContext = authnReq.getRequestedAuthnContext(); + return authContext.getComparison().toString(); + + } + + return null; + } + + private List<String> extractLoA(AuthnRequest authnReq) throws AuthnRequestValidatorException { + List<String> result = new ArrayList<String>(); + if (authnReq.getRequestedAuthnContext() != null) { + RequestedAuthnContext authContext = authnReq.getRequestedAuthnContext(); + if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.MINIMUM)) { + if (authContext.getAuthnContextClassRefs().isEmpty()) { + log.debug("Authn. Req. contains no requested LoA"); + + } else if (authContext.getAuthnContextClassRefs().size() > 1) { + log.info("Authn. Req. contains MORE THAN ONE requested LoA, but " + + AuthnContextComparisonTypeEnumeration.MINIMUM + " allows only one" ); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] {"Authn. Req. contains MORE THAN ONE requested LoA, but " + + AuthnContextComparisonTypeEnumeration.MINIMUM + " allows only one"}); + + } else + result.add(authContext.getAuthnContextClassRefs().get(0).getAuthnContextClassRef()); + + } else if (authContext.getComparison().equals(AuthnContextComparisonTypeEnumeration.EXACT)) { + for (AuthnContextClassRef el : authContext.getAuthnContextClassRefs()) + result.add(el.getAuthnContextClassRef()); + + } else { + log.info("Currently only '" + AuthnContextComparisonTypeEnumeration.MINIMUM + "' and '" + + AuthnContextComparisonTypeEnumeration.EXACT + "' are supported"); + throw new AuthnRequestValidatorException("pvp2.22", + new Object[] {"Currently only '" + AuthnContextComparisonTypeEnumeration.MINIMUM + "' and '" + + AuthnContextComparisonTypeEnumeration.EXACT + "' are supported"}); + + } + + } + + return result; + } + + private String extractScopeRequsterId(AuthnRequest authnReq) { + if (authnReq.getScoping() != null) { + Scoping scoping = authnReq.getScoping(); + if (scoping.getRequesterIDs() != null && + scoping.getRequesterIDs().size() > 0) { + if (scoping.getRequesterIDs().size() == 1) + return scoping.getRequesterIDs().get(0).getRequesterID(); + + else { + log.info("Authn. request contains more than on RequesterIDs! Only use first one"); + return scoping.getRequesterIDs().get(0).getRequesterID(); + + } + } + } + + return null; + } + + +} diff --git a/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java new file mode 100644 index 00000000..67d2d59b --- /dev/null +++ b/connector/src/main/java/at/asitplus/eidas/specific/connector/verification/MetadataSignatureVerificationFilter.java @@ -0,0 +1,142 @@ +/******************************************************************************* + *******************************************************************************/ +package at.asitplus.eidas.specific.connector.verification; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.opensaml.common.SignableSAMLObject; +import org.opensaml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.security.SAMLSignatureProfileValidator; +import org.opensaml.xml.security.x509.BasicX509Credential; +import org.opensaml.xml.signature.SignatureValidator; +import org.opensaml.xml.validation.ValidationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.SAMLRequestNotSignedException; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.AbstractMetadataSignatureFilter; + +public class MetadataSignatureVerificationFilter extends AbstractMetadataSignatureFilter{ + private static final Logger log = LoggerFactory.getLogger(MetadataSignatureVerificationFilter.class); + + private String metadataURL; + private List<BasicX509Credential> trustedCredential = new ArrayList<BasicX509Credential>(); + + public MetadataSignatureVerificationFilter(String trustStorePath, String trustStorePassword, String metadataURL) + throws PVP2MetadataException { + this.metadataURL = metadataURL; + + log.trace("Initialize metadata signature-verification filter with truststore: " + trustStorePath + " ... "); + try { + KeyStore keyStore = KeyStoreUtils.loadKeyStore(trustStorePath, trustStorePassword); + if (keyStore != null) { + //load trusted certificates + Enumeration<String> aliases = keyStore.aliases(); + while(aliases.hasMoreElements()) { + String el = aliases.nextElement(); + log.trace("Process TrustStoreEntry: " + el); + if (keyStore.isCertificateEntry(el)) { + Certificate cert = keyStore.getCertificate(el); + if (cert != null && cert instanceof X509Certificate) { + BasicX509Credential trustedCert = new BasicX509Credential(); + trustedCert.setEntityCertificate((X509Certificate) cert); + this.trustedCredential.add(trustedCert); + log.debug("Add cert: " + ((X509Certificate) cert).getSubjectDN() + " as trusted for metadata: " + metadataURL); + + } else + log.info("Can not process entry: " + el + ". Reason: " + cert.toString()); + + } + } + + + } else + throw new PVP2MetadataException("pvp2.26", + new Object[] {"Can not open trustStore: " + trustStorePath + " for metadata: " + metadataURL}); + + } catch (KeyStoreException | IOException e) { + log.warn("Can not open trustStore: " + trustStorePath + " for metadata: " + metadataURL + " Reason: " + e.getMessage(), e); + throw new PVP2MetadataException("pvp2.26", + new Object[] {"Can not open trustStore: " + trustStorePath + " for metadata"}, e); + + } + + + } + + + @Override + protected void verify(EntityDescriptor desc) throws PVP2MetadataException { + try { + internalVerify(desc); + + } catch (EAAFException e) { + log.info("Metadata verification FAILED for: " + metadataURL + " Reason: " +e.getMessage()); + throw new PVP2MetadataException("pvp2.26", + new Object[] {"Metadata verification FAILED for: " + metadataURL + " Reason: " +e.getMessage()}, e); + + } + } + + @Override + protected void verify(EntitiesDescriptor desc) throws PVP2MetadataException { + throw new PVP2MetadataException("pvp2.26", + new Object[] {"EntitiesDescritors are NOT supported"}); + + } + + @Override + protected void verify(EntityDescriptor entity, EntitiesDescriptor desc) throws PVP2MetadataException { + throw new PVP2MetadataException("pvp2.26", + new Object[] {"EntitiesDescritors are NOT supported"}); + + } + + private void internalVerify(SignableSAMLObject signedElement) + throws EAAFException { + if (signedElement.getSignature() == null) { + throw new SAMLRequestNotSignedException(); + } + + try { + SAMLSignatureProfileValidator sigValidator = new SAMLSignatureProfileValidator(); + sigValidator.validate(signedElement.getSignature()); + } catch (ValidationException e) { + log.error("Failed to validate Signature", e); + throw new SAMLRequestNotSignedException(e); + } + + boolean isTrusted = false; + for (BasicX509Credential cred : trustedCredential) { + SignatureValidator sigValidator = new SignatureValidator(cred); + try { + sigValidator.validate(signedElement.getSignature()); + isTrusted = true; + + } catch (ValidationException e) { + log.info("Failed to verfiy Signature with cert: " + cred.getEntityCertificate().getSubjectDN() + + " Reason: " + e.getMessage()); + + } + } + + if (!isTrusted) { + log.warn("PVP2 metadata: " + metadataURL + " are NOT trusted!"); + throw new SAMLRequestNotSignedException(); + + } + + } + +} |