diff --git a/README.md b/README.md index c9b082bf..27183a7e 100755 --- a/README.md +++ b/README.md @@ -25,6 +25,32 @@ Java platform. Among other things, the SDK: For more information about the Java SDK and how to use it, please see the Javadocs at http://salesforce-marketingcloud.github.io/FuelSDK-Java/. +New Features in Version 1.5.0 +------------ +* Added Refresh Token support for OAuth2 authentication +* Added Web/Public App support for OAuth2 authentication + + More details on Access Tokens for Web/Public Apps can be found [here](https://developer.salesforce.com/docs/atlas.en-us.mc-app-development.meta/mc-app-development/access-token-app.htm) + + Sample Config for OAuth2: + + ``` + clientId= + clientSecret= + authEndpoint= + endpoint= + soapEndpoint= + useOAuth2Authentication=true + accountId= + scope= + applicationType= + redirectURI= + authorizationCode= + ``` + +* applicationType can have one of the following values: `server`, `public`, `web`. The default value of applicationType is `server`. + + New Features in Version 1.4.0 ------------ * Added support for OAuth2 authentication - [More Details](https://developer.salesforce.com/docs/atlas.en-us.mc-app-development.meta/mc-app-development/integration-considerations.htm) @@ -51,7 +77,7 @@ The easiest way to install the Java SDK is via Maven—simply add the follow com.github.salesforce-marketingcloud fuelsdk - 1.4.0 + 1.5.0 Maven will automatically resolve, download, and install all dependencies for you. diff --git a/pom.xml b/pom.xml index 89c3fde1..274ec976 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.github.salesforce-marketingcloud fuelsdk - 1.4.0 + 1.5.0 Salesforce Marketing Cloud Java SDK Salesforce Marketing Cloud Java SDK https://github.com/salesforce-marketingcloud/FuelSDK-Java diff --git a/src/main/java/com/exacttarget/fuelsdk/ETClient.java b/src/main/java/com/exacttarget/fuelsdk/ETClient.java index 6f4d394f..b13ea6cb 100755 --- a/src/main/java/com/exacttarget/fuelsdk/ETClient.java +++ b/src/main/java/com/exacttarget/fuelsdk/ETClient.java @@ -48,6 +48,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; /** @@ -78,9 +79,6 @@ public class ETClient { private String clientId = null; private String clientSecret = null; - private String username = null; - private String password = null; - private String endpoint = null; private String authEndpoint = null; private String soapEndpoint = null; @@ -96,6 +94,15 @@ public class ETClient { private String accessToken = null; private int expiresIn = 0; private String legacyToken = null; + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getRefreshToken() { + return refreshToken; + } + private String refreshToken = null; private long tokenExpirationTime = 0; @@ -103,10 +110,12 @@ public class ETClient { private static String fetchedSoapEndpoint = null; private static final long cacheDurationInMillis = 1000 * 60 * 10; // 10 minutes private boolean useOAuth2Authentication; - private String accountId; - private String scope; - /** + private String applicationType; + private String authorizationCode; + private String redirectURI; + + /** * Class constructor, Initializes a new instance of the class. */ public ETClient() @@ -137,9 +146,6 @@ public ETClient(ETConfiguration configuration) clientId = configuration.get("clientId"); clientSecret = configuration.get("clientSecret"); - username = configuration.get("username"); - password = configuration.get("password"); - endpoint = configuration.get("endpoint"); if (endpoint == null) { endpoint = DEFAULT_ENDPOINT; @@ -159,30 +165,37 @@ public ETClient(ETConfiguration configuration) gson = gsonBuilder.create(); } - useOAuth2Authentication = configuration.isTrue("useOAuth2Authentication") ? true : false; - accountId = configuration.get("accountId"); - scope = configuration.get("scope"); + useOAuth2Authentication = configuration.isTrue("useOAuth2Authentication"); - if (clientId != null && clientSecret != null) { - authConnection = new ETRestConnection(this, authEndpoint, true); - requestToken(); - restConnection = new ETRestConnection(this, endpoint); - fetchSoapEndpoint(); - soapConnection = new ETSoapConnection(this, soapEndpoint, accessToken); - } else { - if (username == null || password == null) { - throw new ETSdkException("must specify either " + - "clientId/clientSecret or username/password"); + applicationType = configuration.get("applicationType"); + authorizationCode = configuration.get("authorizationCode"); + redirectURI = configuration.get("redirectURI"); + + if(isNullOrBlankOrEmpty(applicationType)){ + applicationType = "server"; + configuration.set("applicationType", "server"); + } + + if(applicationType.equals("public") || applicationType.equals("web")){ + if (isNullOrBlankOrEmpty(authorizationCode) || isNullOrBlankOrEmpty(redirectURI)){ + throw new ETSdkException("AuthorizationCode or RedirectURI is null: For Public/Web Apps, " + + "authorizationCode and redirectURI must be provided in config file"); } - if (soapEndpoint == null) { - throw new ETSdkException("must specify soapEndpoint " + - "when authenticating with username/password"); + } + + if(applicationType.equals("public")){ + if(isNullOrBlankOrEmpty(clientId)){ + throw new ETSdkException("ClientId is null: clientId must be provided in config file"); + } + } + else{ + if(isNullOrBlankOrEmpty(clientId) || isNullOrBlankOrEmpty(clientSecret)){ + throw new ETSdkException("ClientId or ClientSecret is null: clientId and clientSecret must be provided in config file"); } - soapConnection = new ETSoapConnection(this, soapEndpoint, - username, - password); } + buildClients(); + if (configuration.isFalse("autoHydrateObjects")) { autoHydrateObjects = false; } @@ -191,8 +204,6 @@ public ETClient(ETConfiguration configuration) logger.trace("ETClient initialized:"); logger.trace(" clientId = " + clientId); logger.trace(" clientSecret = *"); - logger.trace(" username = " + username); - logger.trace(" password = *"); logger.trace(" endpoint = " + endpoint); logger.trace(" authEndpoint = " + authEndpoint); logger.trace(" soapEndpoint = " + soapEndpoint); @@ -200,6 +211,18 @@ public ETClient(ETConfiguration configuration) } } + private void buildClients() throws ETSdkException { + authConnection = new ETRestConnection(this, authEndpoint, true); + requestToken(); + restConnection = new ETRestConnection(this, endpoint); + fetchSoapEndpoint(); + soapConnection = new ETSoapConnection(this, soapEndpoint, accessToken); + } + + public static boolean isNullOrBlankOrEmpty(String str) { + return str == null || StringUtils.isBlank(str) || StringUtils.isEmpty(str); + } + private void fetchSoapEndpoint() { if(useOAuth2Authentication) { return; @@ -329,32 +352,8 @@ public synchronized String requestToken() private synchronized String requestOAuth2Token() throws ETSdkException { - if (clientId == null || clientSecret == null) { - // no-op - return null; - } - // - // Construct the JSON payload. - // - - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("client_id", clientId); - jsonObject.addProperty("client_secret", clientSecret); - jsonObject.addProperty("grant_type", "client_credentials"); - if(accountId != null && accountId.length() > 0) - { - jsonObject.addProperty("account_id", accountId); - } - if(scope != null && scope.length() > 0) - { - jsonObject.addProperty("scope", scope); - } - - String requestPayload = gson.toJson(jsonObject); - - ETRestConnection.Response response = null; - - response = authConnection.post(PATH_OAUTH2TOKEN, requestPayload); + JsonObject payload = createPayload(configuration); + ETRestConnection.Response response = authConnection.post(PATH_OAUTH2TOKEN, gson.toJson(payload)); if (response.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new ETSdkException("error obtaining OAuth2 access token " @@ -364,34 +363,60 @@ private synchronized String requestOAuth2Token() + response.getResponseMessage() + ")"); } - - // - // Parse the JSON response into the appropriate instance - // variables: - // + JsonParser jsonParser = new JsonParser(); String responsePayload = response.getResponsePayload(); + JsonObject jsonObject = jsonParser.parse(responsePayload).getAsJsonObject(); - JsonParser jsonParser = new JsonParser(); - jsonObject = jsonParser.parse(responsePayload).getAsJsonObject(); this.accessToken = jsonObject.get("access_token").getAsString(); - this.expiresIn = jsonObject.get("expires_in").getAsInt(); this.endpoint = jsonObject.get("rest_instance_url").getAsString(); this.soapEndpoint = jsonObject.get("soap_instance_url").getAsString() + "service.asmx"; - // - // Calculate the token expiration time. As before, - // System.currentTimeMills() is in milliseconds so - // we multiply expiresIn by 1000: - // - + this.expiresIn = jsonObject.get("expires_in").getAsInt(); tokenExpirationTime = System.currentTimeMillis() + (expiresIn * 1000); + if(jsonObject.has("refresh_token")){ + this.refreshToken = jsonObject.get("refresh_token").getAsString(); + } + return accessToken; } + JsonObject createPayload(ETConfiguration configuration) { + JsonObject payload = new JsonObject(); + + payload.addProperty("client_id", configuration.get("clientId")); + + String applicationType = configuration.get("applicationType"); + + if(applicationType.equals("web") || applicationType.equals("server")){ + payload.addProperty("client_secret", configuration.get("clientSecret")); + } + if(!isNullOrBlankOrEmpty(refreshToken)){ + payload.addProperty("grant_type", "refresh_token"); + payload.addProperty("refresh_token", refreshToken); + } + else if(applicationType.equals("public") || applicationType.equals("web")){ + payload.addProperty("grant_type", "authorization_code"); + payload.addProperty("code", configuration.get("authorizationCode")); + payload.addProperty("redirect_uri", configuration.get("redirectURI")); + } + else{ + payload.addProperty("grant_type", "client_credentials"); + } + + if(!isNullOrBlankOrEmpty(configuration.get("accountId"))){ + payload.addProperty("account_id", configuration.get("accountId")); + } + if(!isNullOrBlankOrEmpty(configuration.get("scope"))){ + payload.addProperty("scope", configuration.get("scope")); + } + + return payload; + } + /** - * + * * @param refreshToken The refresh token * @return The request token */ diff --git a/src/main/java/com/exacttarget/fuelsdk/ETRestConnection.java b/src/main/java/com/exacttarget/fuelsdk/ETRestConnection.java index 9770949a..bb445e1d 100755 --- a/src/main/java/com/exacttarget/fuelsdk/ETRestConnection.java +++ b/src/main/java/com/exacttarget/fuelsdk/ETRestConnection.java @@ -240,7 +240,7 @@ private HttpURLConnection sendRequest(URL url, Method method, String payload) try { connection = (HttpURLConnection) url.openConnection(); - connection.setRequestProperty("User-Agent", "FuelSDK-Java-v1.4.0-REST-"+method+"-"+object); + connection.setRequestProperty("User-Agent", "FuelSDK-Java-v1.5.0-REST-"+method+"-"+object); connection.setRequestMethod(method.toString()); } catch (ProtocolException ex) { throw new ETSdkException("error setting request method: " + method.toString(), ex); diff --git a/src/main/java/com/exacttarget/fuelsdk/ETSoapConnection.java b/src/main/java/com/exacttarget/fuelsdk/ETSoapConnection.java index 54d70d02..0328b9b2 100755 --- a/src/main/java/com/exacttarget/fuelsdk/ETSoapConnection.java +++ b/src/main/java/com/exacttarget/fuelsdk/ETSoapConnection.java @@ -234,12 +234,12 @@ public Soap getSoap() { } public Soap getSoap(String m) { - soapClient.getRequestContext().put("HTTP_HEADER_USER_AGENT", "FuelSDK-Java-v1.4.0-SOAP-"+m); + soapClient.getRequestContext().put("HTTP_HEADER_USER_AGENT", "FuelSDK-Java-v1.5.0-SOAP-"+m); return soap; } public Soap getSoap(String m, String o) { - soapClient.getRequestContext().put("HTTP_HEADER_USER_AGENT", "FuelSDK-Java-v1.4.0-SOAP-"+m+"-"+o); + soapClient.getRequestContext().put("HTTP_HEADER_USER_AGENT", "FuelSDK-Java-v1.5.0-SOAP-"+m+"-"+o); return soap; } diff --git a/src/test/java/com/exacttarget/fuelsdk/ETClientTest.java b/src/test/java/com/exacttarget/fuelsdk/ETClientTest.java index 3ccb62d4..a89163a3 100755 --- a/src/test/java/com/exacttarget/fuelsdk/ETClientTest.java +++ b/src/test/java/com/exacttarget/fuelsdk/ETClientTest.java @@ -38,32 +38,19 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; -import org.junit.Assume; +import com.google.gson.JsonObject; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; + +import static org.junit.Assert.*; public class ETClientTest { - -/* public static void main(String[] args){ - ETClientTest etc = new ETClientTest(); - try{ - etc.testBackwardCompatibility1(); - }catch(ETSdkException ex){ - ex.printStackTrace(); - } - - } -*/ + + private static ETClient client; + @BeforeClass - public static void setUpBeforeClass() - throws ETSdkException - { - // Assume.assumeNotNull(ETClientTest.class - // .getResource("/fuelsdk.properties")); + public static void setUpClass() throws ETSdkException { + client = new ETClient("fuelsdk.properties"); } @Test @@ -71,13 +58,9 @@ public static void setUpBeforeClass() public void testBackwardCompatibility1() throws ETSdkException { - ETClient client = new ETClient("fuelsdk.properties"); ETFilter filter = new ETFilter(); -// filter.setProperty("key"); -// filter.setOperator(ETFilter.Operator.EQUALS); -// filter.addValue("dataextension_default"); - ETExpression exp = new ETExpression(); + exp.setProperty("key"); exp.setOperator(ETExpression.Operator.EQUALS); exp.addValue("dataextension_default"); @@ -93,20 +76,16 @@ public void testBackwardCompatibility1() public void testBackwardCompatibility2() throws ETSdkException { - ETClient client = new ETClient("fuelsdk.properties"); ETFilter filter = new ETFilter(); -// filter.setProperty("key"); -// filter.setOperator(ETFilter.Operator.EQUALS); -// filter.addValue("dataextension_default"); - ETExpression exp = new ETExpression(); + exp.setProperty("key"); exp.setOperator(ETExpression.Operator.EQUALS); exp.addValue("dataextension_default"); - + filter.setExpression(exp); filter.addProperty("key"); - + ETResponse response = client.retrieve(ETFolder.class, filter);//,"key"); assertEquals(1, response.getObjects().size()); @@ -129,21 +108,17 @@ public void testBackwardCompatibility2() public void testBackwardCompatibility3() throws ETSdkException { - ETClient client = new ETClient("fuelsdk.properties"); ETFilter filter = new ETFilter(); -// filter.setProperty("key"); -// filter.setOperator(ETFilter.Operator.EQUALS); -// filter.addValue("dataextension_default"); - ETExpression exp = new ETExpression(); + exp.setProperty("key"); exp.setOperator(ETExpression.Operator.EQUALS); exp.addValue("dataextension_default"); - + filter.setExpression(exp); filter.addProperty("key"); filter.addProperty("name"); - + ETResponse response = client.retrieve(ETFolder.class, filter);//,"key","name"); assertEquals(1, response.getObjects().size()); @@ -166,22 +141,18 @@ public void testBackwardCompatibility3() public void testBackwardCompatibility4() throws ETSdkException { - ETClient client = new ETClient("fuelsdk.properties"); ETFilter filter = new ETFilter(); -// filter.setProperty("key"); -// filter.setOperator(ETFilter.Operator.EQUALS); -// filter.addValue("dataextension_default"); - ETExpression exp = new ETExpression(); + exp.setProperty("key"); exp.setOperator(ETExpression.Operator.EQUALS); exp.addValue("dataextension_default"); - + filter.setExpression(exp); filter.addProperty("key"); filter.addProperty("name"); filter.addProperty("description"); - + ETResponse response = client.retrieve(ETFolder.class, filter);//,"key","name", "description"); assertEquals(1, response.getObjects().size()); @@ -224,6 +195,129 @@ public void testSoapEndpointCaching() } } + @Test + public void isNullOrBlankOrEmpty_shouldReturnTrue_whenInputIsNullPointer(){ + + String nullPointer = null; + assertTrue(ETClient.isNullOrBlankOrEmpty(nullPointer)); + } + + @Test + public void isNullOrBlankOrEmpty_shouldReturnTrue_whenInputIsBlankString(){ + + String blankString = " "; + assertTrue(ETClient.isNullOrBlankOrEmpty(blankString)); + } + + @Test + public void isNullOrBlankOrEmpty_shouldReturnTrue_whenInputIsEmptyString(){ + + String emptyString = ""; + assertTrue(ETClient.isNullOrBlankOrEmpty(emptyString)); + } + + @Test + public void isNullOrBlankOrEmpty_shouldReturnFalse_whenInputIsNotNullOrBlankOrEmptyString(){ + + String notNullOrBlankOrEmptyString = "NotNullOrBlankOrEmptyString"; + assertFalse(ETClient.isNullOrBlankOrEmpty(notNullOrBlankOrEmptyString)); + } + + @Test + public void createPayload_shouldReturnPayloadWithPublicAppAttributes_whenApplicationTypeIsPublic(){ + ETConfiguration configuration = client.getConfiguration(); + + configuration.set("applicationType", "public"); + configuration.set("redirectURI", "redirectURI"); + configuration.set("authorizationCode", "authorizationCode"); + + JsonObject payload = client.createPayload(configuration); + + assertEquals(configuration.get("clientId"), payload.get("client_id").getAsString()); + assertEquals(configuration.get("redirectURI"), payload.get("redirect_uri").getAsString()); + assertEquals(configuration.get("authorizationCode"), payload.get("code").getAsString()); + assertEquals("authorization_code", payload.get("grant_type").getAsString()); + } + + @Test + public void createPayload_shouldReturnPayloadWithNoClientSecret_whenApplicationTypeIsPublic(){ + ETConfiguration configuration = client.getConfiguration(); + + configuration.set("applicationType", "public"); + configuration.set("redirectURI", "redirectURI"); + configuration.set("authorizationCode", "authorizationCode"); + + JsonObject payload = client.createPayload(configuration); + + assertNull(payload.get("client_secret")); + } + + @Test + public void createPayload_shouldReturnPayloadWithWebAppAttributes_whenApplicationTypeIsWeb(){ + ETConfiguration configuration = client.getConfiguration(); + + configuration.set("applicationType", "web"); + configuration.set("redirectURI", "redirectURI"); + configuration.set("authorizationCode", "authorizationCode"); + + JsonObject payload = client.createPayload(configuration); + + assertEquals(configuration.get("clientId"), payload.get("client_id").getAsString()); + assertEquals(configuration.get("clientSecret"), payload.get("client_secret").getAsString()); + assertEquals(configuration.get("redirectURI"), payload.get("redirect_uri").getAsString()); + assertEquals(configuration.get("authorizationCode"), payload.get("code").getAsString()); + assertEquals("authorization_code", payload.get("grant_type").getAsString()); + } + + @Test + public void createPayload_shouldReturnPayloadWithServerAppAttributes_whenApplicationTypeIsServer(){ + ETConfiguration configuration = client.getConfiguration(); + + configuration.set("applicationType", "server"); + + JsonObject payload = client.createPayload(configuration); + + assertEquals(configuration.get("clientId"), payload.get("client_id").getAsString()); + assertEquals(configuration.get("clientSecret"), payload.get("client_secret").getAsString()); + assertEquals("client_credentials", payload.get("grant_type").getAsString()); + } + + @Test + public void createPayload_shouldReturnPayloadWithServerAppAttributes_whenApplicationTypeIsNotPassedInConfig(){ + ETConfiguration configuration = client.getConfiguration(); + + JsonObject payload = client.createPayload(configuration); + + assertEquals(configuration.get("clientId"), payload.get("client_id").getAsString()); + assertEquals(configuration.get("clientSecret"), payload.get("client_secret").getAsString()); + assertEquals("client_credentials", payload.get("grant_type").getAsString()); + } + + @Test + public void createPayload_shouldReturnPayloadWithNoRedirectURIandAuthCode_whenApplicationTypeIsServer(){ + ETConfiguration configuration = client.getConfiguration(); + + configuration.set("applicationType", "server"); + + JsonObject payload = client.createPayload(configuration); + + assertNull(payload.get("redirect_uri")); + assertNull(payload.get("code")); + } + + @Test + public void createPayload_shouldReturnPayloadWithRefreshToken_whenClientHasRefreshToken() throws ETSdkException { + ETClient client = new ETClient("fuelsdk.properties"); + ETConfiguration configuration = client.getConfiguration(); + + client.setRefreshToken("refreshToken"); + + JsonObject payload = client.createPayload(configuration); + + assertEquals(client.getRefreshToken(), payload.get("refresh_token").getAsString()); + assertEquals("refresh_token", payload.get("grant_type").getAsString()); + } + private String getFetchedSoapEndpoint(ETClient client) throws NoSuchFieldException, IllegalAccessException { Field field = client.getClass().getDeclaredField("fetchedSoapEndpoint"); field.setAccessible(true); @@ -236,25 +330,15 @@ private long getInstanceSoapEndpointExpiration(ETClient client) throws NoSuchFie return field.getLong(null); } - // - // XXX make these available to all tests - // - private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); private void assertIsDataExtensionFolder(ETResponse response) { assertEquals(1, response.getObjects().size()); ETFolder folder = response.getObjects().get(0); -// assertEquals("94511", folder.getId()); // XXX make configurable assertEquals("dataextension_default", folder.getKey()); assertEquals("Data Extensions", folder.getName()); assertEquals("", folder.getDescription()); -// assertEquals("2014-08-10T23:50:00.833", // XXX make configurable -// dateFormat.format(folder.getCreatedDate())); -// assertEquals("2014-08-10T23:50:00.833", // XXX make configurable -// dateFormat.format(folder.getModifiedDate())); -// assertEquals("dataextension", folder.getContentType()); assertNull(folder.getParentFolderKey()); assertTrue(folder.getIsActive()); assertFalse(folder.getIsEditable());