From e37b4cd9cd5e2f8c593b3a9126774d9c804c9b81 Mon Sep 17 00:00:00 2001 From: ScottVonDuhn Date: Thu, 8 Apr 2021 11:15:03 -0400 Subject: [PATCH 1/4] Added HMAC-SHA256 Signature Service and NetSuite API --- .../github/scribejava/apis/NetSuiteApi.java | 40 ++++++++++ .../apis/examples/NetSuiteExample.java | 66 +++++++++++++++++ .../services/HMACSha256SignatureService.java | 53 ++++++++++++++ .../HMACSha256SignatureServiceTest.java | 73 +++++++++++++++++++ 4 files changed, 232 insertions(+) create mode 100644 scribejava-apis/src/main/java/com/github/scribejava/apis/NetSuiteApi.java create mode 100644 scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java create mode 100644 scribejava-core/src/main/java/com/github/scribejava/core/services/HMACSha256SignatureService.java create mode 100644 scribejava-core/src/test/java/com/github/scribejava/core/services/HMACSha256SignatureServiceTest.java diff --git a/scribejava-apis/src/main/java/com/github/scribejava/apis/NetSuiteApi.java b/scribejava-apis/src/main/java/com/github/scribejava/apis/NetSuiteApi.java new file mode 100644 index 000000000..a2832570f --- /dev/null +++ b/scribejava-apis/src/main/java/com/github/scribejava/apis/NetSuiteApi.java @@ -0,0 +1,40 @@ +package com.github.scribejava.apis; + +import com.github.scribejava.core.builder.api.DefaultApi10a; +import com.github.scribejava.core.services.HMACSha256SignatureService; +import com.github.scribejava.core.services.SignatureService; + +public class NetSuiteApi extends DefaultApi10a { + + protected NetSuiteApi() { + } + + private static class InstanceHolder { + private static final NetSuiteApi INSTANCE = new NetSuiteApi(); + } + + public static NetSuiteApi instance() { + return InstanceHolder.INSTANCE; + } + + @Override + public SignatureService getSignatureService() { + return new HMACSha256SignatureService(); + } + + @Override + public String getAccessTokenEndpoint() { + return null; + } + + @Override + public String getRequestTokenEndpoint() { + return null; + } + + @Override + public String getAuthorizationBaseUrl() { + return null; + } + +} diff --git a/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java new file mode 100644 index 000000000..a506ca1ee --- /dev/null +++ b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java @@ -0,0 +1,66 @@ +package com.github.scribejava.apis.examples; + +import com.github.scribejava.apis.NetSuiteApi; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth1AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth10aService; +import org.apache.http.client.utils.URIBuilder; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class NetSuiteExample { + // Replace these with your own account id, consumer key, consumer secret, token id, token secret and restlet domain + private static final String ACCOUNT_ID = "your_account_id"; + private static final String CONSUMER_KEY = "your_consumer_key"; + private static final String CONSUMER_SECRET = "your_consumer_secret"; + private static final String TOKEN_ID = "your_token_id"; + private static final String TOKEN_SECRET = "your_token_secret"; + private static final String RESTLET_DOMAIN = "YOUR-ACCOUNT-ID.restlets.api.netsuite.com"; + + private static final String RESTLET_PATH = "/app/site/hosting/restlet.nl"; + private static final String SCRIPT = "script"; + private static final String DEPLOY = "deploy"; + private static final String PROTECTED_RESOURCE_URL = new URIBuilder() + .setScheme("https") + .setHost(RESTLET_DOMAIN) + .setPath(RESTLET_PATH) + .addParameter(SCRIPT, "your_restlet_script_id") + .addParameter(DEPLOY, "your_restlet_deploy_id") + .toString(); + + private NetSuiteExample() { + } + + @SuppressWarnings("PMD.SystemPrintln") + public static void main(String... args) throws IOException, InterruptedException, ExecutionException { + final OAuth10aService service = new ServiceBuilder(CONSUMER_KEY) + .apiSecret(CONSUMER_SECRET) + .build(NetSuiteApi.instance()); + + System.out.println("=== NetSuite's OAuth (Token-based Authorization) ==="); + System.out.println(); + + final OAuth1AccessToken accessToken = new OAuth1AccessToken(TOKEN_ID, TOKEN_SECRET); + + // Now let's go and ask for a protected resource! + System.out.println("Now we're going to access a protected resource..."); + final OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL); + request.addHeader("Content-Type", "application/json"); + request.setRealm(ACCOUNT_ID); + service.signRequest(accessToken, request); + + try (Response response = service.execute(request)) { + System.out.println("Got it! Let's see what we found..."); + System.out.println(); + System.out.println(response.getCode()); + System.out.println(response.getBody()); + } + + System.out.println(); + System.out.println("That's it man! Go and build something awesome with ScribeJava! :)"); + } +} diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/services/HMACSha256SignatureService.java b/scribejava-core/src/main/java/com/github/scribejava/core/services/HMACSha256SignatureService.java new file mode 100644 index 000000000..0761de3c8 --- /dev/null +++ b/scribejava-core/src/main/java/com/github/scribejava/core/services/HMACSha256SignatureService.java @@ -0,0 +1,53 @@ +package com.github.scribejava.core.services; + +import com.github.scribejava.core.base64.Base64; +import com.github.scribejava.core.exceptions.OAuthSignatureException; +import com.github.scribejava.core.utils.OAuthEncoder; +import com.github.scribejava.core.utils.Preconditions; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * HMAC-SHA256 implementation of {@link SignatureService} + */ +public class HMACSha256SignatureService implements SignatureService { + + private static final String EMPTY_STRING = ""; + private static final String CARRIAGE_RETURN = "\r\n"; + private static final String HMAC_SHA256 = "HmacSHA256"; + private static final String METHOD = "HMAC-SHA256"; + + /** + * {@inheritDoc} + */ + @Override + public String getSignature(String baseString, String apiSecret, String tokenSecret) { + try { + Preconditions.checkEmptyString(baseString, "Base string can't be null or empty string"); + Preconditions.checkNotNull(apiSecret, "Api secret can't be null"); + return doSign(baseString, OAuthEncoder.encode(apiSecret) + '&' + OAuthEncoder.encode(tokenSecret)); + } catch (NoSuchAlgorithmException | InvalidKeyException | RuntimeException e) { + throw new OAuthSignatureException(baseString, e); + } + } + + private String doSign(String toSign, String keyString) throws NoSuchAlgorithmException, InvalidKeyException { + final SecretKeySpec key = new SecretKeySpec(keyString.getBytes(StandardCharsets.UTF_8), HMAC_SHA256); + final Mac mac = Mac.getInstance(HMAC_SHA256); + mac.init(key); + final byte[] bytes = mac.doFinal(toSign.getBytes(StandardCharsets.UTF_8)); + return Base64.encode(bytes).replace(CARRIAGE_RETURN, EMPTY_STRING); + } + + /** + * {@inheritDoc} + */ + @Override + public String getSignatureMethod() { + return METHOD; + } +} diff --git a/scribejava-core/src/test/java/com/github/scribejava/core/services/HMACSha256SignatureServiceTest.java b/scribejava-core/src/test/java/com/github/scribejava/core/services/HMACSha256SignatureServiceTest.java new file mode 100644 index 000000000..fb9f17eec --- /dev/null +++ b/scribejava-core/src/test/java/com/github/scribejava/core/services/HMACSha256SignatureServiceTest.java @@ -0,0 +1,73 @@ +package com.github.scribejava.core.services; + +import com.github.scribejava.core.exceptions.OAuthException; +import org.junit.Before; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +public class HMACSha256SignatureServiceTest { + + private HMACSha256SignatureService service; + + @Before + public void setUp() { + service = new HMACSha256SignatureService(); + } + + @Test + public void shouldReturnSignatureMethodString() { + final String expected = "HMAC-SHA256"; + assertEquals(expected, service.getSignatureMethod()); + } + + @Test + public void shouldReturnSignature() { + final String apiSecret = "apiSecret"; + final String tokenSecret = "tokenSecret"; + final String baseString = "baseString"; + final String signature = "xDJIQbKJTwGumZFvSG1V3ctym2tz6kD8fKGWPr5ImPU="; + assertEquals(signature, service.getSignature(baseString, apiSecret, tokenSecret)); + } + + @Test + public void shouldThrowExceptionIfBaseStringIsNull() { + assertThrows(OAuthException.class, new ThrowingRunnable() { + @Override + public void run() { + service.getSignature(null, "apiSecret", "tokenSecret"); + } + }); + } + + @Test + public void shouldThrowExceptionIfBaseStringIsEmpty() { + assertThrows(OAuthException.class, new ThrowingRunnable() { + @Override + public void run() { + service.getSignature(" ", "apiSecret", "tokenSecret"); + } + }); + } + + @Test + public void shouldThrowExceptionIfApiSecretIsNull() { + assertThrows(OAuthException.class, new ThrowingRunnable() { + @Override + public void run() { + service.getSignature("baseString", null, "tokenSecret"); + } + }); + } + + @Test + public void shouldNotThrowExceptionIfApiSecretIsEmpty() { + final String apiSecret = " "; + final String tokenSecret = "tokenSecret"; + final String baseString = "baseString"; + final String signature = "VRZvKmNIgbjfwPOoCPEvxFUV23v0BopcE6OE9P2Odz0="; + assertEquals(signature, service.getSignature(baseString, apiSecret, tokenSecret)); + } +} From 48a15ce90dc5b29c9b60e19202ce47abc986508e Mon Sep 17 00:00:00 2001 From: ScottVonDuhn Date: Sat, 17 Apr 2021 15:17:05 -0400 Subject: [PATCH 2/4] Added NetSuiteExample to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cfa482f90..73dde2ad6 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ ScribeJava support out-of-box several HTTP clients: * Microsoft Live (https://login.live.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/LiveExample.java) * Misfit (http://misfit.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/MisfitExample.java) * NAVER (http://www.naver.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NaverExample.java) +* NetSuite (https://www.netsuite.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java) * Odnoklassniki Одноклассники (http://ok.ru/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/OdnoklassnikiExample.java) * Polar (https://www.polar.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/PolarAPIExample.java) * Pinterest (https://www.pinterest.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/PinterestExample.java) From f1abf00f2e0e8c5deef74a54a338e26d39897044 Mon Sep 17 00:00:00 2001 From: ScottVonDuhn Date: Fri, 14 May 2021 12:57:45 -0400 Subject: [PATCH 3/4] Update NetSuiteExample to a POST request, including an example JSON request body --- .../apis/examples/NetSuiteExample.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java index a506ca1ee..3fd6318c4 100644 --- a/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java +++ b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java @@ -1,5 +1,6 @@ package com.github.scribejava.apis.examples; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.scribejava.apis.NetSuiteApi; import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.model.OAuth1AccessToken; @@ -10,9 +11,13 @@ import org.apache.http.client.utils.URIBuilder; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutionException; public class NetSuiteExample { + private static final ObjectMapper jsonMapper = new ObjectMapper(); + // Replace these with your own account id, consumer key, consumer secret, token id, token secret and restlet domain private static final String ACCOUNT_ID = "your_account_id"; private static final String CONSUMER_KEY = "your_consumer_key"; @@ -48,10 +53,19 @@ public static void main(String... args) throws IOException, InterruptedException // Now let's go and ask for a protected resource! System.out.println("Now we're going to access a protected resource..."); - final OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL); - request.addHeader("Content-Type", "application/json"); + final OAuthRequest request = new OAuthRequest(Verb.POST, PROTECTED_RESOURCE_URL); request.setRealm(ACCOUNT_ID); service.signRequest(accessToken, request); + request.addHeader("Content-Type", "application/json"); + + Map requestBody = new HashMap<>(); + requestBody.put("message", "your request body for POST and PUT"); + String jsonRequestBody = jsonMapper.writer().writeValueAsString(requestBody); + request.setPayload(jsonRequestBody); + + System.out.println("Request body to sent:"); + System.out.println(jsonRequestBody); + System.out.println(); try (Response response = service.execute(request)) { System.out.println("Got it! Let's see what we found..."); From b222f1619d6497af1bce6fa9c85300932dabc2f6 Mon Sep 17 00:00:00 2001 From: ScottVonDuhn Date: Fri, 14 May 2021 13:00:57 -0400 Subject: [PATCH 4/4] Updated NetSuiteExample --- .../github/scribejava/apis/examples/NetSuiteExample.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java index 3fd6318c4..537e69829 100644 --- a/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java +++ b/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/NetSuiteExample.java @@ -58,12 +58,12 @@ public static void main(String... args) throws IOException, InterruptedException service.signRequest(accessToken, request); request.addHeader("Content-Type", "application/json"); - Map requestBody = new HashMap<>(); - requestBody.put("message", "your request body for POST and PUT"); - String jsonRequestBody = jsonMapper.writer().writeValueAsString(requestBody); + Map requestMap = new HashMap<>(); + requestMap.put("message", "your request body for POST and PUT"); + String jsonRequestBody = jsonMapper.writer().writeValueAsString(requestMap); request.setPayload(jsonRequestBody); - System.out.println("Request body to sent:"); + System.out.println("Request body to send:"); System.out.println(jsonRequestBody); System.out.println();