From 9a16e78a3403200bb100bec5f578fe84fb52c6c6 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Fri, 4 Mar 2022 08:53:06 +0100 Subject: feature(eidas): add optional parameter to support more-than-one MS-Connector stage --- .../specific/connector/MsEidasNodeConstants.java | 2 + .../specific/modules/auth/eidas/v2/Constants.java | 3 + .../eidas/v2/tasks/GenerateAuthnRequestTask.java | 23 +++- .../eidas/v2/tasks/ReceiveAuthnResponseTask.java | 139 ++++++++++++++------- .../resources/eIDAS.Authentication.process.xml | 19 ++- .../test/tasks/GenerateAuthnRequestTaskTest.java | 41 +++++- .../test/tasks/ReceiveEidasResponseTaskTest.java | 31 ++++- 7 files changed, 194 insertions(+), 64 deletions(-) diff --git a/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java b/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java index 027d0832..525043db 100644 --- a/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java +++ b/connector_lib/src/main/java/at/asitplus/eidas/specific/connector/MsEidasNodeConstants.java @@ -156,6 +156,8 @@ public class MsEidasNodeConstants { public static final String REQ_PARAM_SELECTED_ENVIRONMENT = "selectedEnvironment"; public static final String REQ_PARAM_STOP_PROCESS = "stopAuthProcess"; + public static final String EXECCONTEXT_PARAM_MSCONNECTOR_STAGING = "msConnectorStaging"; + public static final String REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_PRODUCTION = "prod"; public static final String REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS = "qs"; public static final String REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING = "test"; diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java index 1732a61a..ea1dc97a 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java @@ -125,6 +125,9 @@ public class Constants { public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE = CONIG_PROPS_EIDAS_SZRCLIENT + ".revisionlog.eidmapping.active"; + public static final String CONIG_PROPS_EIDAS_WORKAROUND_STAGING_MS_CONNECTOR = + CONIG_PROPS_EIDAS_SZRCLIENT + ".workarounds.staging.msconnector.endpoint"; + @Deprecated public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_SQLLITEDATASTORE_URL = CONIG_PROPS_EIDAS_SZRCLIENT + ".workarounds.datastore.sqlite.url"; diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java index 9900fa98..82226d59 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java @@ -46,6 +46,7 @@ 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.api.storage.ITransactionStorage; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import eu.eidas.auth.commons.EidasParameterKeys; @@ -125,14 +126,17 @@ public class GenerateAuthnRequestTask extends AbstractAuthServletTask { // Add country-specific informations into eIDAS request ccSpecificProcessing.preProcess(citizenCountryCode, pendingReq, authnRequestBuilder); - + // build request final LightRequest lightAuthnReq = authnRequestBuilder.build(); - // put request into Hazelcast cache + // put request into shared cache final BinaryLightToken token = putRequestInCommunicationCache(lightAuthnReq); final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); + // Workaround for ms-connector staging + injectStagingWorkaroundForMsConnector(); + // Workaround, because eIDAS node ref. impl. does not return relayState if (basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, @@ -200,6 +204,21 @@ public class GenerateAuthnRequestTask extends AbstractAuthServletTask { } + + private void injectStagingWorkaroundForMsConnector() throws EaafException { + String alternativReturnEndpoint = basicConfig.getBasicConfiguration( + Constants.CONIG_PROPS_EIDAS_WORKAROUND_STAGING_MS_CONNECTOR); + if (StringUtils.isNotEmpty(alternativReturnEndpoint)) { + log.info("Inject alternative MS-Connector end-point: {}", alternativReturnEndpoint); + pendingReq.setRawDataToTransaction( + MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, alternativReturnEndpoint); + + // store pending request after update + requestStoreage.storePendingRequest(pendingReq); + + } + } + /** * Select a forward URL from configuration for a specific environment
*
diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java index 6cab9214..5c5c3461 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java @@ -23,11 +23,16 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; +import java.io.IOException; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; import at.asitplus.eidas.specific.connector.MsConnectorEventCodes; import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; @@ -41,13 +46,22 @@ 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.EidAuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import eu.eidas.auth.commons.EidasParameterKeys; import eu.eidas.auth.commons.light.ILightResponse; +import eu.eidas.auth.commons.tx.BinaryLightToken; +import eu.eidas.specificcommunication.BinaryLightTokenHelper; +import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; +import eu.eidas.specificcommunication.exception.SpecificCommunicationException; +import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; import lombok.extern.slf4j.Slf4j; @Slf4j @Component("ReceiveResponseFromeIDASNodeTask") public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { + @Autowired + ApplicationContext context; + @Autowired private IConfiguration basicConfig; @Autowired @@ -65,54 +79,18 @@ public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { } - log.debug("Receive eIDAS response with RespId:" + eidasResponse.getId() + " for ReqId:" + eidasResponse - .getInResponseToId()); - log.trace("Full eIDAS-Resp: " + eidasResponse.toString()); - revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse - .getId()); - - // check response StatusCode - if (!eidasResponse.getStatus().getStatusCode().equals(Constants.SUCCESS_URI)) { - log.info("Receice eIDAS Response with StatusCode:" + eidasResponse.getStatus().getStatusCode() - + " Subcode:" + eidasResponse.getStatus().getSubStatusCode() + " Msg:" + eidasResponse.getStatus() - .getStatusMessage()); - throw new EidasSAuthenticationException("eidas.02", new Object[] { eidasResponse.getStatus() - .getStatusCode(), eidasResponse.getStatus().getStatusMessage() }); - + String stagingEndpoint = pendingReq.getRawData( + MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, String.class); + if (StringUtils.isNotEmpty(stagingEndpoint)) { + log.info("Find ms-connector staging to: {}. Forwarding to that endpoint ... ", stagingEndpoint); + forwardToOtherStage(response, executionContext, eidasResponse, stagingEndpoint); + + } else { + executionContext.put(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, false); + processResponseOnThatStage(executionContext, eidasResponse); + } - - // extract all Attributes from response - - // ********************************************************** - // ******* MS-specificresponse validation ********** - // ********************************************************** - final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, - "AT"); - final String citizenCountryCode = (String) executionContext.get( - MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); - EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, - attrRegistry); - - // ********************************************************** - // ******* Store resonse infos into session object ********** - // ********************************************************** - - // update MOA-Session data with received information - log.debug("Store eIDAS response information into pending-request."); - final EidAuthProcessDataWrapper authProcessData = pendingReq.getSessionData(EidAuthProcessDataWrapper.class); - authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); - authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); - - //inject set flag to inject - authProcessData.setTestIdentity( - basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_IS_TEST_IDENTITY, false)); - - // store MOA-session to database - requestStoreage.storePendingRequest(pendingReq); - - revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); - } catch (final EaafException e) { revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); throw new TaskExecutionException(pendingReq, "eIDAS Response processing FAILED.", e); @@ -124,7 +102,76 @@ public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { new EidasSAuthenticationException("eidas.05", new Object[] { e.getMessage() }, e)); } + } + + private void forwardToOtherStage(HttpServletResponse response, ExecutionContext executionContext, + ILightResponse eidasResponse, String stagingEndpoint) throws SpecificCommunicationException, IOException { + executionContext.put(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, true); + + final SpecificCommunicationService specificConnectorCommunicationService = + (SpecificCommunicationService) context.getBean( + SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); + BinaryLightToken token = specificConnectorCommunicationService.putResponse(eidasResponse); + final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); + + final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(stagingEndpoint); + redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); + + log.debug("Forward to other stage .... "); + response.sendRedirect(redirectUrl.build().encode().toString()); + + } + + private void processResponseOnThatStage(ExecutionContext executionContext, ILightResponse eidasResponse) + throws EaafException { + log.debug("Receive eIDAS response with RespId:" + eidasResponse.getId() + " for ReqId:" + eidasResponse + .getInResponseToId()); + log.trace("Full eIDAS-Resp: " + eidasResponse.toString()); + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse + .getId()); + + // check response StatusCode + if (!eidasResponse.getStatus().getStatusCode().equals(Constants.SUCCESS_URI)) { + log.info("Receice eIDAS Response with StatusCode:" + eidasResponse.getStatus().getStatusCode() + + " Subcode:" + eidasResponse.getStatus().getSubStatusCode() + " Msg:" + eidasResponse.getStatus() + .getStatusMessage()); + throw new EidasSAuthenticationException("eidas.02", new Object[] { eidasResponse.getStatus() + .getStatusCode(), eidasResponse.getStatus().getStatusMessage() }); + + } + // extract all Attributes from response + + // ********************************************************** + // ******* MS-specificresponse validation ********** + // ********************************************************** + final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, + "AT"); + final String citizenCountryCode = (String) executionContext.get( + MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); + EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, + attrRegistry); + + // ********************************************************** + // ******* Store resonse infos into session object ********** + // ********************************************************** + + // update MOA-Session data with received information + log.debug("Store eIDAS response information into pending-request."); + final EidAuthProcessDataWrapper authProcessData = pendingReq.getSessionData(EidAuthProcessDataWrapper.class); + authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); + authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); + + + //inject set flag to inject + authProcessData.setTestIdentity( + basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_IS_TEST_IDENTITY, false)); + + // store MOA-session to database + requestStoreage.storePendingRequest(pendingReq); + + revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); + } } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/resources/eIDAS.Authentication.process.xml b/eidas_modules/authmodule-eIDAS-v2/src/main/resources/eIDAS.Authentication.process.xml index 55bb1ace..e645c50e 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/resources/eIDAS.Authentication.process.xml +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/resources/eIDAS.Authentication.process.xml @@ -13,17 +13,14 @@ - - - - - - + + + + + + + diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/GenerateAuthnRequestTaskTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/GenerateAuthnRequestTaskTest.java index 4edfe32d..9e4507a9 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/GenerateAuthnRequestTaskTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/GenerateAuthnRequestTaskTest.java @@ -93,7 +93,8 @@ public class GenerateAuthnRequestTaskTest { "http://test/" + RandomStringUtils.randomAlphabetic(5)); basicConfig.putConfigValue( "eidas.ms.auth.eIDAS.node_v2.forward.method", "GET"); - basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.requested.nameIdFormat"); + basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.requested.nameIdFormat"); + basicConfig.removeConfigValue(Constants.CONIG_PROPS_EIDAS_WORKAROUND_STAGING_MS_CONNECTOR); } @@ -284,10 +285,42 @@ public class GenerateAuthnRequestTaskTest { Assert.assertEquals("ProviderName is not Static", Constants.DEFAULT_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP, eidasReq.getProviderName()); Assert.assertEquals("no PublicSP", "public", eidasReq.getSpType()); - Assert.assertEquals("wrong LoA", "http://eidas.europa.eu/LoA/high", eidasReq.getLevelOfAssurance()); + Assert.assertEquals("wrong LoA", "http://eidas.europa.eu/LoA/high", eidasReq.getLevelOfAssurance()); + Assert.assertNull("msConnector Staging", + pendingReq.getRawData(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, String.class)); } + @Test + public void withMsConnectorStaging() throws TaskExecutionException, + SpecificCommunicationException { + executionContext.put(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY, "CC"); + + basicConfig.putConfigValue( + "eidas.ms.auth.eIDAS.node_v2.publicSectorTargets", ".*"); + basicConfig.putConfigValue( + "eidas.ms.auth.eIDAS.node_v2.workarounds.addAlwaysProviderName", "true"); + basicConfig.putConfigValue( + "eidas.ms.auth.eIDAS.node_v2.workarounds.useRequestIdAsTransactionIdentifier", "true"); + basicConfig.putConfigValue( + "eidas.ms.auth.eIDAS.node_v2.workarounds.useStaticProviderNameForPublicSPs", "true"); + basicConfig.removeConfigValue("eidas.ms.auth.eIDAS.node_v2.staticProviderNameForPublicSPs"); + + String msConnectorStage = RandomStringUtils.randomAlphanumeric(10); + basicConfig.putConfigValue(Constants.CONIG_PROPS_EIDAS_WORKAROUND_STAGING_MS_CONNECTOR, msConnectorStage); + + + //execute test + task.execute(pendingReq, executionContext); + + //validate state + Assert.assertEquals("msConnector Staging", msConnectorStage, + pendingReq.getRawData(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, String.class)); + + + + } + @Test public void withCustomStaticProviderNameForPublicSPs() throws TaskExecutionException, SpecificCommunicationException { @@ -458,8 +491,8 @@ public class GenerateAuthnRequestTaskTest { final ILightRequest eidasReq = commService.getAndRemoveRequest(null, null); - Assert.assertEquals("PrividerName", "myNode", eidasReq.getProviderName()); - Assert.assertEquals("RequesterId", "myNode", eidasReq.getRequesterId()); + Assert.assertEquals("PrividerName", "Austria", eidasReq.getProviderName()); + Assert.assertEquals("RequesterId", "Austria", eidasReq.getRequesterId()); Assert.assertEquals("no PublicSP", "private", eidasReq.getSpType()); Assert.assertEquals("wrong LoA", EaafConstants.EIDAS_LOA_HIGH, eidasReq.getLevelOfAssurance()); diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveEidasResponseTaskTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveEidasResponseTaskTest.java index 0e56e2b3..7bf2c2db 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveEidasResponseTaskTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveEidasResponseTaskTest.java @@ -140,12 +140,39 @@ public class ReceiveEidasResponseTaskTest { } } + @Test + public void successAndForward() throws URISyntaxException, TaskExecutionException, + PendingReqIdValidationException, EaafStorageException { + + AuthenticationResponse eidasResponse = buildDummyAuthResponse(Constants.SUCCESS_URI); + httpReq.setAttribute(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); + executionContext.put(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY, "LU"); + + String alternativReturnEndpoint = "http://ms-connector.alternative/" + RandomStringUtils.randomAlphabetic(10); + pendingReq.setRawDataToTransaction( + MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, alternativReturnEndpoint); + + //execute test + task.execute(pendingReq, executionContext); + + //validate state + Assert.assertEquals("msConnectorStage", true, + (Boolean) executionContext.get(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING)); + + //validate state + Assert.assertEquals("Wrong http statusCode", 302, httpResp.getStatus()); + Assert.assertNotNull("No redirect header", httpResp.getHeaderValue("Location")); + Assert.assertTrue("Wrong redirect endpoint", + ((String) httpResp.getHeaderValue("Location")).startsWith(alternativReturnEndpoint)); + + + } + @Test public void success() throws URISyntaxException, TaskExecutionException, PendingReqIdValidationException { @NotNull AuthenticationResponse eidasResponse = buildDummyAuthResponse(Constants.SUCCESS_URI); httpReq.setAttribute(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); - executionContext.put(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY, "LU"); //execute test task.execute(pendingReq, executionContext); @@ -162,6 +189,8 @@ public class ReceiveEidasResponseTaskTest { authProcessData.getGenericDataFromSession(Constants.DATA_FULL_EIDAS_RESPONSE)); Assert.assertFalse("testIdentity flag", authProcessData.isTestIdentity()); + Assert.assertEquals("msConnectorStage", false, + (Boolean) executionContext.get(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING)); } @Test -- cgit v1.2.3