package at.asitplus.eidas.specific.proxy.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Base64; import java.util.Map; import java.util.TimeZone; import java.util.Timer; import java.util.UUID; import org.apache.commons.lang3.RandomStringUtils; import org.apache.ignite.Ignition; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.opensaml.core.config.InitializationException; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.UnmarshallingException; import org.opensaml.core.xml.util.XMLObjectSupport; import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver; import org.opensaml.saml.saml2.core.Issuer; import org.opensaml.saml.saml2.core.RequestAbstractType; import org.opensaml.saml.saml2.core.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.core.io.ResourceLoader; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.Base64Utils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.google.common.collect.ImmutableSet; import at.asitplus.eidas.specific.modules.auth.idaustria.controller.IdAustriaAuthSignalController; 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.core.eidas.EidasConstants; import at.asitplus.eidas.specific.modules.core.eidas.service.EidasAttributeRegistry; import at.asitplus.eidas.specific.modules.msproxyservice.protocol.EidasProxyServiceController; import at.gv.egiz.components.spring.api.SpringBootApplicationContextInitializer; import at.gv.egiz.eaaf.core.api.IStatusMessenger; import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.impl.idp.controller.ProtocolFinalizationController; import at.gv.egiz.eaaf.core.impl.logging.LogMessageProviderFactory; import at.gv.egiz.eaaf.core.impl.utils.DomUtils; import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.OpenSaml3ResourceAdapter; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EaafOpenSaml3xInitializer; import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import eu.eidas.auth.cache.IgniteInstanceInitializerSpecificCommunication; import eu.eidas.auth.commons.EidasParameterKeys; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; import eu.eidas.auth.commons.light.ILightResponse; import eu.eidas.auth.commons.light.impl.LightRequest; import eu.eidas.auth.commons.tx.BinaryLightToken; import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; import lombok.SneakyThrows; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.xml.XMLParserException; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest @ContextConfiguration(initializers = { org.springframework.boot.context.config.DelegatingApplicationContextInitializer.class, SpringBootApplicationContextInitializer.class }) @TestPropertySource(locations = { "file:src/test/resources/config/junit_config_1_springboot.properties" }) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) @ActiveProfiles(profiles = {"JUNIT", "jUnitTestMode"}) public class FullStartUpAndProcessTest { private static final String METADATA_PATH = "classpath:/data/idp_metadata_classpath_entity.xml"; private static final String FINAL_REDIRECT = "https://localhost/ms_proxy/public/secure/finalizeAuthProtocol?pendingid="; private static final String ERROR_REDIRECT = "https://localhost/ms_proxy/public/secure/errorHandling?errorid="; @Autowired private WebApplicationContext wac; @Autowired private ResourceLoader resourceLoader; @Autowired private EidasAttributeRegistry attrRegistry; @Autowired private IdAustriaAuthSignalController idAustriaEndpoint; @Autowired private IdAustriaAuthMetadataProvider idAustriaMetadata; @Autowired private IdAustriaAuthCredentialProvider credentialProvider; @Autowired private EidasProxyServiceController eidasProxyEndpoint; @Autowired private ProtocolFinalizationController finalize; @Autowired private IStatusMessenger messager; /** * jUnit class initializer. * @throws InterruptedException In case of an error * @throws ComponentInitializationException In case of an error * @throws InitializationException In case of an error * */ @BeforeClass @SneakyThrows public static void classInitializer() { final String current = new java.io.File(".").toURI().toString(); System.clearProperty("eidas.ms-proxy.configuration"); //eIDAS Ref. Impl. properties System.setProperty("EIDAS_CONFIG_REPOSITORY", current.substring("file:".length()) + "../basicConfig/eIDAS/"); System.setProperty("SPECIFIC_CONNECTOR_CONFIG_REPOSITORY", current.substring("file:".length()) + "../basicConfig/eIDAS/"); System.setProperty("SPECIFIC_PROXY_SERVICE_CONFIG_REPOSITORY", current.substring("file:".length()) + "../basicConfig/eIDAS/"); EaafOpenSaml3xInitializer.eaafInitialize(); } /** * Test shut-down. * * @throws Exception In case of an error */ @AfterClass @SneakyThrows public static void closeIgniteNode() { System.out.println("Closiong Ignite Node ... "); Ignition.stopAll(true); //set Ignite-node holder to 'null' because static holders are shared between different tests final Field field = IgniteInstanceInitializerSpecificCommunication.class.getDeclaredField("instance"); field.setAccessible(true); field.set(null, null); } /** * jUnit test set-up. * * */ @Before public void setup() throws IOException { DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac); @SuppressWarnings("rawtypes") Map filters = wac.getBeansOfType(FilterRegistrationBean.class); for (FilterRegistrationBean filter : filters.values()) { if (filter.isEnabled()) { builder.addFilter(filter.getFilter(), "/*"); } } LogMessageProviderFactory.setStatusMessager(messager); } @Test @SneakyThrows public void simpleError() { MockHttpServletRequest proxyHttpReq = new MockHttpServletRequest("POST", "https://localhost/ms_proxy"); String spCountryCode = injectEidas2AuthnReq(proxyHttpReq); MockHttpServletResponse proxyHttpResp = new MockHttpServletResponse(); RequestContextHolder.resetRequestAttributes(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(proxyHttpReq, proxyHttpResp)); injectIdAustriaSaml2Metadata(); // send eIDAS Proxy-Service process hand-over eidasProxyEndpoint.receiveEidasAuthnRequest(proxyHttpReq, proxyHttpResp); // extract SAML2 AuthnRequest to IDA system assertEquals("forward to finalization", 200, proxyHttpResp.getStatus()); assertEquals("forward to eIDAS Node page", "text/html;charset=UTF-8", proxyHttpResp.getContentType()); String saml2ReqPage = proxyHttpResp.getContentAsString(); assertNotNull("selectionPage is null", saml2ReqPage); assertFalse("selectionPage is empty", saml2ReqPage.isEmpty()); String saml2ReqB64 = extractRequestToken(saml2ReqPage, "> attr = attributes.getAttributeValuesByNameUri(attrName); assertNotNull("Attribute: " + attrName, attr); assertFalse("Empty AttributeValue: " + attrName, attr.isEmpty()); assertNotNull("AttributeValue: " + attrName, attr.asList().get(0)); assertEquals("Wrong AttributeValue: " + attrName, expected, attr.asList().get(0).getValue()); } @SneakyThrows private String validateSaml2Request(String saml2ReqB64, String spCountryCode) { final RequestAbstractType authnReq = (RequestAbstractType) XMLObjectSupport.unmarshallFromInputStream( XMLObjectProviderRegistrySupport.getParserPool(), new ByteArrayInputStream(Base64Utils.decodeFromString(saml2ReqB64))); // check requested attributes assertEquals("wrong number of extension elements", 1, authnReq.getExtensions().getOrderedChildren().size()); assertEquals("wrong number of requested attributes", 5, authnReq.getExtensions().getOrderedChildren().get(0).getOrderedChildren().size()); return authnReq.getID(); } @SneakyThrows private String buildSaml2Response(String saml2ReqId) { final Response response = initializeResponse( "classpath:/data/idp_metadata_classpath_entity.xml", "/data/Response_with_EID.xml", credentialProvider.getMessageSigningCredential(), true, saml2ReqId); return Base64.getEncoder().encodeToString( DomUtils.serializeNode(XMLObjectSupport.getMarshaller(response).marshall(response)).getBytes( "UTF-8")); } private Response initializeResponse(String idpEntityId, String responsePath, EaafX509Credential credential, boolean validConditions, String saml2ReqId) throws SamlSigningException, XMLParserException, UnmarshallingException, Pvp2MetadataException { final Response response = (Response) XMLObjectSupport.unmarshallFromInputStream( XMLObjectProviderRegistrySupport.getParserPool(), FullStartUpAndProcessTest.class.getResourceAsStream(responsePath)); response.setIssueInstant(Instant.now()); final Issuer issuer = Saml2Utils.createSamlObject(Issuer.class); issuer.setValue(idpEntityId); response.setIssuer(issuer); response.setInResponseTo(saml2ReqId); if (validConditions) { response.getAssertions().get(0).getConditions().setNotOnOrAfter(Instant.now().plusSeconds(5*60)); } return Saml2Utils.signSamlObject(response, credential, true); } @SneakyThrows private void injectIdAustriaSaml2Metadata() { final org.springframework.core.io.Resource resource = resourceLoader.getResource(METADATA_PATH); Timer timer = new Timer("PVP metadata-resolver refresh"); ResourceBackedMetadataResolver fileSystemResolver = new ResourceBackedMetadataResolver(timer, new OpenSaml3ResourceAdapter(resource)); fileSystemResolver.setId("test"); fileSystemResolver.setParserPool(XMLObjectProviderRegistrySupport.getParserPool()); fileSystemResolver.initialize(); idAustriaMetadata.addMetadataResolverIntoChain(fileSystemResolver); } private String extractRequestToken(String selectionPage, String selector) { int start = selectionPage.indexOf(selector); assertTrue("find no starting element of selector", start > 0); int end = selectionPage.indexOf("\"", start + selector.length()); assertTrue("find no end tag", end > 0); return selectionPage.substring(start + selector.length(), end); } @SneakyThrows private String injectEidas2AuthnReq(MockHttpServletRequest proxyHttpReq) { String spCountryCode = "XX"; LightRequest.Builder authnReqBuilder = LightRequest.builder() .id(UUID.randomUUID().toString()) .issuer(RandomStringUtils.randomAlphabetic(10)) .citizenCountryCode(RandomStringUtils.randomAlphabetic(2).toUpperCase()) .levelOfAssurance(EaafConstants.EIDAS_LOA_HIGH) .spCountryCode(spCountryCode) .spType("public") .requestedAttributes(ImmutableAttributeMap.builder() .put(attrRegistry.getCoreAttributeRegistry().getByFriendlyName( EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER).first()) .put(attrRegistry.getCoreAttributeRegistry().getByFriendlyName( EidasConstants.eIDAS_ATTR_CURRENTGIVENNAME).first()) .put(attrRegistry.getCoreAttributeRegistry().getByFriendlyName( EidasConstants.eIDAS_ATTR_CURRENTFAMILYNAME).first()) .put(attrRegistry.getCoreAttributeRegistry().getByFriendlyName( EidasConstants.eIDAS_ATTR_DATEOFBIRTH).first()) .build()); final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = (SpecificCommunicationService) wac.getBean( SpecificCommunicationDefinitionBeanNames.SPECIFIC_PROXYSERVICE_COMMUNICATION_SERVICE.toString()); BinaryLightToken token = springManagedSpecificConnectorCommunicationService.putRequest(authnReqBuilder.build()); proxyHttpReq.addParameter(EidasParameterKeys.TOKEN.toString(), Base64Utils.encodeToString(token.getTokenBytes())); return spCountryCode; } }