diff --git a/docs/SuccessFactors-batchsource.md b/docs/SuccessFactors-batchsource.md
index 05df6e6..8769bc3 100644
--- a/docs/SuccessFactors-batchsource.md
+++ b/docs/SuccessFactors-batchsource.md
@@ -19,8 +19,21 @@ annotating metadata, etc.
**Use Connection:** Whether to use a connection. If a connection is used, you do not need to provide the credentials.
**Connection:** Name of the connection to use. Entity Names information will be provided by the connection.
You also can use the macro function ${conn(connection-name)}.
+**Authentication Type:** Authentication type used to submit request. Supported types are Basic & OAuth 2.0. Default is Basic Authentication.
+* **Basic Authentication**
**SAP SuccessFactors Logon Username (M)**: SAP SuccessFactors Logon Username for user authentication.
**SAP SuccessFactors Logon Password (M)**: SAP SuccessFactors Logon password for user authentication.
+* **OAuth 2.0**
+**Client Id:** Client Id required to generate the token.
+**Company Id:** Company Id required to generate the token.
+**Assertion Token Type:** Assertion token can be entered or can be created using the required parameters.
+* **Enter Token**
+**Assertion Token:** Assertion token used to generate the access token.
+* **Create Token**
+**Token URL:** Token URL to generate the assertion token.
+**Private Key:** Private key required to generate the token.
+**User Id:** User Id required to generate the token.
+
**SAP SuccessFactors Base URL (M)**: SAP SuccessFactors Base URL.
diff --git a/docs/SuccessFactors-connector.md b/docs/SuccessFactors-connector.md
index 3430674..ab14dd5 100644
--- a/docs/SuccessFactors-connector.md
+++ b/docs/SuccessFactors-connector.md
@@ -10,9 +10,20 @@ Properties
**Description:** Description of the connection.
-**SAP SuccessFactors Logon Username (M)**: SAP SuccessFactors Logon Username for user authentication.
-
-**SAP SuccessFactors Logon Password (M)**: SAP SuccessFactors Logon password for user authentication.
+**Authentication Type:** Authentication type used to submit request. Supported types are Basic & OAuth 2.0. Default is Basic Authentication.
+* **Basic Authentication**
+ **SAP SuccessFactors Logon Username (M)**: SAP SuccessFactors Logon Username for user authentication.
+ **SAP SuccessFactors Logon Password (M)**: SAP SuccessFactors Logon password for user authentication.
+* **OAuth 2.0**
+ **Client Id:** Client Id required to generate the token.
+ **Company Id:** Company Id required to generate the token.
+ **Assertion Token Type:** Assertion token can be entered or can be created using the required parameters.
+* **Enter Token**
+ **Assertion Token:** Assertion token used to generate the access token.
+* **Create Token**
+ **Token URL:** Token URL to generate the assertion token.
+ **Private Key:** Private key required to generate the token.
+ **User Id:** User Id required to generate the token.
**SAP SuccessFactors Base URL (M)**: SAP SuccessFactors Base URL.
diff --git a/pom.xml b/pom.xml
index 339fd94..b46b690 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,7 @@
4.12
2.0.0
4.9.1
+ 4.5.13
2.0.0
2.27.2
2.7.0
@@ -334,7 +335,57 @@
okhttp
${okhttp3.version}
-
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclient.version}
+
+
+ org.opensaml
+ xmltooling
+ 1.4.4
+
+
+ org.opensaml
+ openws
+ 1.5.4
+
+
+ org.opensaml
+ opensaml
+ 2.6.4
+
+
+ commons-codec
+ commons-codec
+ 1.10
+
+
+ xml-security
+ xmlsec
+ 1.3.0
+
+
+ commons-collections
+ commons-collections
+ 3.2.2
+
+
+ commons-lang
+ commons-lang
+ 2.1
+
+
+ commons-logging
+ commons-logging
+ 1.1
+
+
+ org.apache.velocity
+ velocity
+ 1.5
+ pom
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsAccessToken.java b/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsAccessToken.java
new file mode 100644
index 0000000..64d83c7
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/successfactors/common/util/SuccessFactorsAccessToken.java
@@ -0,0 +1,391 @@
+/*
+ * 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.
+ */
+
+package io.cdap.plugin.successfactors.common.util;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import io.cdap.plugin.successfactors.connector.SuccessFactorsConnectorConfig;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.joda.time.DateTime;
+import org.opensaml.Configuration;
+import org.opensaml.DefaultBootstrap;
+import org.opensaml.common.SAMLVersion;
+import org.opensaml.saml1.core.NameIdentifier;
+import org.opensaml.saml2.core.Assertion;
+import org.opensaml.saml2.core.Attribute;
+import org.opensaml.saml2.core.AttributeStatement;
+import org.opensaml.saml2.core.AttributeValue;
+import org.opensaml.saml2.core.Audience;
+import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnStatement;
+import org.opensaml.saml2.core.Conditions;
+import org.opensaml.saml2.core.Issuer;
+import org.opensaml.saml2.core.NameID;
+import org.opensaml.saml2.core.Subject;
+import org.opensaml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml2.core.SubjectConfirmationData;
+import org.opensaml.saml2.core.impl.AssertionMarshaller;
+import org.opensaml.xml.ConfigurationException;
+import org.opensaml.xml.Namespace;
+import org.opensaml.xml.XMLObjectBuilder;
+import org.opensaml.xml.io.MarshallingException;
+import org.opensaml.xml.schema.XSString;
+import org.opensaml.xml.schema.impl.XSStringBuilder;
+import org.opensaml.xml.security.SecurityConfiguration;
+import org.opensaml.xml.security.SecurityException;
+import org.opensaml.xml.security.SecurityHelper;
+import org.opensaml.xml.security.x509.BasicX509Credential;
+import org.opensaml.xml.signature.Signature;
+import org.opensaml.xml.signature.SignatureConstants;
+import org.opensaml.xml.signature.SignatureException;
+import org.opensaml.xml.signature.Signer;
+import org.opensaml.xml.util.XMLHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.List;
+import java.util.UUID;
+
+import javax.xml.namespace.QName;
+
+
+/**
+ * AccessToken class
+ */
+public class SuccessFactorsAccessToken {
+ private static final Logger LOG = LoggerFactory.getLogger(SuccessFactorsAccessToken.class);
+ private final SuccessFactorsConnectorConfig config;
+ private final Gson gson = new Gson();
+
+
+ public SuccessFactorsAccessToken(SuccessFactorsConnectorConfig config) {
+ this.config = config;
+ }
+
+ /**
+ * Generates a signed SAML assertion for authentication purposes.
+ *
+ * @param clientId The client ID associated with the application.
+ * @param username The username of the user for whom the assertion is generated.
+ * @param tokenUrl The URL for obtaining the authentication token.
+ * @param privateKeyString The private key used for signing the assertion.
+ * @param expireInMinutes The validity period of the assertion in minutes.
+ * @param userUserNameAsUserId A boolean indicating whether to use the username as the User ID in the assertion.
+ * @return The signed SAML assertion as a string.
+ * @throws Exception If an error occurs during the generation or signing of the SAML assertion.
+ */
+ public static String generateSignedSAMLAssertion(String clientId, String username, String tokenUrl,
+ String privateKeyString, int expireInMinutes,
+ boolean userUserNameAsUserId) {
+
+ Assertion unsignedAssertion = buildDefaultAssertion(clientId, username, tokenUrl, expireInMinutes,
+ userUserNameAsUserId);
+ PrivateKey privateKey = generatePrivateKey(privateKeyString);
+ Assertion assertion = sign(unsignedAssertion, privateKey);
+ String signedAssertion = getSAMLAssertionString(assertion);
+
+ return signedAssertion;
+ }
+
+ /**
+ * Builds a default SAML assertion with specified parameters for authentication purposes.
+ *
+ * @param clientId The client ID associated with the application.
+ * @param userId The user ID for whom the assertion is generated.
+ * @param tokenUrl The URL for obtaining the authentication token.
+ * @param expireInMinutes The validity period of the assertion in minutes.
+ * @param userUserNameAsUserId A boolean indicating whether to use the username as the User ID in the assertion.
+ * @return The constructed SAML assertion.
+ * @throws RuntimeException if an error occurs during the construction of the SAML assertion.
+ */
+ private static Assertion buildDefaultAssertion(String clientId, String userId, String tokenUrl, int expireInMinutes,
+ boolean userUserNameAsUserId) {
+ try {
+ DateTime currentTime = new DateTime();
+ DefaultBootstrap.bootstrap();
+
+ // Create the assertion and set Id, namespace etc.
+ Assertion assertion = create(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME);
+ assertion.setIssueInstant(currentTime);
+ assertion.setID(UUID.randomUUID().toString());
+ assertion.setVersion(SAMLVersion.VERSION_20);
+ Namespace xsNS = new Namespace("http://www.w3.org/2001/XMLSchema", "xs");
+ assertion.addNamespace(xsNS);
+ Namespace xsiNS = new Namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi");
+ assertion.addNamespace(xsiNS);
+
+ Issuer issuer = create(Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
+ issuer.setValue("www.successfactors.com");
+ assertion.setIssuer(issuer);
+
+ // Create the subject and add it to assertion
+ Subject subject = create(Subject.class, Subject.DEFAULT_ELEMENT_NAME);
+ NameID nameID = create(NameID.class, NameID.DEFAULT_ELEMENT_NAME);
+ nameID.setValue(userId);
+ nameID.setFormat(NameIdentifier.UNSPECIFIED);
+ subject.setNameID(nameID);
+ SubjectConfirmation subjectConfirmation = create(SubjectConfirmation.class,
+ SubjectConfirmation.DEFAULT_ELEMENT_NAME);
+ subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer");
+ SubjectConfirmationData sconfData = create(SubjectConfirmationData.class,
+ SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
+ sconfData.setNotOnOrAfter(currentTime.plusMinutes(expireInMinutes));
+ sconfData.setRecipient(tokenUrl);
+ subjectConfirmation.setSubjectConfirmationData(sconfData);
+ subject.getSubjectConfirmations().add(subjectConfirmation);
+ assertion.setSubject(subject);
+
+ // Create the Conditions
+ Conditions conditions = buildConditions(currentTime, expireInMinutes);
+
+ AudienceRestriction audienceRestriction = create(AudienceRestriction.class,
+ AudienceRestriction.DEFAULT_ELEMENT_NAME);
+ Audience audience = create(Audience.class, Audience.DEFAULT_ELEMENT_NAME);
+ audience.setAudienceURI("www.successfactors.com");
+ List audienceList = audienceRestriction.getAudiences();
+ audienceList.add(audience);
+ List audienceRestrictions = conditions.getAudienceRestrictions();
+ audienceRestrictions.add(audienceRestriction);
+ assertion.setConditions(conditions);
+
+ // Create the AuthnStatement and add it to assertion
+ AuthnStatement authnStatement = create(AuthnStatement.class, AuthnStatement.DEFAULT_ELEMENT_NAME);
+ authnStatement.setAuthnInstant(currentTime);
+ authnStatement.setSessionIndex(UUID.randomUUID().toString());
+ AuthnContext authContext = create(AuthnContext.class, AuthnContext.DEFAULT_ELEMENT_NAME);
+ AuthnContextClassRef authnContextClassRef = create(AuthnContextClassRef.class,
+ AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
+ authnContextClassRef.setAuthnContextClassRef(AuthnContext.PPT_AUTHN_CTX);
+ authContext.setAuthnContextClassRef(authnContextClassRef);
+ authnStatement.setAuthnContext(authContext);
+ assertion.getAuthnStatements().add(authnStatement);
+
+ // Create the attribute statement
+ AttributeStatement attributeStatement = create(AttributeStatement.class,
+ AttributeStatement.DEFAULT_ELEMENT_NAME);
+ Attribute apiKeyAttribute = createAttribute("api_key", clientId);
+ attributeStatement.getAttributes().add(apiKeyAttribute);
+ assertion.getAttributeStatements().add(attributeStatement);
+
+ // Set user_username as true while using username as userId
+ if (userUserNameAsUserId) {
+ AttributeStatement useUserNameAsUserIdStatement = create(AttributeStatement.class,
+ AttributeStatement.DEFAULT_ELEMENT_NAME);
+ Attribute useUserNameKeyAttribute = createAttribute("use_username", "true");
+ useUserNameAsUserIdStatement.getAttributes().add(useUserNameKeyAttribute);
+ assertion.getAttributeStatements().add(useUserNameAsUserIdStatement);
+ }
+
+ return assertion;
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * helper method to create open saml objects.
+ * @param cls class type
+ * @param qname qualified name
+ * @param class type
+ * @return the saml object
+ */
+ @SuppressWarnings("unchecked")
+ public static T create(Class cls, QName qname) {
+ return (T) ((XMLObjectBuilder) Configuration.getBuilderFactory().getBuilder(qname)).buildObject(qname);
+ }
+
+ private static Attribute createAttribute(String name, String value) {
+ Attribute result = create(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME);
+ result.setName(name);
+ XSStringBuilder stringBuilder = (XSStringBuilder) Configuration.getBuilderFactory()
+ .getBuilder(XSString.TYPE_NAME);
+ XSString stringValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
+ stringValue.setValue(value);
+ result.getAttributeValues().add(stringValue);
+ return result;
+ }
+
+ private static Conditions buildConditions(DateTime currentTime, int expireInMinutes) {
+ Conditions conditions = create(Conditions.class, Conditions.DEFAULT_ELEMENT_NAME);
+ conditions.setNotBefore(currentTime.minusMinutes(10));
+ conditions.setNotOnOrAfter(currentTime.plusMinutes(expireInMinutes));
+ return conditions;
+ }
+
+ private static String getSAMLAssertionString(Assertion assertion) {
+ AssertionMarshaller marshaller = new AssertionMarshaller();
+ Element element = null;
+ try {
+ element = marshaller.marshall(assertion);
+ } catch (MarshallingException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ String unencodedSAMLAssertion = XMLHelper.nodeToString(element);
+
+ Base64 base64 = new Base64();
+ try {
+ return base64.encodeToString(unencodedSAMLAssertion.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Signs a SAML assertion using the provided private key.
+ *
+ * @param assertion The unsigned SAML assertion to be signed.
+ * @param privateKey The private key used for signing the assertion.
+ * @return The signed SAML assertion.
+ * @throws Exception If an error occurs during the signing process.
+ * - If the SAML assertion is already signed.
+ * - If an invalid X.509 private key is provided.
+ * - If there is a failure in signing the SAML2 assertion.
+ */
+ private static Assertion sign(Assertion assertion, PrivateKey privateKey) {
+ BasicX509Credential credential = new BasicX509Credential();
+ credential.setPrivateKey(privateKey);
+
+ if (assertion.getSignature() != null) {
+ throw new RuntimeException("SAML assertion is already signed");
+ }
+
+ if (privateKey == null) {
+ throw new RuntimeException("Invalid X.509 private key");
+ }
+
+ try {
+ Signature signature = (Signature) Configuration.getBuilderFactory()
+ .getBuilder(Signature.DEFAULT_ELEMENT_NAME).buildObject(Signature.DEFAULT_ELEMENT_NAME);
+ signature.setSigningCredential(credential);
+ SecurityConfiguration secConfig = Configuration.getGlobalSecurityConfiguration();
+ String keyInfoGeneratorProfile = null; // "XMLSignature";
+ SecurityHelper.prepareSignatureParams(signature, credential, secConfig, keyInfoGeneratorProfile);
+
+ // Support sha256 signing algorithm for external oauth saml assertion
+ signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
+
+ assertion.setSignature(signature);
+ Configuration.getMarshallerFactory().getMarshaller(assertion).marshall(assertion);
+ Signer.signObject(signature);
+ } catch (MarshallingException | SecurityException | SignatureException e) {
+ throw new RuntimeException("Failure in signing the SAML2 assertion", e);
+ }
+ return assertion;
+ }
+
+ private static PrivateKey generatePrivateKey(String privateKeyString) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+
+ // Decode the base64-encoded private key string
+ String pk2 = new String(Base64.decodeBase64(privateKeyString), "UTF-8");
+
+ // Extract the actual private key string if it is in a format like "privateKey###additionalInfo"
+ String[] strs = pk2.split("###");
+ if (strs.length == 2) {
+ privateKeyString = strs[0];
+ }
+
+ // Generate the private key from the decoded key string
+ PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));
+ return keyFactory.generatePrivate(privateKeySpec);
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeySpecException e) {
+ // Throw a runtime exception if an error occurs during the private key generation process
+ throw new RuntimeException("Error generating private key", e);
+ }
+ }
+
+ public String getAssertionToken() {
+
+ /**
+ * Below code is to produce signed assertion via code directly using provided
+ * input
+ */
+ String tokenUrl = config.getTokenURL(), clientId = config.getClientId(),
+ privateKey = config.getPrivateKey(), userId = config.getUserId();
+ boolean useUserNameAsUserId = false;
+ int expireInMinutes = 10;
+
+ if (tokenUrl != null && clientId != null && privateKey != null && userId != null) {
+ LOG.info("All properties are set, generating the SAML Assertion...");
+
+ String signedSAMLAssertion = generateSignedSAMLAssertion(clientId, userId, tokenUrl, privateKey,
+ expireInMinutes, useUserNameAsUserId);
+ LOG.info("Signed SAML Assertion is generated");
+ return signedSAMLAssertion;
+ }
+ return null;
+ }
+
+ public String getAccessToken(String assertionToken) throws IOException {
+ HttpClient client = HttpClientBuilder.create().build();
+
+ // Build POST request
+ HttpPost request = new HttpPost(URI.create("https://apisalesdemo2.successfactors.eu/oauth/token"));
+
+ // Set headers
+ request.setHeader("Authorization", "none");
+ request.setHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ // Build request body
+ StringBuilder body = new StringBuilder();
+ body.append("client_id=").append(config.getClientId());
+ body.append("&company_id=").append(config.getCompanyId());
+ body.append("&grant_type=").append("urn:ietf:params:oauth:grant-type:saml2-bearer");
+ body.append("&assertion=").append(assertionToken);
+
+ // Set request entity
+ request.setEntity(new StringEntity(body.toString()));
+
+ // Execute request and get response
+ HttpResponse response = client.execute(request);
+ String accessToken = null;
+ JsonObject jsonObject = null;
+
+ // Read response body
+ if (response.getEntity() != null) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) {
+ jsonObject = gson.fromJson(reader, JsonObject.class);
+
+ // Check if "access_token" is present in the JSON response
+ if (jsonObject != null && jsonObject.has("access_token")) {
+ accessToken = jsonObject.get("access_token").getAsString();
+ }
+ }
+ }
+ return accessToken;
+ }
+}
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..fb76d20 100644
--- a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java
+++ b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java
@@ -20,7 +20,6 @@
import io.cdap.cdap.api.annotation.Name;
import io.cdap.cdap.api.plugin.PluginConfig;
import io.cdap.cdap.etl.api.FailureCollector;
-import io.cdap.plugin.successfactors.common.exception.SuccessFactorsServiceException;
import io.cdap.plugin.successfactors.common.exception.TransportException;
import io.cdap.plugin.successfactors.common.util.ResourceConstants;
import io.cdap.plugin.successfactors.common.util.SuccessFactorsUtil;
@@ -33,16 +32,29 @@
import java.net.HttpURLConnection;
import java.net.URL;
+
+import javax.annotation.Nullable;
import javax.ws.rs.core.MediaType;
/**
* SuccessFactorsConnectorConfig Class
*/
public class SuccessFactorsConnectorConfig extends PluginConfig {
-
+ public static final String PROPERTY_AUTH_TYPE = "authType";
+ public static final String ASSERTION_TOKEN_TYPE = "assertionTokenType";
+ public static final String BASIC_AUTH = "basicAuth";
+ public static final String OAUTH2 = "oAuth2";
+ public static final String ENTER_TOKEN = "enterToken";
+ public static final String CREATE_TOKEN = "createToken";
public static final String BASE_URL = "baseURL";
public static final String UNAME = "username";
public static final String PASSWORD = "password";
+ public static final String TOKEN_URL = "tokenURL";
+ public static final String CLIENT_ID = "clientId";
+ public static final String PRIVATE_KEY = "privateKey";
+ public static final String USER_ID = "userId";
+ public static final String ASSERTION_TOKEN = "assertionToken";
+ public static final String COMPANY_ID = "companyId";
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";
@@ -50,25 +62,84 @@ public class SuccessFactorsConnectorConfig extends PluginConfig {
private static final String SAP_SUCCESSFACTORS_BASE_URL = "SAP SuccessFactors Base URL";
private static final Logger LOG = LoggerFactory.getLogger(SuccessFactorsConnectorConfig.class);
+ @Name(PROPERTY_AUTH_TYPE)
+ @Description("Type of authentication used to submit request. OAuth 2.0, Basic Authentication types are available.")
+ protected String authType;
+
+ @Nullable
+ @Name(ASSERTION_TOKEN_TYPE)
+ @Description("Assertion token can be entered or can be created using the required parameters.")
+ protected String assertionTokenType;
+
+ @Nullable
@Name(UNAME)
@Macro
@Description("SAP SuccessFactors Username for user authentication.")
private final String username;
+ @Nullable
@Name(PASSWORD)
@Macro
@Description("SAP SuccessFactors password for user authentication.")
private final String password;
+ @Nullable
+ @Name(TOKEN_URL)
+ @Macro
+ @Description("Token URL to generate the assertion token.")
+ private final String tokenURL;
+
+ @Nullable
+ @Name(CLIENT_ID)
+ @Macro
+ @Description("Client Id to generate the token.")
+ private final String clientId;
+
+ @Nullable
+ @Name(PRIVATE_KEY)
+ @Macro
+ @Description("Private key to generate the token.")
+ private final String privateKey;
+
+ @Nullable
+ @Name(USER_ID)
+ @Macro
+ @Description("User Id to generate the token.")
+ private final String userId;
+
+ @Nullable
+ @Name(ASSERTION_TOKEN)
+ @Macro
+ @Description("Assertion token used to generate the access token.")
+ private final String assertionToken;
+
+ @Nullable
+ @Name(COMPANY_ID)
+ @Macro
+ @Description("Company Id to generate the token.")
+ private final String companyId;
+
@Macro
@Name(BASE_URL)
@Description("SuccessFactors Base URL.")
private final String baseURL;
- public SuccessFactorsConnectorConfig(String username, String password, String baseURL) {
+ public SuccessFactorsConnectorConfig(@Nullable String username, @Nullable String password, @Nullable String tokenURL,
+ @Nullable String clientId, @Nullable String privateKey, @Nullable String userId,
+ @Nullable String companyId, String baseURL, String authType,
+ String assertionTokenType,
+ @Nullable String samlUsername, @Nullable String assertionToken) {
this.username = username;
this.password = password;
+ this.tokenURL = tokenURL;
+ this.clientId = clientId;
+ this.privateKey = privateKey;
+ this.userId = userId;
+ this.companyId = companyId;
this.baseURL = baseURL;
+ this.authType = authType;
+ this.assertionTokenType = assertionTokenType;
+ this.assertionToken = assertionToken;
}
public String getUsername() {
@@ -79,20 +150,100 @@ public String getPassword() {
return password;
}
+ public String getAuthType() {
+ return authType;
+ }
+
+ @Nullable
+ public String getTokenURL() {
+ return tokenURL;
+ }
+
+ @Nullable
+ public String getCompanyId() {
+ return companyId;
+ }
+
+ @Nullable
+ public String getAssertionTokenType() {
+ return assertionTokenType;
+ }
+
+ @Nullable
+ public String getClientId() {
+ return clientId;
+ }
+
+ @Nullable
+ public String getAssertionToken() {
+ return assertionToken;
+ }
+
+ @Nullable
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+ @Nullable
+ public String getUserId() {
+ return userId;
+ }
+
public String getBaseURL() {
return baseURL;
}
public void validateBasicCredentials(FailureCollector failureCollector) {
- if (SuccessFactorsUtil.isNullOrEmpty(getUsername()) && !containsMacro(UNAME)) {
- String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(SAP_SUCCESSFACTORS_USERNAME);
- failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(UNAME);
+ if (SuccessFactorsUtil.isNullOrEmpty(getAuthType())) {
+ return;
+ }
+
+ if (authType.equals(BASIC_AUTH)) {
+ if (SuccessFactorsUtil.isNullOrEmpty(getUsername()) && !containsMacro(UNAME)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(SAP_SUCCESSFACTORS_USERNAME);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(UNAME);
+ }
+ if (SuccessFactorsUtil.isNullOrEmpty(getPassword()) && !containsMacro(PASSWORD)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(SAP_SUCCESSFACTORS_PASSWORD);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(PASSWORD);
+ }
+
}
- if (SuccessFactorsUtil.isNullOrEmpty(getPassword()) && !containsMacro(PASSWORD)) {
- String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(SAP_SUCCESSFACTORS_PASSWORD);
- failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(PASSWORD);
+ if (authType.equals(OAUTH2)) {
+ if (SuccessFactorsUtil.isNullOrEmpty(getClientId()) && !containsMacro(CLIENT_ID)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(CLIENT_ID);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(CLIENT_ID);
+ }
+ if (SuccessFactorsUtil.isNullOrEmpty(getCompanyId()) && !containsMacro(COMPANY_ID)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(COMPANY_ID);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(COMPANY_ID);
+ }
+
+ if (assertionTokenType.equals(ENTER_TOKEN)) {
+ if (SuccessFactorsUtil.isNullOrEmpty(getAssertionToken()) && !containsMacro(ASSERTION_TOKEN)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(ASSERTION_TOKEN);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(ASSERTION_TOKEN);
+ }
+ }
+
+ if (assertionTokenType.equals(CREATE_TOKEN)) {
+ if (SuccessFactorsUtil.isNullOrEmpty(getTokenURL()) && !containsMacro(TOKEN_URL)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(TOKEN_URL);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(TOKEN_URL);
+ }
+ if (SuccessFactorsUtil.isNullOrEmpty(getPrivateKey()) && !containsMacro(PRIVATE_KEY)) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(PRIVATE_KEY);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(PRIVATE_KEY);
+ }
+ if ((SuccessFactorsUtil.isNullOrEmpty(getUserId()) && !containsMacro(USER_ID))) {
+ String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(USER_ID);
+ failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(USER_ID);
+ }
+ }
+
}
+
if (SuccessFactorsUtil.isNullOrEmpty(getBaseURL()) && !containsMacro(BASE_URL)) {
String errMsg = ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey(SAP_SUCCESSFACTORS_BASE_URL);
failureCollector.addFailure(errMsg, COMMON_ACTION).withConfigProperty(BASE_URL);
@@ -109,7 +260,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 {
@@ -118,6 +269,7 @@ public void validateConnection(FailureCollector collector) {
} catch (TransportException e) {
LOG.error("Unable to fetch the response", e);
collector.addFailure("Unable to call SuccessFatorsEntity", "Please check the values");
+ 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..3d16288 100644
--- a/src/main/java/io/cdap/plugin/successfactors/source/SuccessFactorsSource.java
+++ b/src/main/java/io/cdap/plugin/successfactors/source/SuccessFactorsSource.java
@@ -39,12 +39,14 @@
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;
import io.cdap.plugin.successfactors.source.input.SuccessFactorsPartitionBuilder;
import io.cdap.plugin.successfactors.source.service.SuccessFactorsService;
import io.cdap.plugin.successfactors.source.transport.SuccessFactorsTransporter;
+
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Job;
@@ -56,6 +58,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
+
import javax.annotation.Nullable;
/**
@@ -126,8 +129,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.
@@ -157,8 +159,21 @@ private void attachFieldWithError(SuccessFactorsServiceException ose, FailureCol
errMsg = ResourceConstants.ERR_ODATA_ENTITY_FAILURE.getMsgForKeyWithCode(errMsg);
switch (ose.getErrorCode()) {
case HttpURLConnection.HTTP_UNAUTHORIZED:
- failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsPluginConfig.UNAME);
- failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsPluginConfig.PASSWORD);
+ if (config.getConnection().getAuthType().equals(SuccessFactorsConnectorConfig.BASIC_AUTH)) {
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsPluginConfig.UNAME);
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsPluginConfig.PASSWORD);
+ } else {
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsConnectorConfig.COMPANY_ID);
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsConnectorConfig.CLIENT_ID);
+ if (config.getConnection().getAssertionTokenType().equals(SuccessFactorsConnectorConfig.ENTER_TOKEN)) {
+ failureCollector.addFailure(errMsg, null).
+ withConfigProperty(SuccessFactorsConnectorConfig.ASSERTION_TOKEN);
+ } else {
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsConnectorConfig.PRIVATE_KEY);
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsConnectorConfig.USER_ID);
+ failureCollector.addFailure(errMsg, null).withConfigProperty(SuccessFactorsConnectorConfig.TOKEN_URL);
+ }
+ }
break;
case HttpURLConnection.HTTP_FORBIDDEN:
case ExceptionParser.NO_VERSION_FOUND:
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..24268d4 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,22 @@ public SuccessFactorsPluginConfig(String referenceName,
String associateEntityName,
@Nullable String username,
@Nullable String password,
+ @Nullable String tokenURL,
+ @Nullable String clientId,
+ @Nullable String privateKey,
+ @Nullable String userId,
+ @Nullable String samlUsername,
+ @Nullable String assertionToken,
+ @Nullable String companyId,
+ String authType,
+ String assertionTokenType,
@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, tokenURL, clientId, privateKey, userId,
+ companyId, baseURL, authType, assertionTokenType, samlUsername, assertionToken);
this.referenceName = referenceName;
this.entityName = entityName;
this.associateEntityName = associateEntityName;
@@ -150,6 +160,7 @@ public SuccessFactorsPluginConfig(String referenceName,
this.paginationType = paginationType;
this.additionalQueryParameters = additionalQueryParameters;
}
+
@Nullable
public SuccessFactorsConnectorConfig getConnection() {
return connection;
@@ -300,6 +311,15 @@ public static class Builder {
private String expandOption;
private String paginationType;
private String additionalQueryParameters;
+ private String tokenURL;
+ private String clientId;
+ private String privateKey;
+ private String userId;
+ private String samlUsername;
+ private String assertionToken;
+ private String companyId;
+ private String authType;
+ private String assertionTokenType;
public Builder referenceName(String referenceName) {
this.referenceName = referenceName;
@@ -346,11 +366,46 @@ public Builder expandOption(@Nullable String expandOption) {
return this;
}
+ public Builder authType(@Nullable String authType) {
+ this.authType = authType;
+ return this;
+ }
+
+ public Builder setTokenURL(@Nullable String tokenURL) {
+ this.tokenURL = tokenURL;
+ return this;
+ }
+
+ public Builder setClientId(@Nullable String clientId) {
+ this.clientId = clientId;
+ return this;
+ }
+
+ public Builder setPrivateKey(@Nullable String privateKey) {
+ this.privateKey = privateKey;
+ return this;
+ }
+
+ public Builder setUserId(@Nullable String userId) {
+ this.userId = userId;
+ return this;
+ }
+
public Builder paginationType(@Nullable String paginationType) {
this.paginationType = paginationType;
return this;
}
+ public Builder assertionTokenType(@Nullable String assertionTokenType) {
+ this.assertionTokenType = assertionTokenType;
+ return this;
+ }
+
+ public Builder assertionToken(@Nullable String assertionToken) {
+ this.assertionToken = assertionToken;
+ return this;
+ }
+
public Builder additionalQueryParameters(@Nullable String additionalQueryParameters) {
this.additionalQueryParameters = additionalQueryParameters;
return this;
@@ -358,8 +413,9 @@ public Builder additionalQueryParameters(@Nullable String additionalQueryParamet
public SuccessFactorsPluginConfig build() {
return new SuccessFactorsPluginConfig(referenceName, baseURL, entityName, associateEntityName, username, password,
- filterOption, selectOption, expandOption, additionalQueryParameters,
- paginationType);
+ tokenURL, clientId, privateKey, userId, samlUsername, assertionToken,
+ companyId, authType, assertionTokenType, filterOption, selectOption,
+ expandOption, additionalQueryParameters, paginationType);
}
}
}
diff --git a/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java b/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java
index 0780dc6..d4b8595 100644
--- a/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java
+++ b/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java
@@ -275,7 +275,6 @@ public ODataFeed readServiceEntityData(Edm edm, Long skip, Long top)
String nextLink = dataFeed.getFeedMetadata().getNextLink();
if (nextLink != null) {
nextUrl = nextLink;
- LOG.info("Next page url: {}", nextLink);
}
}
return dataFeed;
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..b3a397d 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,9 +24,12 @@
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.SuccessFactorsAccessToken;
+import io.cdap.plugin.successfactors.connector.SuccessFactorsConnectorConfig;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,10 +37,13 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
+import java.rmi.ConnectException;
import java.util.Base64;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
import javax.ws.rs.core.MediaType;
/**
@@ -50,15 +56,12 @@ public class SuccessFactorsTransporter {
private static final long CONNECTION_TIMEOUT = 300;
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 static String accessToken;
private Response response;
+ private final SuccessFactorsConnectorConfig config;
- public SuccessFactorsTransporter(String username, String password) {
- this.username = username;
- this.password = password;
-
+ public SuccessFactorsTransporter(SuccessFactorsConnectorConfig config) {
+ this.config = config;
}
/**
@@ -128,6 +131,8 @@ public Response retrySapTransportCall(URL endpoint, String mediaType) throws IOE
Retryer retryer = RetryerBuilder.newBuilder()
.retryIfExceptionOfType(RetryableException.class)
+ .retryIfExceptionOfType(ExecutionException.class)
+ .retryIfExceptionOfType(ConnectException.class)
.withWaitStrategy(WaitStrategies.exponentialWait(WAIT_TIME, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt((int) MAX_NUMBER_OF_RETRY_ATTEMPTS))
.build();
@@ -135,8 +140,8 @@ public Response retrySapTransportCall(URL endpoint, String mediaType) throws IOE
try {
retryer.call(fetchRecords);
} catch (RetryException | ExecutionException e) {
- LOG.error("Data Recovery failed for URL {}.", endpoint);
- throw new IOException();
+ LOG.error("Data Recovery failed for URL {}.", endpoint, e);
+ throw new IOException("Failed after retries", e.getCause());
}
return response;
@@ -153,11 +158,50 @@ public Response retrySapTransportCall(URL endpoint, String mediaType) throws IOE
*/
private Response transport(URL endpoint, String mediaType) throws IOException, TransportException {
OkHttpClient enhancedOkHttpClient = getConfiguredClient().build();
- Request req = buildRequest(endpoint, mediaType);
+ Request req = null;
+
+ if (config.getAuthType().equals(SuccessFactorsConnectorConfig.BASIC_AUTH)) {
+ req = buildRequest(endpoint, mediaType);
+ } else {
+ if (accessToken == null || accessToken.isEmpty()) {
+ accessToken = getAccessToken();
+ }
+ req = buildRequestWithBearerToken(endpoint, mediaType, accessToken);
+
+ try {
+ Response response = enhancedOkHttpClient.newCall(req).execute();
+
+ // If the response code is 403 (Forbidden), attempt to refresh access token
+ if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
+ LOG.info("refreshing access token");
+ accessToken = getAccessToken(); // Refresh access token
+ req = buildRequestWithBearerToken(endpoint, mediaType, accessToken);
+ response = enhancedOkHttpClient.newCall(req).execute();
+ }
+
+ return response;
+ } catch (IOException e) {
+ throw new IOException("Failed to execute the request", e);
+ }
+ }
return enhancedOkHttpClient.newCall(req).execute();
}
+ private String getAccessToken() throws IOException {
+ SuccessFactorsAccessToken token = new SuccessFactorsAccessToken(config);
+
+ try {
+ if (config.getAssertionToken() == null) {
+ return token.getAccessToken(token.getAssertionToken());
+ } else {
+ return token.getAccessToken(config.getAssertionToken());
+ }
+ } catch (IOException e) {
+ throw new IOException("Unable to fetch access token", e);
+ }
+ }
+
/**
* Prepares the {@code SuccessFactorsResponseContainer} from the given {@code Response}.
*
@@ -218,13 +262,22 @@ 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)
);
}
+ private Request buildRequestWithBearerToken(URL endpoint, String mediaType, String accessToken) {
+ return new Request.Builder()
+ .addHeader("Authorization", "Bearer " + accessToken)
+ .addHeader("Accept", mediaType)
+ .get()
+ .url(endpoint)
+ .build();
+ }
+
/**
* Calls the SuccessFactors entity for the given URL and returns the respective response.
* Supported calls are:
diff --git a/src/main/resources/i10n/SuccessFactorsBatchSourceBundle.properties b/src/main/resources/i10n/SuccessFactorsBatchSourceBundle.properties
index 9e432c5..5f1cbb5 100644
--- a/src/main/resources/i10n/SuccessFactorsBatchSourceBundle.properties
+++ b/src/main/resources/i10n/SuccessFactorsBatchSourceBundle.properties
@@ -11,7 +11,7 @@ root.cause.log=Root Cause:
## SAP SuccessFactors specific messages
err.feature.not.supported=Entity 'Key' property based extraction is not supported.
err.invalid.base.url=Please verify the provided base url is correct. Please contact the SAP administrator.
-err.invalid.credential=Please verify the connection parameters. Username or password is incorrect.
+err.invalid.credential=Please verify the connection parameters.
err.invalid.entityCall=Associated Entity must be used with the expand field. Please provide the Expand fields.
## SAP SuccessFactors - Design Time generic error
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..2d6c999 100644
--- a/src/test/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorTest.java
+++ b/src/test/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorTest.java
@@ -78,6 +78,7 @@ public void testConfiguration() throws TransportException, SuccessFactorsService
.baseURL("http://localhost")
.entityName("entity-name")
.username("username")
+ .authType("basicAuth")
.password("password");
pluginConfig = pluginConfigBuilder.build();
@@ -85,8 +86,7 @@ public void testConfiguration() throws TransportException, SuccessFactorsService
@Test
public void testValidateSuccessfulConnection() throws TransportException, SuccessFactorsServiceException {
- successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection().getUsername(),
- pluginConfig.getConnection().getPassword());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsUrlContainer.class, SuccessFactorsTransporter.class,
SuccessFactorsSchemaGenerator.class) {
{
@@ -103,8 +103,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsUrlContainer.class, SuccessFactorsTransporter.class,
SuccessFactorsSchemaGenerator.class) {
{
@@ -120,8 +119,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsUrlContainer.class, SuccessFactorsTransporter.class,
SuccessFactorsSchemaGenerator.class) {
{
@@ -154,8 +152,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsTransporter.class) {
{
successFactorsTransporter.callSuccessFactorsEntity(null, anyString, anyString);
@@ -190,8 +187,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsConnector.class, SuccessFactorsTransporter.class) {
{
@@ -232,8 +228,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
successFactorsConnector = new SuccessFactorsConnector(pluginConfig.getConnection());
new Expectations(SuccessFactorsTransporter.class, SuccessFactorsTransporter.class, SuccessFactorsConnector.class) {
@@ -267,8 +262,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsTransporter.class, SuccessFactorsTransporter.class, SuccessFactorsConnector.class) {
{
successFactorsTransporter.callSuccessFactorsEntity(null, anyString, anyString);
@@ -291,8 +285,7 @@ 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());
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
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..5ee2d96 100644
--- a/src/test/java/io/cdap/plugin/successfactors/source/SuccessFactorsSourceTest.java
+++ b/src/test/java/io/cdap/plugin/successfactors/source/SuccessFactorsSourceTest.java
@@ -80,19 +80,13 @@ public void setUp() {
.entityName("entity name")
.username("username")
.password("password")
- .selectOption("col1,col2, \n parent/col1,\r col3 ");
+ .authType("basicAuth");
}
@Test
public void testConfigurePipelineWithInvalidUrl() throws Exception {
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("unit-test-ref-name")
- .baseURL("base_url")
- .entityName("entity name")
- .username("username")
- .password("password")
- .selectOption("col1,col2, \n parent/col1,\r col3 ");
- SuccessFactorsPluginConfig pluginConfig = pluginConfigBuilder.build();
+ SuccessFactorsPluginConfig pluginConfig = pluginConfigBuilder.baseURL("base_url")
+ .selectOption("col1,col2, \n parent/col1,\r col3 ").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
Map plugins = new HashMap<>();
MockPipelineConfigurer mockPipelineConfigurer = new MockPipelineConfigurer(null, plugins);
@@ -107,15 +101,9 @@ public void testConfigurePipelineWithInvalidUrl() throws Exception {
@Test
public void testConfigurePipelineWithInvalidReferenceName() {
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("")
- .baseURL("http://localhost")
- .entityName("entity name")
- .username("username")
- .password("password")
- .selectOption("col1,col2, \n parent/col1,\r col3 ");
try {
- pluginConfig = pluginConfigBuilder.build();
+ pluginConfig = pluginConfigBuilder.referenceName("")
+ .selectOption("col1,col2, \n parent/col1,\r col3 ").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
Map plugins = new HashMap<>();
MockPipelineConfigurer mockPipelineConfigurer = new MockPipelineConfigurer(null, plugins);
@@ -152,16 +140,8 @@ private Schema getPluginSchema() throws IOException {
@Test
public void testConfigurePipelineWSchemaNotNull() throws SuccessFactorsServiceException, TransportException,
IOException {
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("unit-test-ref-name")
- .baseURL("http://localhost")
- .entityName("entity-name")
- .username("username")
- .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));
@@ -265,20 +245,12 @@ private List getSplits() {
@Test
public void testPrepareRun() throws Exception {
+ pluginConfig = pluginConfigBuilder.paginationType("serverSide")
+ .selectOption("col1,col2, \n parent/col1,\r col3 ")
+ .filterOption("$topeq2").build();
successFactorsService = new SuccessFactorsService(pluginConfig, null);
successFactorsPartitionBuilder = new SuccessFactorsPartitionBuilder();
- successFactorsTransporter = new SuccessFactorsTransporter("username", "password");
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("unit-test-ref-name")
- .baseURL("http://localhost")
- .entityName("entity name")
- .username("username")
- .password("password")
- .paginationType("serverSide")
- .selectOption("col1,col2, \n parent/col1,\r col3 ")
- .filterOption("$topeq2");
-
- pluginConfig = pluginConfigBuilder.build();
+ successFactorsTransporter = new SuccessFactorsTransporter(pluginConfig.getConnection());
new Expectations(SuccessFactorsService.class, SuccessFactorsTransporter.class) {
{
@@ -313,14 +285,8 @@ public void testPrepareRun() throws Exception {
@Test
public void testPrepareRunUnauthorizedError() throws Exception {
successFactorsService = new SuccessFactorsService(pluginConfig, null);
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("")
- .baseURL("")
- .entityName("entity name")
- .username("username")
- .password("password");
-
- pluginConfig = pluginConfigBuilder.build();
+ pluginConfig = pluginConfigBuilder.referenceName("")
+ .baseURL("").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
new Expectations(SuccessFactorsService.class) {
@@ -346,14 +312,8 @@ public void testPrepareRunUnauthorizedError() throws Exception {
@Test
public void testPrepareRunForbiddenError() throws Exception {
successFactorsService = new SuccessFactorsService(pluginConfig, null);
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("")
- .baseURL("")
- .entityName("entity name")
- .username("username")
- .password("password");
-
- pluginConfig = pluginConfigBuilder.build();
+ pluginConfig = pluginConfigBuilder.referenceName("")
+ .baseURL("").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
new Expectations(SuccessFactorsService.class) {
@@ -379,14 +339,8 @@ public void testPrepareRunForbiddenError() throws Exception {
@Test
public void testPrepareRunNotFoundError() throws Exception {
successFactorsService = new SuccessFactorsService(pluginConfig, null);
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("")
- .baseURL("")
- .entityName("entity name")
- .username("username")
- .password("password");
-
- pluginConfig = pluginConfigBuilder.build();
+ pluginConfig = pluginConfigBuilder.referenceName("")
+ .baseURL("").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
new Expectations(SuccessFactorsService.class) {
@@ -412,14 +366,8 @@ public void testPrepareRunNotFoundError() throws Exception {
@Test
public void testPrepareRunBadRequestError() throws Exception {
successFactorsService = new SuccessFactorsService(pluginConfig, null);
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("")
- .baseURL("")
- .entityName("entity name")
- .username("username")
- .password("password");
-
- pluginConfig = pluginConfigBuilder.build();
+ pluginConfig = pluginConfigBuilder.referenceName("")
+ .baseURL("").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
new Expectations(SuccessFactorsService.class) {
@@ -445,14 +393,8 @@ public void testPrepareRunBadRequestError() throws Exception {
@Test
public void testPrepareRunInvalidVersionError() throws Exception {
successFactorsService = new SuccessFactorsService(pluginConfig, null);
- pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
- .referenceName("")
- .baseURL("")
- .entityName("entity name")
- .username("username")
- .password("password");
-
- pluginConfig = pluginConfigBuilder.build();
+ pluginConfig = pluginConfigBuilder.referenceName("")
+ .baseURL("").build();
successFactorsSource = new SuccessFactorsSource(pluginConfig);
new Expectations(SuccessFactorsService.class) {
diff --git a/src/test/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfigTest.java b/src/test/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfigTest.java
index b30c39f..efcb30f 100644
--- a/src/test/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfigTest.java
+++ b/src/test/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfigTest.java
@@ -19,6 +19,8 @@
import io.cdap.cdap.etl.api.validation.ValidationFailure;
import io.cdap.cdap.etl.mock.validation.MockFailureCollector;
import io.cdap.plugin.successfactors.common.util.ResourceConstants;
+import io.cdap.plugin.successfactors.connector.SuccessFactorsConnectorConfig;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -43,6 +45,7 @@ public void setUp() {
.referenceName(REFERENCE_NAME)
.baseURL(BASE_URL)
.entityName(ENTITY_NAME)
+ .authType(SuccessFactorsConnectorConfig.BASIC_AUTH)
.username(USER_NAME)
.password(PASSWORD);
}
@@ -174,4 +177,49 @@ public void testRefactoredPluginPropertyValues() {
Assert.assertEquals("Entity name not trimmed", "entity-name", pluginConfig.getEntityName());
Assert.assertEquals("Select option not trimmed", "col1,col2,parent/col1,col3", pluginConfig.getSelectOption());
}
+
+ @Test
+ public void testWithEmptyOauthCreateTokenParameters() {
+ SuccessFactorsPluginConfig pluginConfig = pluginConfigBuilder
+ .entityName("entity")
+ .authType("oAuth2")
+ .assertionTokenType("createToken")
+ .username(null)
+ .password(PASSWORD)
+ .setPrivateKey(null)
+ .setUserId(null)
+ .setTokenURL(null)
+ .build();
+ try {
+ pluginConfig.validatePluginParameters(failureCollector);
+ Assert.fail("Username is null");
+ } catch (ValidationException ve) {
+ List failures = ve.getFailures();
+ Assert.assertEquals(5, failures.size());
+ Assert.assertEquals(ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey("clientId"),
+ failures.get(0).getMessage());
+ }
+ }
+
+ @Test
+ public void testWithEmptyOauthEnterTokenParameters() {
+ SuccessFactorsPluginConfig pluginConfig = pluginConfigBuilder
+ .entityName("entity")
+ .authType("oAuth2")
+ .assertionTokenType("enterToken")
+ .username(null)
+ .password(PASSWORD)
+ .assertionToken(null)
+ .setClientId(null)
+ .build();
+ try {
+ pluginConfig.validatePluginParameters(failureCollector);
+ Assert.fail("Username is null");
+ } catch (ValidationException ve) {
+ List failures = ve.getFailures();
+ Assert.assertEquals(3, failures.size());
+ Assert.assertEquals(ResourceConstants.ERR_MISSING_PARAM_PREFIX.getMsgForKey("clientId"),
+ failures.get(0).getMessage());
+ }
+ }
}
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..b6dd4ff 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
@@ -22,6 +22,7 @@
import io.cdap.plugin.successfactors.source.metadata.TestSuccessFactorsUtil;
import io.cdap.plugin.successfactors.source.service.SuccessFactorsService;
import io.cdap.plugin.successfactors.source.transport.SuccessFactorsTransporter;
+
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.olingo.odata2.api.edm.Edm;
@@ -46,15 +47,13 @@ public class SuccessFactorsInputFormatTest {
@Before
public void initializeTests() {
- pluginConfig = Mockito.spy(new SuccessFactorsPluginConfig("referenceName",
- "baseURL",
- "entityName",
- null,
- "username",
- "password",
- "filterOption",
- "selectOption",
- "expandOption",
+ pluginConfig = Mockito.spy(new SuccessFactorsPluginConfig("referenceName", "baseURL",
+ "entityName", null,
+ "username", "password", "",
+ "", "", "", "",
+ "", "", "",
+ "", "filterOption",
+ "selectOption", "expandOption",
"additionalQueryParameters",
null));
}
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..c4a5ad2 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
@@ -80,8 +80,10 @@ public void testSelectWithExpandNames() throws SuccessFactorsServiceException {
public void testBuildExpandOutputSchema() throws SuccessFactorsServiceException {
SuccessFactorsPluginConfig pluginConfig = new SuccessFactorsPluginConfig("referenceName",
"baseUR", "entityName", "associateEntityName", "username",
- "password", "filterOption", "selectOption", "expandOption",
- "additionalQueryParameters", "paginationType");
+ "password", "",
+ "", "", "", "", "", "", "", "",
+ "filterOption", "selectOption", "expandOption", "additionalQueryParameters",
+ "paginationType");
Schema outputSchema = generator.buildExpandOutputSchema("Benefit",
"eligibleBenefits", "associatedEntity", pluginConfig);
int lastIndex = outputSchema.getFields().size() - 1;
@@ -154,7 +156,8 @@ public void testInvalidEntityName() throws SuccessFactorsServiceException {
public void testInvalidExpandName() throws SuccessFactorsServiceException {
SuccessFactorsPluginConfig pluginConfig = new SuccessFactorsPluginConfig("referenceName",
"baseUR", "entityName", "associateEntityName", "username",
- "password", "filterOption", "selectOption", "expandOption",
+ "password", "",
+ "", "", "", "", "", "", "", "", "filterOption", "selectOption", "expandOption",
"additionalQueryParameters", "paginationType");
exception.expectMessage("'assEntity' not found in the 'Benefit' entity.");
generator.buildExpandOutputSchema("Benefit", "INVALID-NAVIGATION-NAME",
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..3257186 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
@@ -73,6 +73,7 @@ public void setUp() throws Exception {
.filterOption("picklistId eq 'hrRanking'")
.username("test")
.password("secret")
+ .authType("basicAuth")
.paginationType("serverSide");
String metadataString = TestSuccessFactorsUtil.convertInputStreamToString(TestSuccessFactorsUtil.readResource
@@ -88,8 +89,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..c861043 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
@@ -89,6 +89,7 @@ public void setUp() throws Exception {
.entityName("Background_SpecialAssign")
.username("test")
.password("secret")
+ .authType("basicAuth")
.paginationType("serverSide");
String metadataString = TestSuccessFactorsUtil.convertInputStreamToString(TestSuccessFactorsUtil.readResource
@@ -104,8 +105,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 +141,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 +153,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..5a8b961 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
@@ -122,11 +122,11 @@ public void setUp() {
.entityName("Entity")
.username("test")
.password("secret")
+ .authType("basicAuth")
.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 +141,48 @@ 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 testCallSuccessFactorsWithOauth2() throws TransportException {
+ pluginConfigBuilder = SuccessFactorsPluginConfig.builder()
+ .baseURL("https://localhost:" + wireMockRule.httpsPort())
+ .entityName("Entity")
+ .username("test")
+ .password("secret")
+ .authType("oAuth2")
+ .expandOption("Products/Supplier");
+ pluginConfig = pluginConfigBuilder.build();
+ 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 +193,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..76fdd7f 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
@@ -33,6 +33,9 @@ public void initializeTests() {
"associatedEntity",
"username",
"password",
+ "", "", "", "",
+ "", "", "",
+ "", "",
"filterOption",
"selectOption",
"expandOption",
@@ -72,6 +75,9 @@ public void testGetURLWithAdditionalQueryParameters() {
"associatedEntity",
"username",
"password",
+ "", "", "", "",
+ "", "", "",
+ "", "",
"",
"",
"",
diff --git a/widgets/SuccessFactors-batchsource.json b/widgets/SuccessFactors-batchsource.json
index cd4523b..39bfce7 100644
--- a/widgets/SuccessFactors-batchsource.json
+++ b/widgets/SuccessFactors-batchsource.json
@@ -32,21 +32,83 @@
}
},
{
- "widget-type": "textbox",
- "label": "SAP SuccessFactors Logon Username",
- "name": "username",
+ "widget-type": "radio-group",
+ "label": "Authentication Type",
+ "name": "authType",
"widget-attributes": {
- "placeholder": ""
+ "layout": "inline",
+ "default": "basicAuth",
+ "options": [
+ {
+ "id": "basicAuth",
+ "label": "Basic Authentication"
+ },
+ {
+ "id": "oAuth2",
+ "label": "OAuth 2.0"
+ }
+ ]
}
},
{
- "widget-type": "password",
- "label": "SAP SuccessFactors Logon Password",
- "name": "password",
+ "name": "assertionTokenType",
+ "label": "Assertion Token Type",
+ "widget-type": "radio-group",
"widget-attributes": {
- "placeholder": ""
+ "layout": "inline",
+ "default": "enterToken",
+ "options": [
+ {
+ "id": "enterToken",
+ "label": "Enter Token"
+ },
+ {
+ "id": "createToken",
+ "label": "Create Token"
+ }
+ ]
}
},
+ {
+ "widget-type": "textbox",
+ "label": "Token URL",
+ "name": "tokenURL"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Client Id",
+ "name": "clientId"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Private Key",
+ "name": "privateKey"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "User Id",
+ "name": "userId"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Company Id",
+ "name": "companyId"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "SAP SuccessFactors Logon Username",
+ "name": "username"
+ },
+ {
+ "widget-type": "password",
+ "label": "SAP SuccessFactors Logon Password",
+ "name": "password"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Assertion Token",
+ "name": "assertionToken"
+ },
{
"widget-type": "textbox",
"label": "SAP SuccessFactors Base URL",
@@ -176,6 +238,38 @@
{
"type": "property",
"name": "baseURL"
+ },
+ {
+ "type": "property",
+ "name": "authType"
+ },
+ {
+ "type": "property",
+ "name": "tokenURL"
+ },
+ {
+ "type": "property",
+ "name": "clientId"
+ },
+ {
+ "type": "property",
+ "name": "privateKey"
+ },
+ {
+ "type": "property",
+ "name": "userId"
+ },
+ {
+ "type": "property",
+ "name": "companyId"
+ },
+ {
+ "type": "property",
+ "name": "assertionTokenType"
+ },
+ {
+ "type": "property",
+ "name": "assertionToken"
}
]
},
@@ -190,6 +284,78 @@
"name": "connection"
}
]
+ },
+ {
+ "name": "basicAuth",
+ "condition": {
+ "property": "authType",
+ "operator": "equal to",
+ "value": "basicAuth"
+ },
+ "show": [
+ {
+ "name": "username",
+ "type": "property"
+ },
+ {
+ "name": "password",
+ "type": "property"
+ }
+ ]
+ },
+ {
+ "name": "oAuth2",
+ "condition": {
+ "property": "authType",
+ "operator": "equal to",
+ "value": "oAuth2"
+ },
+ "show": [
+ {
+ "name": "assertionTokenType",
+ "type": "property"
+ },
+ {
+ "name": "clientId",
+ "type": "property"
+ },
+ {
+ "type": "property",
+ "name": "companyId"
+ }
+ ]
+ },
+ {
+ "name": "enterAssertionToken",
+ "condition": {
+ "expression": "authType == 'oAuth2' && assertionTokenType == 'enterToken'"
+ },
+ "show": [
+ {
+ "type": "property",
+ "name": "assertionToken"
+ }
+ ]
+ },
+ {
+ "name": "createAssertionToken",
+ "condition": {
+ "expression": "authType == 'oAuth2' && assertionTokenType == 'createToken'"
+ },
+ "show": [
+ {
+ "name": "tokenURL",
+ "type": "property"
+ },
+ {
+ "name": "privateKey",
+ "type": "property"
+ },
+ {
+ "name": "userId",
+ "type": "property"
+ }
+ ]
}
],
"outputs": [
diff --git a/widgets/SuccessFactors-connector.json b/widgets/SuccessFactors-connector.json
index 7ab0324..c2721bc 100644
--- a/widgets/SuccessFactors-connector.json
+++ b/widgets/SuccessFactors-connector.json
@@ -8,21 +8,83 @@
"label": "Credentials",
"properties": [
{
- "widget-type": "textbox",
- "label": "SAP SuccessFactors Logon Username",
- "name": "username",
+ "widget-type": "radio-group",
+ "label": "Authentication Type",
+ "name": "authType",
"widget-attributes": {
- "placeholder": ""
+ "layout": "inline",
+ "default": "basicAuth",
+ "options": [
+ {
+ "id": "basicAuth",
+ "label": "Basic Authentication"
+ },
+ {
+ "id": "oAuth2",
+ "label": "OAuth 2.0"
+ }
+ ]
}
},
{
- "widget-type": "password",
- "label": "SAP SuccessFactors Logon Password",
- "name": "password",
+ "name": "assertionTokenType",
+ "label": "Assertion Token Type",
+ "widget-type": "radio-group",
"widget-attributes": {
- "placeholder": ""
+ "layout": "inline",
+ "default": "enterToken",
+ "options": [
+ {
+ "id": "enterToken",
+ "label": "Enter Token"
+ },
+ {
+ "id": "createToken",
+ "label": "Create Token"
+ }
+ ]
}
},
+ {
+ "widget-type": "textbox",
+ "label": "Token URL",
+ "name": "tokenURL"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Client Id",
+ "name": "clientId"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Private Key",
+ "name": "privateKey"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "User Id",
+ "name": "userId"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Assertion Token",
+ "name": "assertionToken"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "Company Id",
+ "name": "companyId"
+ },
+ {
+ "widget-type": "textbox",
+ "label": "SAP SuccessFactors Logon Username",
+ "name": "username"
+ },
+ {
+ "widget-type": "password",
+ "label": "SAP SuccessFactors Logon Password",
+ "name": "password"
+ },
{
"widget-type": "textbox",
"label": "SAP SuccessFactors Base URL",
@@ -34,5 +96,79 @@
]
}
],
+ "filters":[
+ {
+ "name": "basicAuth",
+ "condition": {
+ "property": "authType",
+ "operator": "equal to",
+ "value": "basicAuth"
+ },
+ "show": [
+ {
+ "name": "username",
+ "type": "property"
+ },
+ {
+ "name": "password",
+ "type": "property"
+ }
+ ]
+ },
+ {
+ "name": "Authenticate with oAuth2",
+ "condition": {
+ "property": "authType",
+ "operator": "equal to",
+ "value": "oAuth2"
+ },
+ "show": [
+ {
+ "name": "assertionTokenType",
+ "type": "property"
+ },
+ {
+ "name": "clientId",
+ "type": "property"
+ },
+ {
+ "type": "property",
+ "name": "companyId"
+ }
+ ]
+ },
+ {
+ "name": "enterAssertionToken",
+ "condition": {
+ "expression": "authType == 'oAuth2' && assertionTokenType== 'enterToken'"
+ },
+ "show": [
+ {
+ "type": "property",
+ "name": "assertionToken"
+ }
+ ]
+ },
+ {
+ "name": "createAssertionToken",
+ "condition": {
+ "expression": "authType == 'oAuth2' && assertionTokenType == 'createToken'"
+ },
+ "show": [
+ {
+ "name": "tokenURL",
+ "type": "property"
+ },
+ {
+ "name": "privateKey",
+ "type": "property"
+ },
+ {
+ "name": "userId",
+ "type": "property"
+ }
+ ]
+ }
+ ],
"outputs": []
}
\ No newline at end of file