From cda12dc5d04257bb4d739a07af167001480fb62e Mon Sep 17 00:00:00 2001 From: Shubhangi-cs Date: Mon, 18 Dec 2023 14:57:20 +0530 Subject: [PATCH] Proxy implementation --- README.md | 33 ++++++++++- docs/SuccessFactors-batchsource.md | 4 ++ docs/SuccessFactors-connector.md | 5 ++ .../common/util/SuccessFactorsUtil.java | 3 +- .../connector/SuccessFactorsConnector.java | 9 +-- .../SuccessFactorsConnectorConfig.java | 47 ++++++++++++++- .../source/SuccessFactorsSource.java | 13 +++- .../config/SuccessFactorsPluginConfig.java | 30 +++++++++- .../transport/SuccessFactorsTransporter.java | 59 ++++++++++++++++--- .../SuccessFactorsConnectorTest.java | 17 +----- .../source/SuccessFactorsSourceTest.java | 6 +- .../input/SuccessFactorsInputFormatTest.java | 3 +- .../SuccessFactorsSchemaGeneratorTest.java | 13 ++-- ...timeFunctionalForAssociatedEntityTest.java | 3 +- .../transport/RuntimeFunctionalTest.java | 9 +-- .../SuccessFactorsTransporterTest.java | 53 ++++++++++++++--- .../SuccessFactorsUrlContainerTest.java | 6 +- widgets/SuccessFactors-batchsource.json | 49 +++++++++++++++ widgets/SuccessFactors-connector.json | 39 ++++++++++++ 19 files changed, 326 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 8cb87a8..81b8d0c 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# successfactors-plugins \ No newline at end of file +# successfactors-plugins + +## Overview + +This repository contains resources and documentation for integrating and working with SAP SuccessFactors. SuccessFactors is a cloud-based human capital management (HCM) suite that helps organizations manage their workforce effectively. + +## Prerequisites + +Before you begin, ensure you have the following: + +- **SuccessFactors Account:** Obtain access to SAP SuccessFactors and have the necessary permissions to configure integrations. + +- **API Access:** Set up API access and obtain the required API credentials from your SuccessFactors instance. + +# License and Trademarks + +Copyright © 2022 Cask Data, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the +License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See the License for the specific language governing permissions +and limitations under the License. + +Cask is a trademark of Cask Data, Inc. All rights reserved. + +Apache, Apache HBase, and HBase are trademarks of The Apache Software Foundation. Used with +permission. No endorsement by The Apache Software Foundation is implied by the use of these marks. diff --git a/docs/SuccessFactors-batchsource.md b/docs/SuccessFactors-batchsource.md index 05df6e6..be73309 100644 --- a/docs/SuccessFactors-batchsource.md +++ b/docs/SuccessFactors-batchsource.md @@ -23,6 +23,10 @@ You also can use the macro function ${conn(connection-name)}. **SAP SuccessFactors Logon Password (M)**: SAP SuccessFactors Logon password for user authentication. **SAP SuccessFactors Base URL (M)**: SAP SuccessFactors Base URL. +## Proxy Configuration +**Proxy URL:** Proxy URL. Must contain a protocol, address and port. +**Username:** Proxy username. +**Password:** Proxy password. ## Advance Option: diff --git a/docs/SuccessFactors-connector.md b/docs/SuccessFactors-connector.md index 3430674..85739a9 100644 --- a/docs/SuccessFactors-connector.md +++ b/docs/SuccessFactors-connector.md @@ -16,6 +16,11 @@ Properties **SAP SuccessFactors Base URL (M)**: SAP SuccessFactors Base URL. +**Proxy URL:** Proxy URL. Must contain a protocol, address and port. + +**Username:** Proxy username. + +**Password:** Proxy password. Path of the connection ---------------------- diff --git a/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsUtil.java b/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsUtil.java index e646586..da2298f 100644 --- a/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsUtil.java +++ b/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsUtil.java @@ -97,8 +97,7 @@ public static String trim(String rawString) { * @return SuccessFactorsService instance */ public static SuccessFactorsService getSuccessFactorsService(SuccessFactorsPluginConfig pluginConfig) { - SuccessFactorsTransporter transporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); + SuccessFactorsTransporter transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); SuccessFactorsService successFactorsService = new SuccessFactorsService(pluginConfig, transporter); return successFactorsService; } diff --git a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java index 6f4e723..da10995 100644 --- a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java +++ b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java @@ -149,8 +149,7 @@ public ConnectorSpec generateSpec(ConnectorContext connectorContext, ConnectorSp List listEntities() throws TransportException, IOException { URL dataURL = HttpUrl.parse(config.getBaseURL()).newBuilder().build().url(); - SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config.getUsername(), - config.getPassword()); + SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config); SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient.callSuccessFactorsEntity (dataURL, MediaType.APPLICATION_JSON, METADATA); try (InputStream inputStream = responseContainer.getResponseStream()) { @@ -225,8 +224,7 @@ private InputStream callEntityData(long top, String entityName) URL dataURL = HttpUrl.parse(config.getBaseURL()).newBuilder().addPathSegment(entityName). addQueryParameter(TOP_OPTION, String.valueOf(top)).addQueryParameter(SELECT_OPTION, selectFields.toString()) .build().url(); - SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config.getUsername(), - config.getPassword()); + SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config); SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient.callSuccessFactorsWithRetry(dataURL); ExceptionParser.checkAndThrowException("", responseContainer); @@ -255,8 +253,7 @@ SuccessFactorsEntityProvider fetchServiceMetadata(String entity) throws Transpor private InputStream getMetaDataStream(String entity) throws TransportException, IOException { URL metadataURL = HttpUrl.parse(config.getBaseURL()).newBuilder().addPathSegments(entity) .addPathSegment(METADATACALL).build().url(); - SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config.getUsername(), - config.getPassword()); + SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config); SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient .callSuccessFactorsEntity(metadataURL, MediaType.APPLICATION_XML, METADATA); return responseContainer.getResponseStream(); diff --git a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java index 50f96c5..02bac6c 100644 --- a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java +++ b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java @@ -33,6 +33,8 @@ import java.net.HttpURLConnection; import java.net.URL; + +import javax.annotation.Nullable; import javax.ws.rs.core.MediaType; /** @@ -43,6 +45,9 @@ public class SuccessFactorsConnectorConfig extends PluginConfig { public static final String BASE_URL = "baseURL"; public static final String UNAME = "username"; public static final String PASSWORD = "password"; + public static final String PROPERTY_PROXY_URL = "proxyUrl"; + public static final String PROPERTY_PROXY_USERNAME = "proxyUsername"; + public static final String PROPERTY_PROXY_PASSWORD = "proxyPassword"; public static final String TEST = "TEST"; private static final String COMMON_ACTION = ResourceConstants.ERR_MISSING_PARAM_OR_MACRO_ACTION.getMsgForKey(); private static final String SAP_SUCCESSFACTORS_USERNAME = "SAP SuccessFactors Username"; @@ -65,10 +70,44 @@ public class SuccessFactorsConnectorConfig extends PluginConfig { @Description("SuccessFactors Base URL.") private final String baseURL; - public SuccessFactorsConnectorConfig(String username, String password, String baseURL) { + @Nullable + @Name(PROPERTY_PROXY_URL) + @Description("Proxy URL. Must contain a protocol, address and port.") + @Macro + private String proxyUrl; + + @Nullable + @Name(PROPERTY_PROXY_USERNAME) + @Description("Proxy username.") + @Macro + private String proxyUsername; + + @Nullable + @Name(PROPERTY_PROXY_PASSWORD) + @Description("Proxy password.") + @Macro + private String proxyPassword; + + public SuccessFactorsConnectorConfig(String username, String password, String baseURL, String proxyUrl, + String proxyUsername, String proxyPassword) { this.username = username; this.password = password; this.baseURL = baseURL; + this.proxyUrl = proxyUrl; + this.proxyUsername = proxyUsername; + this.proxyPassword = proxyPassword; + } + + public String getProxyUrl() { + return proxyUrl; + } + + public String getProxyUsername() { + return proxyUsername; + } + + public String getProxyPassword() { + return proxyPassword; } public String getUsername() { @@ -109,7 +148,7 @@ public void validateBasicCredentials(FailureCollector failureCollector) { * Method to validate the credential fields. */ public void validateConnection(FailureCollector collector) { - SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(getUsername(), getPassword()); + SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(this); URL testerURL = HttpUrl.parse(getBaseURL()).newBuilder().build().url(); SuccessFactorsResponseContainer responseContainer = null; try { @@ -117,7 +156,9 @@ public void validateConnection(FailureCollector collector) { successFactorsHttpClient.callSuccessFactorsEntity(testerURL, MediaType.APPLICATION_JSON, TEST); } catch (TransportException e) { LOG.error("Unable to fetch the response", e); - collector.addFailure("Unable to call SuccessFatorsEntity", "Please check the values"); + collector.addFailure("Unable to call SuccessFactorsEntity", + "Please check the values for basic and proxy parameters if proxy exists."); + return; } if (responseContainer.getHttpStatusCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { String errMsg = ResourceConstants.ERR_INVALID_CREDENTIAL.getMsgForKey(); diff --git a/src/main/java/io/cdap/plugin/successfactors/source/SuccessFactorsSource.java b/src/main/java/io/cdap/plugin/successfactors/source/SuccessFactorsSource.java index ec8087e..2e5a49c 100644 --- a/src/main/java/io/cdap/plugin/successfactors/source/SuccessFactorsSource.java +++ b/src/main/java/io/cdap/plugin/successfactors/source/SuccessFactorsSource.java @@ -39,6 +39,7 @@ import io.cdap.plugin.successfactors.common.util.ResourceConstants; import io.cdap.plugin.successfactors.common.util.SuccessFactorsUtil; import io.cdap.plugin.successfactors.connector.SuccessFactorsConnector; +import io.cdap.plugin.successfactors.connector.SuccessFactorsConnectorConfig; import io.cdap.plugin.successfactors.source.config.SuccessFactorsPluginConfig; import io.cdap.plugin.successfactors.source.input.SuccessFactorsInputFormat; import io.cdap.plugin.successfactors.source.input.SuccessFactorsInputSplit; @@ -126,8 +127,7 @@ public void prepareRun(BatchSourceContext context) throws Exception { @Nullable private Schema getOutputSchema(FailureCollector failureCollector) { if (config.getConnection() != null) { - SuccessFactorsTransporter transporter = new SuccessFactorsTransporter(config.getConnection().getUsername(), - config.getConnection().getPassword()); + SuccessFactorsTransporter transporter = new SuccessFactorsTransporter(config.getConnection()); SuccessFactorsService successFactorsServices = new SuccessFactorsService(config, transporter); try { //validate if the given parameters form a valid SuccessFactors URL. @@ -136,7 +136,14 @@ private Schema getOutputSchema(FailureCollector failureCollector) { } catch (TransportException te) { String errorMsg = ExceptionParser.buildTransportError(te); errorMsg = ResourceConstants.ERR_ODATA_SERVICE_CALL.getMsgForKeyWithCode(errorMsg); - failureCollector.addFailure(errorMsg, null).withConfigProperty(SuccessFactorsPluginConfig.BASE_URL); + if (SuccessFactorsUtil.isNullOrEmpty(config.getConnection().getProxyUrl())) { + failureCollector.addFailure(errorMsg, null).withConfigProperty(SuccessFactorsPluginConfig.BASE_URL); + } else { + failureCollector.addFailure(errorMsg, + "Unable to connect to successFactors. Please check the basic and proxy connection parameters") + .withConfigProperty(SuccessFactorsPluginConfig.BASE_URL) + .withConfigProperty(SuccessFactorsConnectorConfig.PROPERTY_PROXY_URL); + } } catch (SuccessFactorsServiceException ose) { attachFieldWithError(ose, failureCollector); } diff --git a/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java b/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java index c03bcf9..62849fa 100644 --- a/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java +++ b/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java @@ -135,12 +135,16 @@ public SuccessFactorsPluginConfig(String referenceName, String associateEntityName, @Nullable String username, @Nullable String password, + @Nullable String proxyUrl, + @Nullable String proxyPassword, + @Nullable String proxyUsername, @Nullable String filterOption, @Nullable String selectOption, @Nullable String expandOption, @Nullable String additionalQueryParameters, String paginationType) { - this.connection = new SuccessFactorsConnectorConfig(username, password, baseURL); + this.connection = new SuccessFactorsConnectorConfig(username, password, baseURL, proxyUrl, proxyPassword, + proxyUsername); this.referenceName = referenceName; this.entityName = entityName; this.associateEntityName = associateEntityName; @@ -214,7 +218,10 @@ public String getAdditionalQueryParameters() { * @return boolean flag as per the check */ public boolean isSchemaBuildRequired() { - return !(containsMacro(UNAME) || containsMacro(PASSWORD) || containsMacro(BASE_URL) || containsMacro(ENTITY_NAME)); + return !(containsMacro(UNAME) || containsMacro(PASSWORD) || containsMacro(BASE_URL) || containsMacro(ENTITY_NAME) + || containsMacro(SuccessFactorsConnectorConfig.PROPERTY_PROXY_URL) + || containsMacro(SuccessFactorsConnectorConfig.PROPERTY_PROXY_USERNAME) + || containsMacro(SuccessFactorsConnectorConfig.PROPERTY_PROXY_PASSWORD)); } /** @@ -300,6 +307,9 @@ public static class Builder { private String expandOption; private String paginationType; private String additionalQueryParameters; + private String proxyUrl; + private String proxyUsername; + private String proxyPassword; public Builder referenceName(String referenceName) { this.referenceName = referenceName; @@ -331,6 +341,19 @@ public Builder password(@Nullable String password) { return this; } + public Builder proxyUrl(@Nullable String proxyUrl) { + this.proxyUrl = proxyUrl; + return this; + } + public Builder proxyUsername(@Nullable String proxyUsername) { + this.proxyUsername = proxyUsername; + return this; + } + public Builder proxyPassword(@Nullable String proxyPassword) { + this.proxyPassword = proxyPassword; + return this; + } + public Builder filterOption(@Nullable String filterOption) { this.filterOption = filterOption; return this; @@ -357,7 +380,8 @@ public Builder additionalQueryParameters(@Nullable String additionalQueryParamet } public SuccessFactorsPluginConfig build() { - return new SuccessFactorsPluginConfig(referenceName, baseURL, entityName, associateEntityName, username, password, + return new SuccessFactorsPluginConfig(referenceName, baseURL, entityName, associateEntityName, username, + password, proxyUrl, proxyUsername, proxyPassword, filterOption, selectOption, expandOption, additionalQueryParameters, paginationType); } diff --git a/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java b/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java index ec30cd4..730a50a 100644 --- a/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java +++ b/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java @@ -24,20 +24,30 @@ import io.cdap.cdap.api.retry.RetryableException; import io.cdap.plugin.successfactors.common.exception.TransportException; import io.cdap.plugin.successfactors.common.util.ResourceConstants; +import io.cdap.plugin.successfactors.common.util.SuccessFactorsUtil; +import io.cdap.plugin.successfactors.connector.SuccessFactorsConnectorConfig; +import okhttp3.Authenticator; +import okhttp3.Credentials; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import okhttp3.Route; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; + import javax.ws.rs.core.MediaType; /** @@ -51,14 +61,11 @@ public class SuccessFactorsTransporter { private static final long WAIT_TIME = 5; private static final long MAX_NUMBER_OF_RETRY_ATTEMPTS = 5; - private final String username; - private final String password; + private SuccessFactorsConnectorConfig config; private Response response; - public SuccessFactorsTransporter(String username, String password) { - this.username = username; - this.password = password; - + public SuccessFactorsTransporter(SuccessFactorsConnectorConfig pluginConfig) { + this.config = pluginConfig; } /** @@ -152,7 +159,8 @@ public Response retrySapTransportCall(URL endpoint, String mediaType) throws IOE * @throws TransportException any error while preparing the {@code OkHttpClient} */ private Response transport(URL endpoint, String mediaType) throws IOException, TransportException { - OkHttpClient enhancedOkHttpClient = getConfiguredClient().build(); + OkHttpClient enhancedOkHttpClient = + buildConfiguredClient(config.getProxyUrl(), config.getProxyUsername(), config.getProxyPassword()); Request req = buildRequest(endpoint, mediaType); return enhancedOkHttpClient.newCall(req).execute(); @@ -189,6 +197,39 @@ private Request buildRequest(URL endpoint, String mediaType) { .build(); } + /** + * Builds and configures an OkHttpClient with the specified proxy settings and authentication credentials. + * @param proxyUrl The URL of the proxy server (e.g., "http://proxy.example.com:8080"). + * Set to null or an empty string to bypass proxy configuration. + * @param proxyUsername The username for proxy authentication. Set to null or an empty string if not required. + * @param proxyPassword The password for proxy authentication. Set to null or an empty string if not required. + * @return An OkHttpClient configured with the specified proxy settings and authentication credentials. + */ + private OkHttpClient buildConfiguredClient(String proxyUrl, String proxyUsername, String proxyPassword) + throws MalformedURLException, TransportException { + OkHttpClient.Builder builder = getConfiguredClient(); + + if (SuccessFactorsUtil.isNotNullOrEmpty(proxyUrl)) { + URL url = new URL(proxyUrl); + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(url.getHost(), url.getPort())); + builder.proxy(proxy); + + if (SuccessFactorsUtil.isNotNullOrEmpty(proxyUsername) && SuccessFactorsUtil.isNotNullOrEmpty(proxyPassword)) { + builder.proxyAuthenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) { + String credential = Credentials.basic(proxyUsername, proxyPassword); + return response.request().newBuilder() + .header("Proxy-Authorization", credential) + .build(); + } + }); + } + } + + return builder.build(); + } + /** * Builds the {@code OkHttpClient.Builder} with following optimized configuration parameters as per the SAP Gateway * recommendations. @@ -218,9 +259,9 @@ private OkHttpClient.Builder getConfiguredClient() throws TransportException { */ private String getAuthenticationKey() { return "Basic " + Base64.getEncoder() - .encodeToString(username + .encodeToString(config.getUsername() .concat(":") - .concat(password) + .concat(config.getPassword()) .getBytes(StandardCharsets.UTF_8) ); } diff --git a/src/test/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorTest.java b/src/test/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorTest.java index acfbfb8..d058b68 100644 --- a/src/test/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorTest.java @@ -81,12 +81,11 @@ public void testConfiguration() throws TransportException, SuccessFactorsService .password("password"); pluginConfig = pluginConfigBuilder.build(); + successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); } @Test public void testValidateSuccessfulConnection() throws TransportException, SuccessFactorsServiceException { - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); new Expectations(SuccessFactorsUrlContainer.class, SuccessFactorsTransporter.class, SuccessFactorsSchemaGenerator.class) { { @@ -103,8 +102,6 @@ public void testValidateSuccessfulConnection() throws TransportException, Succes public void testValidateUnauthorisedConnection() throws TransportException, SuccessFactorsServiceException { MockFailureCollector collector = new MockFailureCollector(); ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); new Expectations(SuccessFactorsUrlContainer.class, SuccessFactorsTransporter.class, SuccessFactorsSchemaGenerator.class) { { @@ -120,8 +117,6 @@ public void testValidateUnauthorisedConnection() throws TransportException, Succ @Test public void testValidateNotFoundConnection() throws TransportException, SuccessFactorsServiceException { MockFailureCollector collector = new MockFailureCollector(); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); new Expectations(SuccessFactorsUrlContainer.class, SuccessFactorsTransporter.class, SuccessFactorsSchemaGenerator.class) { { @@ -154,8 +149,6 @@ private SuccessFactorsResponseContainer getNotFoundResponseContainer() { public void testGenerateSpec() throws TransportException, IOException { ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); MockFailureCollector collector = new MockFailureCollector(); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); new Expectations(SuccessFactorsTransporter.class) { { successFactorsTransporter.callSuccessFactorsEntity(null, anyString, anyString); @@ -190,8 +183,6 @@ public void testGenerateSpecWithSchema() throws TransportException, IOException, ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); MockFailureCollector collector = new MockFailureCollector(); successFactorsConnector = new SuccessFactorsConnector(pluginConfig.getConnection()); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); new Expectations(SuccessFactorsConnector.class, SuccessFactorsTransporter.class) { { @@ -232,8 +223,6 @@ public void testBrowse() throws IOException, TransportException { ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); List entities = new ArrayList<>(); entities.add("Achievement"); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); successFactorsConnector = new SuccessFactorsConnector(pluginConfig.getConnection()); new Expectations(SuccessFactorsTransporter.class, SuccessFactorsTransporter.class, SuccessFactorsConnector.class) { @@ -267,8 +256,6 @@ public void testBrowse() throws IOException, TransportException { @Test(expected = IOException.class) public void testSampleWithoutSampleData() throws IOException, TransportException { ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); new Expectations(SuccessFactorsTransporter.class, SuccessFactorsTransporter.class, SuccessFactorsConnector.class) { { successFactorsTransporter.callSuccessFactorsEntity(null, anyString, anyString); @@ -291,8 +278,6 @@ public void testSampleWithoutSampleData() throws IOException, TransportException @Test public void testSampleWithSampleData() throws IOException, TransportException, EntityProviderException, SuccessFactorsServiceException, EdmException { - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); String entityName = "entity"; List records = new ArrayList<>(); StructuredRecord structuredRecord = Mockito.mock(StructuredRecord.class); diff --git a/src/test/java/io/cdap/plugin/successfactors/source/SuccessFactorsSourceTest.java b/src/test/java/io/cdap/plugin/successfactors/source/SuccessFactorsSourceTest.java index c496581..d19f1ab 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/SuccessFactorsSourceTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/SuccessFactorsSourceTest.java @@ -160,8 +160,7 @@ public void testConfigurePipelineWSchemaNotNull() throws SuccessFactorsServiceEx .password("password"); pluginConfig = pluginConfigBuilder.build(); - successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); + successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); successFactorsUrlContainer = new SuccessFactorsUrlContainer(pluginConfig); successFactorsSchemaGenerator = new SuccessFactorsSchemaGenerator(new SuccessFactorsEntityProvider(edm)); @@ -267,7 +266,6 @@ private List getSplits() { public void testPrepareRun() throws Exception { successFactorsService = new SuccessFactorsService(pluginConfig, null); successFactorsPartitionBuilder = new SuccessFactorsPartitionBuilder(); - successFactorsTransporter = new SuccessFactorsTransporter("username", "password"); pluginConfigBuilder = SuccessFactorsPluginConfig.builder() .referenceName("unit-test-ref-name") .baseURL("http://localhost") @@ -279,7 +277,7 @@ public void testPrepareRun() throws Exception { .filterOption("$topeq2"); pluginConfig = pluginConfigBuilder.build(); - + successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); new Expectations(SuccessFactorsService.class, SuccessFactorsTransporter.class) { { context.getOutputSchema(); diff --git a/src/test/java/io/cdap/plugin/successfactors/source/input/SuccessFactorsInputFormatTest.java b/src/test/java/io/cdap/plugin/successfactors/source/input/SuccessFactorsInputFormatTest.java index 5124e28..fd63057 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/input/SuccessFactorsInputFormatTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/input/SuccessFactorsInputFormatTest.java @@ -51,7 +51,8 @@ public void initializeTests() { "entityName", null, "username", - "password", + "password", null, null, + null, "filterOption", "selectOption", "expandOption", diff --git a/src/test/java/io/cdap/plugin/successfactors/source/metadata/SuccessFactorsSchemaGeneratorTest.java b/src/test/java/io/cdap/plugin/successfactors/source/metadata/SuccessFactorsSchemaGeneratorTest.java index b95ee5e..c237099 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/metadata/SuccessFactorsSchemaGeneratorTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/metadata/SuccessFactorsSchemaGeneratorTest.java @@ -39,12 +39,17 @@ public class SuccessFactorsSchemaGeneratorTest { public final ExpectedException exception = ExpectedException.none(); private SuccessFactorsEntityProvider serviceHelper; private SuccessFactorsSchemaGenerator generator; + private SuccessFactorsPluginConfig pluginConfig; @Before public void setup() throws EntityProviderException { edm = EntityProvider.readMetadata(TestSuccessFactorsUtil.readResource("successfactors-metadata.xml"), false); serviceHelper = new SuccessFactorsEntityProvider(edm); generator = new SuccessFactorsSchemaGenerator(serviceHelper); + pluginConfig = new SuccessFactorsPluginConfig("referenceName", "baseUR", + "entityName", "associateEntityName", "username", "password", + null, null, null, "filterOption", "selectOption", + "expandOption", "additionalQueryParameters", "paginationType"); } @Test @@ -78,10 +83,6 @@ public void testSelectWithExpandNames() throws SuccessFactorsServiceException { @Test public void testBuildExpandOutputSchema() throws SuccessFactorsServiceException { - SuccessFactorsPluginConfig pluginConfig = new SuccessFactorsPluginConfig("referenceName", - "baseUR", "entityName", "associateEntityName", "username", - "password", "filterOption", "selectOption", "expandOption", - "additionalQueryParameters", "paginationType"); Schema outputSchema = generator.buildExpandOutputSchema("Benefit", "eligibleBenefits", "associatedEntity", pluginConfig); int lastIndex = outputSchema.getFields().size() - 1; @@ -152,10 +153,6 @@ public void testInvalidEntityName() throws SuccessFactorsServiceException { @Test public void testInvalidExpandName() throws SuccessFactorsServiceException { - SuccessFactorsPluginConfig pluginConfig = new SuccessFactorsPluginConfig("referenceName", - "baseUR", "entityName", "associateEntityName", "username", - "password", "filterOption", "selectOption", "expandOption", - "additionalQueryParameters", "paginationType"); exception.expectMessage("'assEntity' not found in the 'Benefit' entity."); generator.buildExpandOutputSchema("Benefit", "INVALID-NAVIGATION-NAME", "assEntity", pluginConfig); diff --git a/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalForAssociatedEntityTest.java b/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalForAssociatedEntityTest.java index 928c7a1..31cf59c 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalForAssociatedEntityTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalForAssociatedEntityTest.java @@ -88,8 +88,7 @@ public void testRecordReaderForAssociatedEntity() throws Exception { long availableRowCount = 1; List partitionList = new SuccessFactorsPartitionBuilder().buildSplits(availableRowCount); - transporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), pluginConfig. - getConnection().getPassword()); + transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); successFactorsService = new SuccessFactorsService(pluginConfig, transporter); prepareStubForMetadata(); edmData = successFactorsService.getSuccessFactorsServiceEdm(encodedMetadataString); diff --git a/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalTest.java b/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalTest.java index 847f94a..1de10ca 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/transport/RuntimeFunctionalTest.java @@ -104,8 +104,7 @@ public void runPipelineWithDefaultValues() throws Exception { long availableRowCount = 3; List partitionList = new SuccessFactorsPartitionBuilder().buildSplits(availableRowCount); - transporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), pluginConfig. - getConnection().getPassword()); + transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); successFactorsService = new SuccessFactorsService(pluginConfig, transporter); prepareStubForMetadata(pluginConfig); edmData = successFactorsService.getSuccessFactorsServiceEdm(encodedMetadataString); @@ -141,8 +140,7 @@ public void verifyFailToDecodeMetadataString() throws SuccessFactorsServiceExcep exceptionRule.expect(SuccessFactorsServiceException.class); exceptionRule .expectMessage(ResourceConstants.ERR_METADATA_DECODE.getMsgForKeyWithCode(pluginConfig.getEntityName())); - transporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), pluginConfig. - getConnection().getPassword()); + transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); successFactorsService = new SuccessFactorsService(pluginConfig, transporter); successFactorsService.getSuccessFactorsServiceEdm("encodedMetadataString"); } @@ -154,8 +152,7 @@ public void verifyDataCorrectness() prepareStubForMetadata(pluginConfig); long availableRowCount = 3; List partitionList = new SuccessFactorsPartitionBuilder().buildSplits(availableRowCount); - transporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); + transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); successFactorsService = new SuccessFactorsService(pluginConfig, transporter); edmData = successFactorsService.getSuccessFactorsServiceEdm(encodedMetadataString); for (SuccessFactorsInputSplit inputSplit : partitionList) { diff --git a/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporterTest.java b/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporterTest.java index 8cf5dcc..90c05df 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporterTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporterTest.java @@ -69,8 +69,8 @@ public class SuccessFactorsTransporterTest { public static void classSetup() { new MockUp() { @Mock - public OkHttpClient.Builder getConfiguredClient() { - return allowAllSSL(); + public OkHttpClient buildConfiguredClient(String proxyUrl, String proxyUsername, String proxyPassword) { + return allowAllSSL().build(); } }; } @@ -125,8 +125,7 @@ public void setUp() { .expandOption("Products/Supplier"); pluginConfig = pluginConfigBuilder.build(); successFactorsURL = new SuccessFactorsUrlContainer(pluginConfig); - transporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(), - pluginConfig.getConnection().getPassword()); + transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); } @Test @@ -141,16 +140,52 @@ public void testCallSuccessFactors() throws TransportException { SuccessFactorsResponseContainer response = transporter .callSuccessFactors(successFactorsURL.getTesterURL(), MediaType.APPLICATION_JSON, SuccessFactorsService.TEST); - Assert.assertEquals("SuccessFactors Service data version is same.", + Assert.assertEquals("SuccessFactors Service data version is not same.", "2.0", response.getDataServiceVersion()); - Assert.assertEquals("HTTP status code is same.", + Assert.assertEquals("HTTP status code is not same.", HttpURLConnection.HTTP_OK, response.getHttpStatusCode()); - Assert.assertEquals("HTTP response body is same.", + Assert.assertEquals("HTTP response body is not same.", expectedBody, TestSuccessFactorsUtil.convertInputStreamToString(response.getResponseStream())); - Assert.assertEquals("HTTP status is same", "OK", response.getHttpStatusMsg()); + Assert.assertEquals("HTTP status is not same", "OK", response.getHttpStatusMsg()); + } + + @Test + public void testCallSuccessFactorsWithProxy() throws TransportException { + pluginConfigBuilder = SuccessFactorsPluginConfig.builder() + .baseURL("https://localhost:" + wireMockRule.httpsPort()) + .entityName("Entity") + .username("test") + .password("secret") + .proxyUrl("https://proxy") + .proxyUsername("user") + .proxyPassword("password") + .expandOption("Products/Supplier"); + pluginConfig = pluginConfigBuilder.build(); + successFactorsURL = new SuccessFactorsUrlContainer(pluginConfig); + transporter = new SuccessFactorsTransporter(pluginConfig.getConnection()); + String expectedBody = "{\"d\": [{\"ID\": 0,\"Name\": \"Bread\"}}]}"; + WireMock.stubFor(WireMock.get("/Entity?%24expand=Products%2FSupplier&%24top=1") + .withBasicAuth(pluginConfig.getConnection().getUsername(), + pluginConfig.getConnection().getPassword()) + .willReturn(WireMock.ok() + .withHeader(SuccessFactorsTransporter.SERVICE_VERSION, "2.0") + .withBody(expectedBody))); + SuccessFactorsResponseContainer response = transporter + .callSuccessFactors(successFactorsURL.getTesterURL(), MediaType.APPLICATION_JSON, SuccessFactorsService.TEST); + + Assert.assertEquals("SuccessFactors Service data version is not same.", + "2.0", + response.getDataServiceVersion()); + Assert.assertEquals("HTTP status code is not same.", + HttpURLConnection.HTTP_OK, + response.getHttpStatusCode()); + Assert.assertEquals("HTTP response body is not same.", + expectedBody, + TestSuccessFactorsUtil.convertInputStreamToString(response.getResponseStream())); + Assert.assertEquals("HTTP status is not same", "OK", response.getHttpStatusMsg()); } @Test @@ -161,7 +196,7 @@ public void testUnAuthorized() throws TransportException { .callSuccessFactors(successFactorsURL.getMetadataURL(), MediaType.APPLICATION_XML, SuccessFactorsService.METADATA); WireMock.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/Entity/$metadata"))); - Assert.assertEquals("HTTP status code is matching.", + Assert.assertEquals("HTTP status code is not matching.", HttpURLConnection.HTTP_UNAUTHORIZED, response.getHttpStatusCode()); } diff --git a/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsUrlContainerTest.java b/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsUrlContainerTest.java index 57f58e9..540c3c9 100644 --- a/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsUrlContainerTest.java +++ b/src/test/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsUrlContainerTest.java @@ -32,7 +32,8 @@ public void initializeTests() { "entityName", "associatedEntity", "username", - "password", + "password", null, null, + null, "filterOption", "selectOption", "expandOption", @@ -71,7 +72,8 @@ public void testGetURLWithAdditionalQueryParameters() { "EmpJob", "associatedEntity", "username", - "password", + "password", null, null, + null, "", "", "", diff --git a/widgets/SuccessFactors-batchsource.json b/widgets/SuccessFactors-batchsource.json index cd4523b..8cb6189 100644 --- a/widgets/SuccessFactors-batchsource.json +++ b/widgets/SuccessFactors-batchsource.json @@ -93,6 +93,26 @@ } ] }, + { + "label": "Proxy Configuration", + "properties": [ + { + "widget-type": "textbox", + "label": "Proxy URL", + "name": "proxyUrl" + }, + { + "widget-type": "textbox", + "label": "Username", + "name": "proxyUsername" + }, + { + "widget-type": "password", + "label": "Password", + "name": "proxyPassword" + } + ] + }, { "label": "Advanced", "properties": [ @@ -176,6 +196,18 @@ { "type": "property", "name": "baseURL" + }, + { + "type": "property", + "name": "proxyUrl" + }, + { + "type": "property", + "name": "proxyUsername" + }, + { + "type": "property", + "name": "proxyPassword" } ] }, @@ -190,6 +222,23 @@ "name": "connection" } ] + }, + { + "name": "Proxy authentication", + "condition": { + "property": "proxyUrl", + "operator": "exists" + }, + "show": [ + { + "name": "proxyUsername", + "type": "property" + }, + { + "name": "proxyPassword", + "type": "property" + } + ] } ], "outputs": [ diff --git a/widgets/SuccessFactors-connector.json b/widgets/SuccessFactors-connector.json index 7ab0324..7ffbc4a 100644 --- a/widgets/SuccessFactors-connector.json +++ b/widgets/SuccessFactors-connector.json @@ -32,6 +32,45 @@ } } ] + }, + { + "label": "Proxy Configuration", + "properties": [ + { + "widget-type": "textbox", + "label": "Proxy URL", + "name": "proxyUrl" + }, + { + "widget-type": "textbox", + "label": "Username", + "name": "proxyUsername" + }, + { + "widget-type": "password", + "label": "Password", + "name": "proxyPassword" + } + ] + } + ], + "filters":[ + { + "name": "Proxy authentication", + "condition": { + "property": "proxyUrl", + "operator": "exists" + }, + "show": [ + { + "name": "proxyUsername", + "type": "property" + }, + { + "name": "proxyPassword", + "type": "property" + } + ] } ], "outputs": []