package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.tasks; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.springframework.util.Assert.isInstanceOf; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Objects; import javax.xml.transform.TransformerException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.MarshallingException; import org.opensaml.core.xml.util.XMLObjectSupport; import org.opensaml.saml.saml2.core.Issuer; import org.opensaml.saml.saml2.core.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.google.common.collect.Lists; import at.asitplus.eidas.specific.core.test.config.dummy.MsConnectorDummyConfigMap; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.RegisterResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.ManualFixNecessaryException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthCredentialProvider; import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthMetadataProvider; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterOperationStatus; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterStatusResults; import at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveMobilePhoneSignatureResponseTask; import at.asitplus.eidas.specific.modules.auth.eidas.v2.test.dummy.DummyOA; import at.asitplus.eidas.specific.modules.auth.eidas.v2.test.dummy.DummyPendingRequest; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; 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.EaafException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl; import at.gv.egiz.eaaf.core.impl.utils.DomUtils; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.PvpMetadataResolverFactory; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.initialize.EaafOpenSaml3xInitializer; import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnResponseValidationException; import net.shibboleth.utilities.java.support.xml.ParserPool; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/SpringTest-context_tasks_test.xml", "/SpringTest-context_basic_mapConfig.xml" }) public class ReceiveMobilePhoneSignatureResponseTaskTest { private static final String METADATA_PATH = "classpath:/data/idp_metadata_classpath_entity.xml"; private static final String BPK_FROM_ID_AUSTRIA = "QVGm48cqcM4UcyhDTNGYmVdrIoY="; @Autowired protected MsConnectorDummyConfigMap authConfig; @Autowired private IdAustriaClientAuthMetadataProvider metadataProvider; @Autowired private IdAustriaClientAuthCredentialProvider credentialProvider; @Autowired private PvpMetadataResolverFactory metadataFactory; @Autowired private ReceiveMobilePhoneSignatureResponseTask task; @MockBean private RegisterSearchService registerSearchService; private final ExecutionContext executionContext = new ExecutionContextImpl(); private MockHttpServletRequest httpReq; private MockHttpServletResponse httpResp; private DummyPendingRequest pendingReq; /** * JUnit class initializer. * * @throws Exception In case of an OpenSAML3 initialization error */ @BeforeClass public static void initialize() throws Exception { EaafOpenSaml3xInitializer.eaafInitialize(); } /** * jUnit test set-up. * * @throws Exception In case of an set-up error */ @Before public void setUp() throws Exception { httpReq = new MockHttpServletRequest("POST", "https://localhost/authhandler"); httpReq.setScheme("https"); httpReq.setServerPort(443); httpReq.setContextPath("/authhandler"); httpResp = new MockHttpServletResponse(); RequestContextHolder.resetRequestAttributes(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpReq, httpResp)); authConfig.putConfigValue(IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_ENTITYID, METADATA_PATH); DummyOA oaParam = new DummyOA(); oaParam.setUniqueAppId("http://test.com/test"); oaParam.setTargetIdentifier(EaafConstants.URN_PREFIX_CDID + RandomStringUtils.randomAlphabetic(2)); pendingReq = new DummyPendingRequest(); pendingReq.initialize(httpReq, authConfig); pendingReq.setPendingRequestId(RandomStringUtils.randomAlphanumeric(10)); pendingReq.setOnlineApplicationConfiguration(oaParam); metadataProvider.fullyDestroy(); } @Test public void unsupportedHttpMethod() { httpReq = new MockHttpServletRequest("PUT", "https://localhost/authhandler"); RequestContextHolder.resetRequestAttributes(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpReq, httpResp)); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.03", ((AuthnResponseValidationException) e.getOriginalException()).getErrorId()); } @Test public void httpGetNoMessage() { httpReq = new MockHttpServletRequest("GET", "https://localhost/authhandler"); RequestContextHolder.resetRequestAttributes(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpReq, httpResp)); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.12", ((AuthnResponseValidationException) e.getOriginalException()).getErrorId()); } @Test public void httpPostNoMessage() { httpReq = new MockHttpServletRequest("POST", "https://localhost/authhandler"); RequestContextHolder.resetRequestAttributes(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpReq, httpResp)); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.12", ((AuthnResponseValidationException) e.getOriginalException()).getErrorId()); } @Test public void httpPostMessageNotSigned() throws IOException { byte[] bytes = IOUtils.toByteArray(ReceiveMobilePhoneSignatureResponseTask.class .getResourceAsStream("/data/Response_without_sig_classpath_entityid.xml")); httpReq.addParameter("SAMLResponse", Base64.getEncoder().encodeToString(bytes)); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.12", ((AuthnResponseValidationException) e.getOriginalException()).getErrorId()); } @Test public void httpPostMessageWrongDestinationEndpoint() throws Exception { initResponse("/data/Response_with_wrong_destination_endpoint.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.12", ((AuthnResponseValidationException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedNoMetadata() throws Exception { initResponse("/data/Response_without_sig_classpath_entityid.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.11", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedAssertionOutDated() throws Exception { setupMetadataResolver(); initResponse("/data/Response_without_sig_classpath_entityid.xml", false); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.12", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedAssertionFromWrongIdp() throws Exception { authConfig.putConfigValue(IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_ENTITYID, "http://wrong.idp/" + RandomStringUtils.randomAlphabetic(5)); setupMetadataResolver(); initResponse("/data/Response_without_sig_classpath_entityid.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.08", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedAssertionMissingAttributes() throws Exception { setupMetadataResolver(); initResponse("/data/Response_without_sig_classpath_entityid.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.12", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedWithError() throws Exception { setupMetadataResolver(); initResponse("/data/Response_without_sig_with_error.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.05", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedWitUserStopErrorCode() throws Exception { setupMetadataResolver(); initResponse("/data/Response_without_sig_with_error_userstop.xml", true); task.execute(pendingReq, executionContext); assertEquals("Transition To S16", true, executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK)); assertEquals("matching failed flag", true, executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)); assertEquals("failed reason", "module.eidasauth.matching.23", executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON)); assertNull("no final matching result", MatchingTaskUtils.getFinalMatchingResult(pendingReq)); } @Test public void httpPostValidSignedWithErrorAndNoSubCode() throws Exception { setupMetadataResolver(); initResponse("/data/Response_without_sig_with_error_without_subcode.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.05", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedWithErrorAndEmptySubCode() throws Exception { setupMetadataResolver(); initResponse("/data/Response_without_sig_with_error_empty_subcode.xml", true); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); assertEquals("sp.pvp2.05", ((EaafException) e.getOriginalException()).getErrorId()); } @Test public void httpPostValidSignedAssertionEidValidButNameMismatch() throws Exception { setupMetadataResolver(); initResponse("/data/Response_with_EID.xml", true); AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); SimpleEidasData eidData = createEidasDataMatchingToSamlResponse() .familyName("notmatching") .build(); authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); task.execute(pendingReq, executionContext); assertEquals("Next task", true, executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK)); assertEquals("matching failed flag", true, executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)); assertEquals("failed reason", "module.eidasauth.matching.24", executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON)); assertNull("no final matching result", MatchingTaskUtils.getFinalMatchingResult(pendingReq)); } //TODO: implement new test that this test makes no sense any more @Ignore @Test public void httpPostValidSignedAssertionEidValid_NoRegisterResult() throws Exception { setupMetadataResolver(); initResponse("/data/Response_with_EID.xml", true); AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); SimpleEidasData eidData = createEidasDataMatchingToSamlResponse().build(); authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); RegisterStatusResults registerSearchResult = new RegisterStatusResults(new RegisterOperationStatus(generateRandomProcessId(), true), Collections.emptyList(), Collections.emptyList()); MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); task.execute(pendingReq, executionContext); AuthProcessDataWrapper session = pendingReq.getSessionData(AuthProcessDataWrapper.class); assertEquals("LoA", "http://eidas.europa.eu/LoA/low", session.getQaaLevel()); assertEquals("IssueInstant", "2014-03-05T06:39:51Z", session.getIssueInstantString()); assertEquals("Transition To S16", true, executionContext.get(Constants.TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK)); } @Test public void httpPostValidSignedAssertionEidValid_ExactlyOneRegisterResult() throws Exception { setupMetadataResolver(); initResponse("/data/Response_with_EID.xml", true); AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); SimpleEidasData eidData = createEidasDataMatchingToSamlResponse().build(); authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); RegisterStatusResults registerSearchResult = buildResultWithOneMatch(); MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); task.execute(pendingReq, executionContext); AuthProcessDataWrapper session = pendingReq.getSessionData(AuthProcessDataWrapper.class); assertEquals("LoA", "http://eidas.europa.eu/LoA/low", session.getQaaLevel()); assertEquals("IssueInstant", "2014-03-05T06:39:51Z", session.getIssueInstantString()); assertNull("Transition To S16", executionContext.get(Constants.TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK)); //TODO: update this check because this task selects one result from MDS search result before and creates a new element //Mockito.verify(registerSearchService).step7aKittProcess(eq(registerSearchResult), eq(eidData)); } @Test public void httpPostValidSignedAssertionEidValid_ExactlyOneRegisterResultDeprecadedBpkEnc() throws Exception { setupMetadataResolver(); initResponse("/data/Response_with_EID_deprecated_bpk_encoding.xml", true); AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); SimpleEidasData eidData = createEidasDataMatchingToSamlResponse().build(); authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); RegisterStatusResults registerSearchResult = buildResultWithOneMatch(); MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); task.execute(pendingReq, executionContext); AuthProcessDataWrapper session = pendingReq.getSessionData(AuthProcessDataWrapper.class); assertEquals("LoA", "http://eidas.europa.eu/LoA/low", session.getQaaLevel()); assertEquals("IssueInstant", "2014-03-05T06:39:51Z", session.getIssueInstantString()); assertNull("Transition To S16", executionContext.get(Constants.TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK)); //TODO: update this check because this task selects one result from MDS search result before and creates a new element //Mockito.verify(registerSearchService).step7aKittProcess(eq(registerSearchResult), eq(eidData)); } //TODO: implement new test that this test makes no sense any more @Ignore @Test public void httpPostValidSignedAssertionEidValid_MoreThanOneRegisterResult() throws Exception { setupMetadataResolver(); initResponse("/data/Response_with_EID.xml", true); AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); SimpleEidasData eidData = createEidasDataMatchingToSamlResponse().build(); authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); TaskExecutionException e = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); isInstanceOf(AuthnResponseValidationException.class, e.getOriginalException()); isInstanceOf(ManualFixNecessaryException.class, e.getOriginalException().getCause()); assertEquals("sp.pvp2.12", ((AuthnResponseValidationException) e.getOriginalException()).getErrorId()); AuthProcessDataWrapper session = pendingReq.getSessionData(AuthProcessDataWrapper.class); assertNull("Transition To S16", executionContext.get(Constants.TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK)); } @NotNull private RegisterStatusResults buildResultWithOneMatch() { return new RegisterStatusResults(new RegisterOperationStatus(generateRandomProcessId(), true), Collections.singletonList(RegisterResult.builder() .bpk(BPK_FROM_ID_AUSTRIA) .pseudonym(Arrays.asList("bar")) .givenName("foo") .familyName("foo") .dateOfBirth("bar") .build()), Collections.emptyList()); } @NotNull private RegisterStatusResults buildResultWithTwoMatches() { List results = Lists.newArrayList( RegisterResult.builder() .bpk(BPK_FROM_ID_AUSTRIA) .pseudonym(Arrays.asList("bar")) .givenName("foo") .familyName("foo") .dateOfBirth("bar") .build(), RegisterResult.builder() .bpk("bpk") .pseudonym(Arrays.asList("pseudonym")) .givenName("givenName") .familyName("familyName") .dateOfBirth("dateOfBirth") .build()); return new RegisterStatusResults(new RegisterOperationStatus(generateRandomProcessId(), true), results, Collections.emptyList()); } private BigInteger generateRandomProcessId() { return new BigInteger(RandomStringUtils.randomNumeric(10)); } private SimpleEidasData.SimpleEidasDataBuilder createEidasDataMatchingToSamlResponse() { // data from "/data/Response_with_EID.xml" return SimpleEidasData.builder() .familyName("Mustermann") .givenName("Max") .dateOfBirth("1940-01-01"); } private void addSamlResponseToHttpReq(Response response) throws TransformerException, IOException, MarshallingException { String node = DomUtils.serializeNode(XMLObjectSupport.getMarshaller(response).marshall(response)); String base64encoded = Base64.getEncoder().encodeToString(node.getBytes(StandardCharsets.UTF_8)); httpReq.addParameter("SAMLResponse", base64encoded); } private void initResponse(String responsePath, boolean validConditions) throws Exception { InputStream inputStream = ReceiveMobilePhoneSignatureResponseTaskTest.class.getResourceAsStream(responsePath); ParserPool parserPool = Objects.requireNonNull(XMLObjectProviderRegistrySupport.getParserPool()); Response response = (Response) XMLObjectSupport.unmarshallFromInputStream(parserPool, inputStream); response.setIssueInstant(Instant.now()); Issuer issuer = Saml2Utils.createSamlObject(Issuer.class); issuer.setValue("classpath:/data/idp_metadata_classpath_entity.xml"); response.setIssuer(issuer); if (validConditions) { response.getAssertions().get(0).getConditions().setNotOnOrAfter(Instant.now().plusSeconds(5*60)); } Response signedResponse = Saml2Utils.signSamlObject(response, credentialProvider.getMessageSigningCredential(), true); addSamlResponseToHttpReq(signedResponse); } private void setupMetadataResolver() throws Pvp2MetadataException { metadataProvider.addMetadataResolverIntoChain(metadataFactory.createMetadataProvider( METADATA_PATH, null, "jUnit IDP", null)); } }