25 | * Note: This might be replaced by utility method from commons-lang or guava someday
26 | * if one of those libraries is added as dependency.
27 | *
28 | *
29 | * @param array The array of strings
30 | * @param separator The separator
31 | * @return the resulting string
32 | */
33 | public static String join(String[] array, String separator) {
34 | int len = array.length;
35 | if (len == 0) return "";
36 |
37 | StringBuilder out = new StringBuilder();
38 | out.append(array[0]);
39 | for (int i = 1; i < len; i++) {
40 | out.append(separator).append(array[i]);
41 | }
42 | return out.toString();
43 | }
44 |
45 | /**
46 | * Check if the given value is null or an empty string
47 | *
48 | * @param value The value to check
49 | * @return true if the value is null or empty
50 | */
51 | public static boolean isEmpty(String value) {
52 | return value == null || value.isEmpty();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/test/Amazon.SellingPartnerAPIAATests/UtilsTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using Xunit;
5 | using Amazon.SellingPartnerAPIAA;
6 |
7 | namespace Amazon.SellingPartnerAPIAATests
8 | {
9 | public class UtilsTest
10 | {
11 | private const string TestString = "test";
12 |
13 | [Fact]
14 | public void TestUrlEncode_WithoutEncoding()
15 | {
16 | string result = Utils.UrlEncode("Test-_.~");
17 | Assert.Equal("Test-_.~", result);
18 | }
19 |
20 | [Fact]
21 | public void TestUrlEncode_WithEncoding()
22 | {
23 | string result = Utils.UrlEncode("Test$%*^");
24 | Assert.Equal("Test%24%25%2A%5E", result);
25 | }
26 |
27 | [Fact]
28 | public void TestUrlEncode_Empty()
29 | {
30 | Assert.Empty(Utils.UrlEncode(string.Empty));
31 | }
32 |
33 | [Fact]
34 | public void TestHash()
35 | {
36 | Assert.NotEmpty(Utils.Hash(TestString));
37 | }
38 |
39 | [Fact]
40 | public void TestToHex()
41 | {
42 | string result = Utils.ToHex(Encoding.UTF8.GetBytes(TestString));
43 | Assert.Equal("74657374", result);
44 | }
45 |
46 | [Fact]
47 | public void TestGetKeyedHash()
48 | {
49 | byte[] expectedHash = new byte[] { 106, 120, 238, 51, 86, 30, 87, 173, 232, 197, 95, 132,155,
50 | 183, 80, 81, 25, 213, 212, 241, 218, 201, 168, 17, 253, 143, 54, 226, 42, 118, 61, 54 };
51 | byte[] keyedHash = Utils.GetKeyedHash(Encoding.UTF8.GetBytes("testKey"), TestString);
52 | Assert.True(expectedHash.SequenceEqual(keyedHash));
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAAccessTokenRequestMetaBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace Amazon.SellingPartnerAPIAA
4 | {
5 | public class LWAAccessTokenRequestMetaBuilder
6 | {
7 | public const string SellerAPIGrantType = "refresh_token";
8 | public const string SellerlessAPIGrantType = "client_credentials";
9 |
10 | private const string Delimiter = " ";
11 |
12 | ///
13 | /// Builds an instance of LWAAccessTokenRequestMeta modeling appropriate LWA token
14 | /// request params based on configured LWAAuthorizationCredentials
15 | ///
16 | /// LWA Authorization Credentials
17 | ///
18 | public virtual LWAAccessTokenRequestMeta Build(LWAAuthorizationCredentials lwaAuthorizationCredentials)
19 | {
20 | LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta = new LWAAccessTokenRequestMeta()
21 | {
22 | ClientId = lwaAuthorizationCredentials.ClientId,
23 | ClientSecret = lwaAuthorizationCredentials.ClientSecret,
24 | RefreshToken = lwaAuthorizationCredentials.RefreshToken
25 | };
26 |
27 | if (lwaAuthorizationCredentials.Scopes == null || lwaAuthorizationCredentials.Scopes.Count == 0)
28 | {
29 | lwaAccessTokenRequestMeta.GrantType = SellerAPIGrantType;
30 | }
31 | else
32 | {
33 | lwaAccessTokenRequestMeta.Scope = string.Join(Delimiter, lwaAuthorizationCredentials.Scopes);
34 | lwaAccessTokenRequestMeta.GrantType = SellerlessAPIGrantType;
35 | }
36 |
37 | return lwaAccessTokenRequestMeta;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/exception/HttpResponseException.java:
--------------------------------------------------------------------------------
1 | package com.amazon.spapi.documents.exception;
2 |
3 | /**
4 | * The details of an HTTP response that indicates failure.
5 | */
6 | public class HttpResponseException extends Exception {
7 | private final String body;
8 | private final int code;
9 |
10 | /**
11 | * {@inheritDoc}
12 | *
13 | * @param message The {@link Exception} message
14 | * @param body The body
15 | * @param code The HTTP status code
16 | */
17 | public HttpResponseException(String message, String body, int code) {
18 | super(message);
19 | this.body = body;
20 | this.code = code;
21 | }
22 |
23 | /**
24 | * {@inheritDoc}
25 | *
26 | * @param message The {@link Exception} message
27 | * @param cause The {@link Exception} cause
28 | * @param body The body
29 | * @param code The HTTP status code
30 | */
31 | public HttpResponseException(String message, Throwable cause, String body, int code) {
32 | super(message, cause);
33 | this.body = body;
34 | this.code = code;
35 | }
36 |
37 | /**
38 | * The body. To ensure that a remote server cannot overwhelm heap memory, the body may have been truncated.
39 | *
40 | * @return The body
41 | */
42 | public String getBody() {
43 | return body;
44 | }
45 |
46 | /**
47 | * The HTTP status code
48 | *
49 | * @return The HTTP status code
50 | */
51 | public int getCode() {
52 | return code;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return super.toString() + " {code="
58 | + getCode()
59 | + ", body="
60 | + getBody()
61 | + '}';
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheImpl.java:
--------------------------------------------------------------------------------
1 | package com.amazon.SellingPartnerAPIAA;
2 |
3 | import java.util.concurrent.ConcurrentHashMap;
4 |
5 | public class LWAAccessTokenCacheImpl implements LWAAccessTokenCache {
6 | //in milliseconds; to avoid returning a token that would expire before or while a request is made
7 | private long expiryAdjustment = 60 * 1000;
8 | private static final long SECOND_TO_MILLIS = 1000;
9 | private ConcurrentHashMap
26 | *
27 | * A non-AMD browser application (discouraged) might do something like this:
28 | *
29 | * var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
30 | * var yyy = new {{moduleName}}.Yyy(); // Construct a model instance.
31 | * yyyModel.someProperty = 'someValue';
32 | * ...
33 | * var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
34 | * ...
35 | *
36 | *
37 | * @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index
38 | * @version {{projectVersion}}
39 | */{{/emitJSDoc}}
40 | export {
41 | {{=< >=}}
42 | <#emitJSDoc>/**
43 | * The ApiClient constructor.
44 | * @property {module:<#invokerPackage>/ApiClient}
45 | */
46 | ApiClient<#models>,
47 |
48 | <#emitJSDoc>/**
49 | * The model constructor.
50 | * @property {module:<#invokerPackage>/<#modelPackage>/}
51 | */
52 | <#apiInfo><#apis>,
53 |
54 | <#emitJSDoc>/**
55 | * The service constructor.
56 | * @property {module:<#invokerPackage>/<#apiPackage>/}
57 | */
58 |
59 | };<={{ }}=>
60 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/src/authandauth/RateLimitConfigurationOnRequests.php:
--------------------------------------------------------------------------------
1 | rateLimitType = $config["rateLimitType"] ?? "token_bucket";
35 | $this->rateLimitToken = $config["rateLimitToken"];
36 | $this->rateLimitTokenLimit = $config["rateLimitTokenLimit"];
37 |
38 | $this->waitTimeOutInMilliSeconds = $config["waitTimeOutInMilliSeconds"] ?? 0;
39 | if ($this->rateLimitType === "sliding_window" && $this->waitTimeOutInMilliSeconds != 0) {
40 | throw new InvalidArgumentException("Sliding Window RateLimiter cannot reserve tokens");
41 | }
42 | }
43 |
44 | public function getRateLimitType(): string
45 | {
46 | return $this->rateLimitType;
47 | }
48 |
49 | public function setRateLimitType(string $rateLimitType): RateLimitConfigurationOnRequests
50 | {
51 | $this->rateLimitType = $rateLimitType;
52 | return $this;
53 | }
54 |
55 | public function getRateLimitToken(): int
56 | {
57 | return $this->rateLimitToken;
58 | }
59 |
60 | public function setRateLimitToken(int $rateLimitToken): RateLimitConfigurationOnRequests
61 | {
62 | $this->rateLimitToken = $rateLimitToken;
63 | return $this;
64 | }
65 |
66 | public function getRateLimitTokenLimit(): int
67 | {
68 | return $this->rateLimitTokenLimit;
69 | }
70 |
71 | public function setRateLimitTokenLimit(int $rateLimitTokenLimit): RateLimitConfigurationOnRequests
72 | {
73 | $this->rateLimitTokenLimit = $rateLimitTokenLimit;
74 | return $this;
75 | }
76 |
77 | public function getTimeOut(): float
78 | {
79 | return $this->waitTimeOutInMilliSeconds;
80 | }
81 |
82 | public function setTimeOut(float $waitTimeOutInMilliSeconds): RateLimitConfigurationOnRequests
83 | {
84 | $this->waitTimeOutInMilliSeconds = $waitTimeOutInMilliSeconds;
85 | return $this;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/ModelInterface.mustache:
--------------------------------------------------------------------------------
1 | clientId = $config["clientId"];
45 | $this->clientSecret = $config["clientSecret"];
46 | $this->refreshToken = $config["refreshToken"] ?? null;
47 | $this->endpoint = $config["endpoint"];
48 | $this->scopes = $config["scopes"] ?? null;
49 | }
50 |
51 | public function getClientId(): string
52 | {
53 | return $this->clientId;
54 | }
55 |
56 | public function setClientId(string $clientId): LWAAuthorizationCredentials
57 | {
58 | $this->clientId = $clientId;
59 | return $this;
60 | }
61 |
62 | public function getClientSecret(): string
63 | {
64 | return $this->clientSecret;
65 | }
66 |
67 | public function setClientSecret(string $clientSecret): LWAAuthorizationCredentials
68 | {
69 | $this->clientSecret = $clientSecret;
70 | return $this;
71 | }
72 |
73 | public function getRefreshToken(): ?string
74 | {
75 | return $this->refreshToken;
76 | }
77 |
78 | public function setRefreshToken(?string $refreshToken): LWAAuthorizationCredentials
79 | {
80 | $this->refreshToken = $refreshToken;
81 | return $this;
82 | }
83 |
84 | public function getEndpoint(): string
85 | {
86 | return $this->endpoint;
87 | }
88 |
89 | public function setEndpoint(string $endpoint): LWAAuthorizationCredentials
90 | {
91 | $this->endpoint = $endpoint;
92 | return $this;
93 | }
94 |
95 | public function getScopes(): ?array
96 | {
97 | return $this->scopes;
98 | }
99 |
100 | public function setScopes(?array $scopes): LWAAuthorizationCredentials
101 | {
102 | $this->scopes = $scopes;
103 | return $this;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/Utils.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Security.Cryptography;
3 | using System.Globalization;
4 |
5 | namespace Amazon.SellingPartnerAPIAA
6 | {
7 | public static class Utils
8 | {
9 | public const string ValidUrlCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
10 |
11 | ///
12 | /// Returns URL encoded version of input data according to RFC-3986
13 | ///
14 | /// String to be URL-encoded
15 | /// URL encoded version of input data
16 | public static string UrlEncode(string data)
17 | {
18 | StringBuilder encoded = new StringBuilder();
19 | foreach (char symbol in Encoding.UTF8.GetBytes(data))
20 | {
21 | if (ValidUrlCharacters.IndexOf(symbol) != -1)
22 | {
23 | encoded.Append(symbol);
24 | }
25 | else
26 | {
27 | encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol));
28 | }
29 | }
30 | return encoded.ToString();
31 | }
32 |
33 | ///
34 | /// Returns hashed value of input data using SHA256
35 | ///
36 | /// String to be hashed
37 | /// Hashed value of input data
38 | public static byte[] Hash(string data)
39 | {
40 | return new SHA256CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(data));
41 | }
42 |
43 | ///
44 | /// Returns lowercase hexadecimal string of input byte array
45 | ///
46 | /// Data to be converted
47 | /// Lowercase hexadecimal string
48 | public static string ToHex(byte[] data)
49 | {
50 | StringBuilder sb = new StringBuilder();
51 |
52 | for (int i = 0; i < data.Length; i++)
53 | {
54 | sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
55 | }
56 |
57 | return sb.ToString();
58 | }
59 |
60 | ///
61 | /// Computes the hash of given string using the specified key with HMACSHA256
62 | ///
63 | /// Key
64 | /// String to be hashed
65 | /// Hashed value of input data
66 | public static byte[] GetKeyedHash(byte[] key, string value)
67 | {
68 | KeyedHashAlgorithm hashAlgorithm = new HMACSHA256(key);
69 | hashAlgorithm.Initialize();
70 | return hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializerTest.java:
--------------------------------------------------------------------------------
1 | package com.amazon.SellingPartnerAPIAA;
2 |
3 | import com.google.gson.Gson;
4 | import org.junit.Assert;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.Arguments;
8 | import org.junit.jupiter.params.provider.MethodSource;
9 |
10 | import java.util.Arrays;
11 | import java.util.HashSet;
12 | import java.util.Set;
13 | import java.util.stream.Stream;
14 |
15 | import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API;
16 | import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API;
17 |
18 | public class LWAClientScopesSerializerDeserializerTest {
19 | private static final String TEST_SCOPE_1 = SCOPE_NOTIFICATIONS_API;
20 | private static final String TEST_SCOPE_2 = SCOPE_MIGRATION_API;
21 |
22 | private static final Set scopesTestSellerless = new HashSet(Arrays.asList(TEST_SCOPE_1,
23 | TEST_SCOPE_2));
24 |
25 | private static final String SELLER_TYPE_SELLER = "seller";
26 | private static final String SELLER_TYPE_SELLERLESS = "sellerless";
27 |
28 | private Gson gson;
29 |
30 | @BeforeEach
31 | public void setup() {
32 | gson = new Gson();
33 | }
34 |
35 | public static Stream scopeSerialization(){
36 |
37 | return Stream.of(
38 | Arguments.of(SELLER_TYPE_SELLER, null),
39 | Arguments.of(SELLER_TYPE_SELLERLESS, new LWAClientScopes(scopesTestSellerless))
40 | );
41 | }
42 |
43 | public static Stream scopeDeserialization(){
44 |
45 | return Stream.of(
46 | Arguments.of(SELLER_TYPE_SELLER, null),
47 | Arguments.of(SELLER_TYPE_SELLERLESS, "{\"scope\":\"sellingpartnerapi::migration sellingpartnerapi::notifications\"}")
48 | );
49 | }
50 |
51 | @ParameterizedTest
52 | @MethodSource("scopeSerialization")
53 | public void testSerializeScope(String sellerType, LWAClientScopes testScope){
54 |
55 | String scopeJSON = gson.toJson(testScope);
56 |
57 | if (sellerType.equals(SELLER_TYPE_SELLER)) {
58 | Assert.assertEquals("null", scopeJSON);
59 | }
60 | else if (sellerType.equals(SELLER_TYPE_SELLERLESS)){
61 | Assert.assertTrue(!scopeJSON.isEmpty());
62 | }
63 | }
64 |
65 | @ParameterizedTest
66 | @MethodSource("scopeDeserialization")
67 | public void testDeserializeScope(String sellerType, String serializedValue){
68 |
69 | LWAClientScopes deserializedValue = gson.fromJson(serializedValue, LWAClientScopes.class);
70 | if (sellerType.equals(SELLER_TYPE_SELLER)) {
71 | Assert.assertNull(deserializedValue);
72 | }
73 | else if (sellerType.equals(SELLER_TYPE_SELLERLESS)){
74 | Assert.assertEquals(deserializedValue.getScopes(),scopesTestSellerless);
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSigner.java:
--------------------------------------------------------------------------------
1 | package com.amazon.SellingPartnerAPIAA;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import okhttp3.Request;
7 |
8 | /**
9 | * LWA Authorization Signer
10 | */
11 | public class LWAAuthorizationSigner {
12 | public static final String SIGNED_ACCESS_TOKEN_HEADER_NAME = "x-amz-access-token";
13 |
14 | @Getter(AccessLevel.PACKAGE)
15 | @Setter(AccessLevel.PACKAGE)
16 | private LWAClient lwaClient;
17 |
18 | private LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta;
19 |
20 | private void buildLWAAccessTokenRequestMeta(LWAAuthorizationCredentials lwaAuthorizationCredentials) {
21 | String tokenRequestGrantType;
22 | if (!lwaAuthorizationCredentials.getScopes().isEmpty()) {
23 | tokenRequestGrantType = "client_credentials";
24 | } else {
25 | tokenRequestGrantType = "refresh_token";
26 | }
27 |
28 | lwaAccessTokenRequestMeta = LWAAccessTokenRequestMeta.builder()
29 | .clientId(lwaAuthorizationCredentials.getClientId())
30 | .clientSecret(lwaAuthorizationCredentials.getClientSecret())
31 | .refreshToken(lwaAuthorizationCredentials.getRefreshToken())
32 | .grantType(tokenRequestGrantType).scopes(lwaAuthorizationCredentials.getScopes())
33 | .build();
34 | }
35 |
36 | /**
37 | *
38 | * @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange
39 | */
40 | public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials) {
41 |
42 | lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint());
43 |
44 | buildLWAAccessTokenRequestMeta(lwaAuthorizationCredentials);
45 |
46 | }
47 |
48 | /**
49 | *
50 | * Overloaded Constructor @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange
51 | * and LWAAccessTokenCache
52 | */
53 | public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials,
54 | LWAAccessTokenCache lwaAccessTokenCache) {
55 |
56 | lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint());
57 | lwaClient.setLWAAccessTokenCache(lwaAccessTokenCache);
58 |
59 | buildLWAAccessTokenRequestMeta(lwaAuthorizationCredentials);
60 |
61 | }
62 |
63 | /**
64 | * Signs a Request with an LWA Access Token
65 | * @param originalRequest Request to sign (treated as immutable)
66 | * @return Copy of originalRequest with LWA signature
67 | * @throws LWAException If calls to fetch LWA access token fails
68 | */
69 | public Request sign(Request originalRequest) throws LWAException {
70 | String accessToken = lwaClient.getAccessToken(lwaAccessTokenRequestMeta);
71 |
72 | return originalRequest.newBuilder()
73 | .addHeader(SIGNED_ACCESS_TOKEN_HEADER_NAME, accessToken)
74 | .build();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/impl/AESCryptoStreamFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.amazon.spapi.documents.impl;
2 |
3 | import com.amazon.spapi.documents.exception.CryptoException;
4 | import com.google.common.io.ByteStreams;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.io.ByteArrayInputStream;
8 | import java.io.InputStream;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.Arrays;
11 | import java.util.Base64;
12 |
13 | import static org.junit.jupiter.api.Assertions.*;
14 |
15 | class AESCryptoStreamFactoryTest {
16 | private static String KEY = "sxx/wImF6BFndqSAz56O6vfiAh8iD9P297DHfFgujec=";
17 | private static String VECTOR = "7S2tn363v0wfCfo1IX2Q1A==";
18 |
19 | @Test
20 | public void testBuilderConstructorMissingInitializationVector() {
21 | String key = "DEF";
22 | String initializationVector = null;
23 |
24 | assertThrows(IllegalArgumentException.class, () ->
25 | new AESCryptoStreamFactory.Builder(key, initializationVector));
26 | }
27 |
28 | @Test
29 | public void testBuilderConstructorMissingKey() {
30 | String key = null;
31 | String initializationVector = "ABC";
32 |
33 | assertThrows(IllegalArgumentException.class, () ->
34 | new AESCryptoStreamFactory.Builder(key, initializationVector));
35 | }
36 |
37 | @Test
38 | public void testBadKey() throws Exception {
39 | byte[] decodedKey = Base64.getDecoder().decode(KEY);
40 | String encodedKey = Base64.getEncoder().encodeToString(
41 | Arrays.copyOfRange(decodedKey, 2, decodedKey.length-1));
42 |
43 | AESCryptoStreamFactory aesCryptoStreamFactory = new AESCryptoStreamFactory.Builder(encodedKey, VECTOR).build();
44 | try (InputStream inputStream = new ByteArrayInputStream("Hello World!".getBytes(StandardCharsets.UTF_8))) {
45 | assertThrows(CryptoException.class, () -> aesCryptoStreamFactory.newDecryptStream(inputStream));
46 | }
47 | }
48 |
49 | @Test
50 | public void testEncryptDecrypt() throws Exception {
51 | String stringContent = "Hello World!";
52 | byte[] byteContent = stringContent.getBytes(StandardCharsets.UTF_8);
53 |
54 | AESCryptoStreamFactory aesCryptoStreamFactory = new AESCryptoStreamFactory.Builder(KEY, VECTOR).build();
55 |
56 | try (InputStream encryptStream =
57 | aesCryptoStreamFactory.newEncryptStream(new ByteArrayInputStream(byteContent))) {
58 | byte[] encryptedContent = ByteStreams.toByteArray(encryptStream);
59 |
60 | try (InputStream decryptStream =
61 | aesCryptoStreamFactory.newDecryptStream(new ByteArrayInputStream(encryptedContent))) {
62 | byte[] decryptedContent = ByteStreams.toByteArray(decryptStream);
63 |
64 | assertArrayEquals(decryptedContent, byteContent);
65 | assertFalse(Arrays.equals(encryptedContent, decryptedContent));
66 | }
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-javascript/src/generate-js-sdk.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | GITHUB_REPO="https://github.com/amzn/selling-partner-api-models.git"
3 | BASE_DIR=$(cd "$(dirname "$0")" && cd .. && pwd)
4 | CLONE_DIR="$BASE_DIR/models"
5 |
6 | # Get swagger jar file path from the input arguments
7 | swagger_jar_path=""
8 | while getopts 'j:' flag; do
9 | case "${flag}" in
10 | j) swagger_jar_path="${OPTARG}"
11 | ;;
12 | *) echo "available options are '-j'"
13 | exit 1
14 | ;;
15 | esac
16 | done
17 |
18 | if [ ! -f "$swagger_jar_path" ]; then
19 | echo "option '-j ' did not specify executable file. Aborted."
20 | exit 1
21 | fi
22 |
23 | ##########################################################################
24 | if [ -d "$CLONE_DIR" ]; then
25 | # prompt user, and if user gives "y", delete the directory.
26 | read -p "Found $CLONE_DIR already exists. Would you like to delete all the files under '$CLONE_DIR' and clone again? [y/n]: " confirm
27 | if [ "$confirm" == "y" ]; then
28 | # delete all the files in the directory
29 | rm -rf "${CLONE_DIR:?}/*"
30 | echo "Deleted files in $CLONE_DIR directory. Recreating $CLONE_DIR again."
31 | git clone "$GITHUB_REPO" "$CLONE_DIR"
32 | else
33 | echo "OK. Proceeding with the existing ${CLONE_DIR} without cloning."
34 | fi
35 | else
36 | midir "$CLONE_DIR"
37 | git clone "$GITHUB_REPO" "$CLONE_DIR"
38 | fi
39 |
40 | ##########################################################################
41 | TEMPLATE_DIR="$BASE_DIR/src/resources/swagger-codegen/templates"
42 | MODEL_DIR="$CLONE_DIR/models"
43 | SDK_DIR="$BASE_DIR/sdk"
44 | CODEGEN_CONFIG_PATH="$BASE_DIR/src/js.config.json"
45 |
46 | MODELS=("${MODEL_DIR}"/*/*)
47 | basePackage="js-client"
48 |
49 | if [ -d "$SDK_DIR" ]; then
50 | rm -rf "${SDK_DIR:?}/*"
51 | fi
52 |
53 | if [ ! -d "$SDK_DIR" ]; then
54 | mkdir "$SDK_DIR"
55 | fi
56 |
57 | function get_model_name () {
58 | swaggerFile="$1"
59 | modelNameWithExtension="${swaggerFile##*/}"
60 | echo "${modelNameWithExtension%.*}"
61 | }
62 |
63 | for model in "${MODELS[@]}"
64 | do
65 | modelName=$(get_model_name "$model")
66 | echo "model = $model"
67 | echo "model name = $modelName"
68 | java -jar "${swagger_jar_path}" generate \
69 | --config "${CODEGEN_CONFIG_PATH}" \
70 | --input-spec "${model}" \
71 | --lang javascript \
72 | --template-dir "${TEMPLATE_DIR}" \
73 | --output "${SDK_DIR}" \
74 | --invoker-package "${modelName}" \
75 | --api-package "${basePackage}.${modelName}.api" \
76 | --model-package "${basePackage}.${modelName}.model" \
77 | --group-id "com.amazon" \
78 | --artifact-id "sp-api-javascript-client"
79 | done
80 |
81 | echo "***********************************************************"
82 | echo "SP-API SDK is created under ${SDK_DIR} and SDK source code "
83 | echo "should be found ${SDK_DIR}/src/."
84 | echo "Please copy the SDK source DIRECTORIES (such as \"catalogItems_2022-04-01\")"
85 | echo "you want to use to the directory \"code/javascript/src/jsdsdk.\"."
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/src/authandauth/LWAAccessTokenRequestMeta.php:
--------------------------------------------------------------------------------
1 | refreshToken = $lwaAuthorizationCredentials->getRefreshToken();
28 | $this->clientId = $lwaAuthorizationCredentials->getClientId();
29 | $this->clientSecret = $lwaAuthorizationCredentials->getClientSecret();
30 | $this->scopes = $lwaAuthorizationCredentials->getScopes();
31 |
32 | if (!empty($lwaAuthorizationCredentials->getScopes())) {
33 | $this->grantType = "client_credentials";
34 | } else {
35 | $this->grantType = "refresh_token";
36 | }
37 | }
38 |
39 | public function jsonSerialize(): array
40 | {
41 | return [
42 | static::GRANT_TYPE_SERIALIZED => $this->grantType,
43 | static::CLIENT_ID_SERIALIZED => $this->clientId,
44 | static::CLIENT_SECRET_SERIALIZED => $this->clientSecret,
45 | static::REFRESH_TOKEN_SERIALIZED => $this->refreshToken,
46 | static::SCOPE_SERIALIZED => $this->scopes ? implode(" ", $this->scopes) : null
47 | ];
48 | }
49 |
50 | public function getGrantType(): string
51 | {
52 | return $this->grantType;
53 | }
54 |
55 | public function setGrantType(string $grantType)
56 | {
57 | $this->grantType = $grantType;
58 | }
59 |
60 | public function getRefreshToken(): ?string
61 | {
62 | return $this->refreshToken;
63 | }
64 |
65 | public function setRefreshToken(string $refreshToken)
66 | {
67 | $this->refreshToken = $refreshToken;
68 | }
69 |
70 | public function getClientId(): string
71 | {
72 | return $this->clientId;
73 | }
74 |
75 | public function setClientId(string $clientId)
76 | {
77 | $this->clientId = $clientId;
78 | }
79 |
80 | public function getClientSecret(): string
81 | {
82 | return $this->clientSecret;
83 | }
84 |
85 | public function setClientSecret(string $clientSecret)
86 | {
87 | $this->clientSecret = $clientSecret;
88 | }
89 |
90 | public function getScopes(): ?array
91 | {
92 | return $this->scopes;
93 | }
94 |
95 | public function setScopes(?array $scopes)
96 | {
97 | $this->scopes = $scopes;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/test/Amazon.SellingPartnerAPIAATests/LWAAccessTokenRequestMetaBuilderTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Amazon.SellingPartnerAPIAA;
4 | using Xunit;
5 |
6 | namespace Amazon.SellingPartnerAPIAATests
7 | {
8 | public class LWAAccessTokenRequestMetaBuilderTest
9 | {
10 | private const string TestClientId = "cid";
11 | private const string TestClientSecret = "csecret";
12 | private const string TestRefreshToken = "rtoken";
13 | private static readonly Uri TestUri = new Uri("https://www.amazon.com");
14 | private LWAAccessTokenRequestMetaBuilder lwaAccessTokenRequestMetaBuilderUnderTest;
15 |
16 | public LWAAccessTokenRequestMetaBuilderTest()
17 | {
18 | lwaAccessTokenRequestMetaBuilderUnderTest = new LWAAccessTokenRequestMetaBuilder();
19 | }
20 |
21 | [Fact]
22 | public void LWAAuthorizationCredentialsWithoutScopesBuildsSellerTokenRequestMeta()
23 | {
24 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials()
25 | {
26 | ClientId = TestClientId,
27 | ClientSecret = TestClientSecret,
28 | Endpoint = TestUri,
29 | RefreshToken = TestRefreshToken
30 | };
31 |
32 | LWAAccessTokenRequestMeta expected = new LWAAccessTokenRequestMeta()
33 | {
34 | ClientId = TestClientId,
35 | ClientSecret = TestClientSecret,
36 | GrantType = LWAAccessTokenRequestMetaBuilder.SellerAPIGrantType,
37 | RefreshToken = TestRefreshToken,
38 | Scope = null
39 | };
40 |
41 | LWAAccessTokenRequestMeta actual = lwaAccessTokenRequestMetaBuilderUnderTest.Build(lwaAuthorizationCredentials);
42 |
43 | Assert.Equal(expected, actual);
44 | }
45 |
46 | [Fact]
47 | public void LWAAuthorizationCredentialsWithScopesBuildsSellerlessTokenRequestMeta()
48 | {
49 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials()
50 | {
51 | ClientId = TestClientId,
52 | ClientSecret = TestClientSecret,
53 | Endpoint = TestUri,
54 | Scopes = new List() { ScopeConstants.ScopeMigrationAPI, ScopeConstants.ScopeNotificationsAPI }
55 | };
56 |
57 | LWAAccessTokenRequestMeta expected = new LWAAccessTokenRequestMeta()
58 | {
59 | ClientId = TestClientId,
60 | ClientSecret = TestClientSecret,
61 | GrantType = LWAAccessTokenRequestMetaBuilder.SellerlessAPIGrantType,
62 | Scope = string.Format("{0} {1}", ScopeConstants.ScopeMigrationAPI, ScopeConstants.ScopeNotificationsAPI),
63 | RefreshToken = null
64 | };
65 |
66 | LWAAccessTokenRequestMeta actual = lwaAccessTokenRequestMetaBuilderUnderTest.Build(lwaAuthorizationCredentials);
67 |
68 | Assert.Equal(expected, actual);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/src/authandauth/RestrictedDataTokenSigner.php:
--------------------------------------------------------------------------------
1 | withHeader(LWAAuthorizationSigner::SIGNED_ACCESS_TOKEN_HEADER_NAME, $restrictedDataToken);
28 | }
29 |
30 | /**
31 | * Check if an operation requires RDT access
32 | *
33 | * @param string $operationName The operation name in format 'ClassName-operationId'
34 | * @return bool True if the operation requires RDT, false otherwise
35 | */
36 | public static function isRestrictedOperation(string $operationName): bool
37 | {
38 | // List of operations that require RDT access
39 | $restrictedOperations = [
40 | // Direct Fulfillment Orders API
41 | 'VendorOrdersApi-getOrders',
42 | 'VendorOrdersApi-getOrder',
43 |
44 | // Direct Fulfillment Shipping API
45 | 'VendorShippingLabelsApi-getShippingLabel',
46 | 'VendorShippingLabelsApi-getShippingLabels',
47 | 'VendorShippingApi-getPackingSlips',
48 | 'VendorShippingApi-getPackingSlip',
49 | 'VendorShippingLabelsApi-getCustomerInvoice',
50 | 'VendorShippingLabelsApi-getCustomerInvoices',
51 | 'VendorShippingLabelsApi-createShippingLabels',
52 |
53 | // Easy Ship API v2022-03-23
54 | 'EasyShipApi-createScheduledPackageBulk',
55 |
56 | // Orders API
57 | 'OrdersV0Api-getOrders',
58 | 'OrdersV0Api-getOrder',
59 | 'OrdersV0Api-getOrderBuyerInfo',
60 | 'OrdersV0Api-getOrderAddress',
61 | 'OrdersV0Api-getOrderItems',
62 | 'OrdersV0Api-getOrderItemsBuyerInfo',
63 | 'OrdersV0Api-getOrderRegulatedInfo',
64 |
65 | // Merchant Fulfillment API
66 | 'MerchantFulfillmentApi-getShipment',
67 | 'MerchantFulfillmentApi-cancelShipment',
68 | 'MerchantFulfillmentApi-createShipment',
69 |
70 | // Shipment Invoicing
71 | 'ShipmentInvoiceApi-getShipmentDetails',
72 |
73 | // Reports API
74 | 'ReportsApi-getReportDocument'
75 | ];
76 |
77 | return in_array($operationName, $restrictedOperations);
78 | }
79 | }
80 |
81 |
82 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/UploadSpecificationTest.java:
--------------------------------------------------------------------------------
1 | package com.amazon.spapi.documents;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.mockito.Mockito;
5 |
6 | import java.io.ByteArrayInputStream;
7 | import java.io.InputStream;
8 |
9 | import static org.junit.jupiter.api.Assertions.*;
10 |
11 | class UploadSpecificationTest {
12 | @Test
13 | public void testBuilderConstructorMissingContentType() throws Exception {
14 | String contentType = null;
15 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class);
16 | InputStream source = new ByteArrayInputStream(new byte[0]);
17 | String url = "https://www.amazon.com";
18 |
19 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder(
20 | contentType, cryptoStreamFactory, source, url));
21 | }
22 |
23 | @Test
24 | public void testBuilderConstructorMissingCryptoStreamFactory() throws Exception {
25 | String contentType = "text/xml; charset=UTF-8";
26 | CryptoStreamFactory cryptoStreamFactory = null;
27 | InputStream source = new ByteArrayInputStream(new byte[0]);
28 | String url = "https://www.amazon.com";
29 |
30 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder(
31 | contentType, cryptoStreamFactory, source, url));
32 | }
33 |
34 | @Test
35 | public void testBuilderConstructorMissingSource() throws Exception {
36 | String contentType = "text/xml; charset=UTF-8";
37 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class);
38 | InputStream source = null;
39 | String url = "https://www.amazon.com";
40 |
41 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder(
42 | contentType, cryptoStreamFactory, source, url));
43 | }
44 |
45 | @Test
46 | public void testBuilderConstructorMissingUrl() throws Exception {
47 | String contentType = "text/xml; charset=UTF-8";
48 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class);
49 | InputStream source = new ByteArrayInputStream(new byte[0]);
50 | String url = null;
51 |
52 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder(
53 | contentType, cryptoStreamFactory, source, url));
54 | }
55 |
56 | @Test
57 | public void testSuccess() throws Exception {
58 | String contentType = "text/xml; charset=UTF-8";
59 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class);
60 | InputStream source = new ByteArrayInputStream(new byte[0]);
61 | String url = "http://abc.com/123";
62 |
63 | UploadSpecification spec = new UploadSpecification.Builder(contentType, cryptoStreamFactory, source, url)
64 | .build();
65 |
66 | assertEquals(contentType, spec.getContentType());
67 | assertSame(cryptoStreamFactory, spec.getCryptoStreamFactory());
68 | assertSame(source, spec.getSource());
69 | assertEquals(url, spec.getUrl());
70 | }
71 | }
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/README.md:
--------------------------------------------------------------------------------
1 | # Selling Partner API Authentication/Authorization Library
2 | This library provides helper classes for use when signing HTTP requests for Amazon Selling Partner APIs. It is intended for use
3 | with the Selling Partner API Client Libraries generated by [openapi generator](https://openapi-generator.tech/)
4 | using the Guzzlehttp library. It can also be integrated into custom projects.
5 |
6 | ## LWAAuthorizationSigner
7 | Obtains and signs a request with an access token from LWA (Login with Amazon) for the specified endpoint using the provided LWA credentials.
8 |
9 | *Example*
10 | ```
11 | $lwaAuthorizationCredentials = new LWAAuthorizationCredentials([
12 | "clientId" => '.....',
13 | "clientSecret" => '.....',
14 | "refreshToken" => '.....',
15 | "endpoint" => 'https://api.amazon.com/auth/o2/token'
16 | ]);
17 |
18 | // Initialize LWAAuthorizationSigner instance
19 | $lwaAuthorizationSigner = new LWAAuthorizationSigner($lwaAuthorizationCredentials);
20 | $config = new Configuration([], $lwaAuthorizationCredentials);
21 |
22 | // Setting SP-API endpoint region. Change it according to the desired region
23 | $config->setHost('https://sellingpartnerapi-na.amazon.com');
24 |
25 | // Create a new HTTP client
26 | $client = new GuzzleHttp\Client();
27 |
28 | // Create an instance of the Orders Api client
29 | $api = new OrdersApi($config, null, $client);
30 | ```
31 |
32 | ## LWAAccessTokenCache
33 | Implements cache for access token that is returned in LWAClient and reuses the access token until time to live.
34 |
35 | ## RateLimitConfiguration
36 | Interface to set and get rateLimit configurations that are used with RateLimiter. RateLimiter is used on client side to restrict the rate at which requests are made. RateLimitConfiguration takes a Permit, the rate at which requests are made, and TimeOut.
37 |
38 | *Example*
39 | ```
40 | $rateLimitOption = new RateLimitConfigurationOnRequests([
41 | "rateLimitToken" => "...",
42 | "rateLimitTokenLimit" => "...",
43 | "waitTimeOutInMilliSeconds" => "..."
44 | ]);
45 | ```
46 |
47 |
48 | ## Resources
49 | This package features Mustache templates designed for use with [openapi generator](https://openapi-generator.tech/).
50 | When you build Selling Partner API OpenAPI models with these templates, they help generate a rich SDK with functionality to invoke Selling Partner APIs built in. The templates are located in *resources/openapi-generator*.
51 |
52 | Dependencies are declared in the composer.json file.
53 |
54 | ## License
55 | OpenAPI Generator templates are subject to the [OpenAPI Generator License](https://github.com/OpenAPITools/openapi-generator/blob/v5.2.1/LICENSE).
56 |
57 | All other work licensed as follows:
58 |
59 | Copyright 2019 Amazon.com, Inc
60 |
61 | Licensed under the Apache License, Version 2.0 (the "License");
62 | you may not use this library except in compliance with the License.
63 | You may obtain a copy of the License at
64 |
65 | http://www.apache.org/licenses/LICENSE-2.0
66 |
67 | Unless required by applicable law or agreed to in writing, software
68 | distributed under the License is distributed on an "AS IS" BASIS,
69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
70 | See the License for the specific language governing permissions and
71 | limitations under the License.
72 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/resources/swagger-codegen/templates/IReadableConfiguration.mustache:
--------------------------------------------------------------------------------
1 | {{>partial_header}}
2 |
3 | using System.Collections.Generic;
4 | using Amazon.SellingPartnerAPIAA;
5 |
6 | namespace {{packageName}}.Client
7 | {
8 | ///
9 | /// Represents a readable-only configuration contract.
10 | ///
11 | public interface IReadableConfiguration
12 | {
13 | ///
14 | /// Gets the access token.
15 | ///
16 | /// Access token.
17 | string AccessToken { get; }
18 |
19 | ///
20 | /// Gets the API key.
21 | ///
22 | /// API key.
23 | IDictionary ApiKey { get; }
24 |
25 | ///
26 | /// Gets the API key prefix.
27 | ///
28 | /// API key prefix.
29 | IDictionary ApiKeyPrefix { get; }
30 |
31 | ///
32 | /// Gets the base path.
33 | ///
34 | /// Base path.
35 | string BasePath { get; }
36 |
37 | ///
38 | /// Gets the date time format.
39 | ///
40 | /// Date time foramt.
41 | string DateTimeFormat { get; }
42 |
43 | ///
44 | /// Gets the default header.
45 | ///
46 | /// Default header.
47 | IDictionary DefaultHeader { get; }
48 |
49 | ///
50 | /// Gets the temp folder path.
51 | ///
52 | /// Temp folder path.
53 | string TempFolderPath { get; }
54 |
55 | ///
56 | /// Gets the HTTP connection timeout (in milliseconds)
57 | ///
58 | /// HTTP connection timeout.
59 | int Timeout { get; }
60 |
61 | ///
62 | /// Gets the user agent.
63 | ///
64 | /// User agent.
65 | string UserAgent { get; }
66 |
67 | ///
68 | /// Gets the username.
69 | ///
70 | /// Username.
71 | string Username { get; }
72 |
73 | ///
74 | /// Gets the password.
75 | ///
76 | /// Password.
77 | string Password { get; }
78 |
79 | ///
80 | /// Gets the API key with prefix.
81 | ///
82 | /// API key identifier (authentication scheme).
83 | /// API key with prefix.
84 | string GetApiKeyWithPrefix(string apiKeyIdentifier);
85 |
86 | ///
87 | /// Gets the LWAAuthorizationCredentials for Amazon Selling Partner API Authorization
88 | ///
89 | /// AuthorizationCredentials
90 | LWAAuthorizationCredentials AuthorizationCredentials { get; }
91 |
92 | ///
93 | /// Gets the RateLimitConfigurationOnRequests for Amazon Selling Partner API RateLimit
94 | ///
95 | /// RateLimitConfiguration
96 | RateLimitConfiguration RateLimitConfig { get; }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/RestrictedDataTokenSigner.java:
--------------------------------------------------------------------------------
1 | package com.amazon.SellingPartnerAPIAA;
2 |
3 | import okhttp3.Request;
4 | import java.util.Arrays;
5 | import java.util.List;
6 |
7 | public class RestrictedDataTokenSigner {
8 |
9 | /**
10 | * Sign the request with a Restricted Data Token
11 | *
12 | * @param request Request to sign
13 | * @param restrictedDataToken The Restricted Data Token
14 | * @param operationName The operation name in format 'ClassName-operationId'
15 | * @return Signed request
16 | * @throws IllegalArgumentException If RDT is used for a non-restricted operation
17 | */
18 | public static Request sign(Request request, String restrictedDataToken, String operationName) {
19 | boolean isRestricted = isRestrictedOperation(operationName);
20 | if (!isRestricted) {
21 | throw new IllegalArgumentException(
22 | "Operation '" + operationName + "' does not require a Restricted Data Token (RDT). " +
23 | "Remove the RDT parameter for non-restricted operations."
24 | );
25 | }
26 |
27 | return request.newBuilder()
28 | .header(LWAAuthorizationSigner.SIGNED_ACCESS_TOKEN_HEADER_NAME, restrictedDataToken)
29 | .build();
30 | }
31 |
32 | /**
33 | * Check if an operation requires RDT access
34 | *
35 | * @param operationName The operation name in format 'ClassName-operationId'
36 | * @return True if the operation requires RDT, false otherwise
37 | */
38 | private static boolean isRestrictedOperation(String operationName) {
39 | List restrictedOperations = Arrays.asList(
40 | // Direct Fulfillment Orders API
41 | "VendorOrdersApi-getOrders",
42 | "VendorOrdersApi-getOrder",
43 |
44 | // Direct Fulfillment Shipping API
45 | "VendorShippingLabelsApi-getShippingLabel",
46 | "VendorShippingLabelsApi-getShippingLabels",
47 | "VendorShippingApi-getPackingSlips",
48 | "VendorShippingApi-getPackingSlip",
49 | "VendorShippingLabelsApi-getCustomerInvoice",
50 | "VendorShippingLabelsApi-getCustomerInvoices",
51 | "VendorShippingLabelsApi-createShippingLabels",
52 |
53 | // Easy Ship API v2022-03-23
54 | "EasyShipApi-createScheduledPackageBulk",
55 |
56 | // Orders API
57 | "OrdersV0Api-getOrders",
58 | "OrdersV0Api-getOrder",
59 | "OrdersV0Api-getOrderBuyerInfo",
60 | "OrdersV0Api-getOrderAddress",
61 | "OrdersV0Api-getOrderItems",
62 | "OrdersV0Api-getOrderItemsBuyerInfo",
63 | "OrdersV0Api-getOrderRegulatedInfo",
64 |
65 | // Merchant Fulfillment API
66 | "MerchantFulfillmentApi-getShipment",
67 | "MerchantFulfillmentApi-cancelShipment",
68 | "MerchantFulfillmentApi-createShipment",
69 |
70 | // Shipment Invoicing
71 | "ShipmentInvoiceApi-getShipmentDetails",
72 |
73 | // Reports API
74 | "ReportsApi-getReportDocument"
75 | );
76 |
77 | return restrictedOperations.contains(operationName);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *master* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
62 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/ApiException.mustache:
--------------------------------------------------------------------------------
1 | responseHeaders = $responseHeaders;
73 | $this->responseBody = $responseBody;
74 | }
75 |
76 | /**
77 | * Gets the HTTP response header
78 | *
79 | * @return string[][]|null HTTP response header
80 | */
81 | public function getResponseHeaders(): ?array
82 | {
83 | return $this->responseHeaders;
84 | }
85 |
86 | /**
87 | * Gets the HTTP body of the server response either as Json or string
88 | *
89 | * @return stdClass|string|null HTTP body of the server response either as \stdClass or string
90 | */
91 | public function getResponseBody(): string|stdClass|null
92 | {
93 | return $this->responseBody;
94 | }
95 |
96 | /**
97 | * Sets the deserialized response object (during deserialization)
98 | *
99 | * @param mixed $obj Deserialized response object
100 | *
101 | * @return void
102 | */
103 | public function setResponseObject(mixed $obj): void
104 | {
105 | $this->responseObject = $obj;
106 | }
107 |
108 | /**
109 | * Gets the deserialized response object (during deserialization)
110 | *
111 | * @return stdClass|string|null the deserialized response object
112 | */
113 | public function getResponseObject(): stdClass|string|null
114 | {
115 | return $this->responseObject;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.amazon.sellingpartnerapi
6 | sellingpartner-api-documents-helper-java
7 | 1.0.0
8 |
9 |
10 | com.squareup.okhttp
11 | okhttp
12 | 2.7.5
13 |
14 |
15 | com.squareup.okhttp
16 | logging-interceptor
17 | 2.7.5
18 |
19 |
20 | io.gsonfire
21 | gson-fire
22 | 1.8.0
23 |
24 |
25 | org.apache.directory.studio
26 | org.apache.commons.io
27 | 2.4
28 |
29 |
30 | com.google.guava
31 | guava
32 | 30.0-jre
33 |
34 |
35 | org.threeten
36 | threetenbp
37 | 1.3.5
38 |
39 |
40 |
41 | org.junit.platform
42 | junit-platform-commons
43 | 1.7.0
44 |
45 |
46 | org.junit.jupiter
47 | junit-jupiter-engine
48 | 5.0.0
49 |
50 |
51 | org.junit.jupiter
52 | junit-jupiter-params
53 | 5.3.2
54 |
55 |
56 | org.junit.jupiter
57 | junit-jupiter-migrationsupport
58 | 5.5.1
59 |
60 |
61 | org.mockito
62 | mockito-core
63 | 3.0.0
64 |
65 |
66 | org.mockito
67 | mockito-inline
68 | 3.0.0
69 |
70 |
71 |
72 |
73 |
74 | maven-surefire-plugin
75 | 2.22.2
76 |
77 |
78 | maven-failsafe-plugin
79 | 2.22.2
80 |
81 |
82 |
83 |
84 |
85 | org.apache.maven.plugins
86 | maven-compiler-plugin
87 | 3.7.0
88 |
89 |
90 |
91 |
92 |
93 | 1.8
94 | ${java.version}
95 | ${java.version}
96 | 1.0.0
97 | UTF-8
98 |
99 |
100 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/impl/AESCryptoStreamFactory.java:
--------------------------------------------------------------------------------
1 | package com.amazon.spapi.documents.impl;
2 |
3 | import com.amazon.spapi.documents.CryptoStreamFactory;
4 | import com.amazon.spapi.documents.exception.CryptoException;
5 | import com.google.common.base.Preconditions;
6 |
7 | import javax.crypto.Cipher;
8 | import javax.crypto.CipherInputStream;
9 | import javax.crypto.NoSuchPaddingException;
10 | import javax.crypto.spec.IvParameterSpec;
11 | import javax.crypto.spec.SecretKeySpec;
12 | import java.io.InputStream;
13 | import java.security.InvalidAlgorithmParameterException;
14 | import java.security.InvalidKeyException;
15 | import java.security.Key;
16 | import java.security.NoSuchAlgorithmException;
17 | import java.util.Base64;
18 |
19 | /**
20 | * A crypto stream factory implementing AES encryption.
21 | */
22 | public class AESCryptoStreamFactory implements CryptoStreamFactory {
23 | private static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding";
24 |
25 | private final Key key;
26 | private final byte[] initializationVector;
27 |
28 | private AESCryptoStreamFactory(Key key, byte[] initializationVector) {
29 | this.key = key;
30 | this.initializationVector = initializationVector;
31 | }
32 |
33 | private Cipher createInitializedCipher(int mode) throws CryptoException {
34 | try {
35 | Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
36 | cipher.init(mode, key, new IvParameterSpec(initializationVector));
37 | return cipher;
38 | } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException |
39 | NoSuchPaddingException e) {
40 | throw new CryptoException(e);
41 | }
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | @Override
48 | public InputStream newDecryptStream(InputStream source) throws CryptoException {
49 | return new CipherInputStream(source, createInitializedCipher(Cipher.DECRYPT_MODE));
50 | }
51 |
52 | /**
53 | * {@inheritDoc}
54 | */
55 | @Override
56 | public InputStream newEncryptStream(InputStream source) throws CryptoException {
57 | return new CipherInputStream(source, createInitializedCipher(Cipher.ENCRYPT_MODE));
58 | }
59 |
60 | /**
61 | * Use this to create an instance of an {@link AESCryptoStreamFactory}.
62 | */
63 | public static class Builder {
64 | private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();
65 | private static final String REQUIRED_KEY_ALGORITHM = "AES";
66 |
67 | private final String key;
68 | private final String initializationVector;
69 |
70 | /**
71 | * Create the builder.
72 | *
73 | * @param key The key
74 | * @param initializationVector The initialization vector
75 | */
76 | public Builder(String key, String initializationVector) {
77 | Preconditions.checkArgument(key != null, "key is required");
78 | Preconditions.checkArgument(initializationVector != null, "initializationVector is required");
79 |
80 | this.key = key;
81 | this.initializationVector = initializationVector;
82 | }
83 |
84 | /**
85 | * Create the crypto stream factory.
86 | *
87 | * @return the crypto stream factory
88 | */
89 | public AESCryptoStreamFactory build() {
90 | Key convertedKey = new SecretKeySpec(BASE64_DECODER.decode(key), REQUIRED_KEY_ALGORITHM);
91 | byte[] convertedInitializationVector = BASE64_DECODER.decode(initializationVector);
92 |
93 | return new AESCryptoStreamFactory(convertedKey, convertedInitializationVector);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-php/src/authandauth/LWAClient.php:
--------------------------------------------------------------------------------
1 | client = new Client();
23 | $this->endpoint = $endpoint;
24 | }
25 |
26 | public function setLWAAccessTokenCache(?LWAAccessTokenCache $tokenCache): void
27 | {
28 | $this->lwaAccessTokenCache = $tokenCache;
29 | }
30 |
31 | public function getAccessToken(LWAAccessTokenRequestMeta &$lwaAccessTokenRequestMeta): string
32 | {
33 | if ($this->lwaAccessTokenCache !== null) {
34 | return $this->getAccessTokenFromCache($lwaAccessTokenRequestMeta);
35 | } else {
36 | return $this->getAccessTokenFromEndpoint($lwaAccessTokenRequestMeta);
37 | }
38 | }
39 |
40 | public function getAccessTokenFromCache(LWAAccessTokenRequestMeta &$lwaAccessTokenRequestMeta)
41 | {
42 | $requestBody = json_encode($lwaAccessTokenRequestMeta);
43 | if (!$requestBody) {
44 | throw new RuntimeException("Request body could not be encoded");
45 | }
46 | $accessTokenCacheData = $this->lwaAccessTokenCache->get($requestBody);
47 | if ($accessTokenCacheData !== null) {
48 | return $accessTokenCacheData;
49 | } else {
50 | return $this->getAccessTokenFromEndpoint($lwaAccessTokenRequestMeta);
51 | }
52 | }
53 |
54 | public function getAccessTokenFromEndpoint(LWAAccessTokenRequestMeta &$lwaAccessTokenRequestMeta)
55 | {
56 | $requestBody = json_encode($lwaAccessTokenRequestMeta);
57 |
58 | if (!$requestBody) {
59 | throw new RuntimeException("Request body could not be encoded");
60 | }
61 |
62 | $contentHeader = [
63 | "Content-Type" => "application/json",
64 | ];
65 |
66 | try {
67 | $lwaRequest = new Request("POST", $this->endpoint, $contentHeader, $requestBody);
68 |
69 | $lwaResponse = $this->client->send($lwaRequest);
70 | $responseJson = json_decode($lwaResponse->getBody(), true);
71 |
72 | if (!$responseJson["access_token"] || !$responseJson["expires_in"]) {
73 | throw new RuntimeException("Response did not have required body");
74 | }
75 |
76 | $accessToken = $responseJson["access_token"];
77 |
78 | if ($this->lwaAccessTokenCache !== null) {
79 | $timeToTokenExpire = (float)$responseJson["expires_in"];
80 | $this->lwaAccessTokenCache->set($requestBody, $accessToken, $timeToTokenExpire);
81 | }
82 | } catch (BadResponseException $e) {
83 | //Catches 400 and 500 level error codes
84 | throw new RuntimeException("Unsuccessful LWA token exchange", $e->getCode());
85 | } catch (Exception $e) {
86 | throw new RuntimeException("Error getting LWA Access Token", $e->getCode());
87 | } catch (GuzzleException $e) {
88 | throw new RuntimeException("Error getting LWA Access Token", $e->getCode());
89 | }
90 |
91 | return $accessToken;
92 | }
93 |
94 | public function setClient(Client $client): void
95 | {
96 | $this->client = $client;
97 | }
98 |
99 | public function getEndpoint(): string
100 | {
101 | return $this->endpoint;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Linq;
6 | using RestSharp;
7 |
8 | namespace Amazon.SellingPartnerAPIAA
9 | {
10 | public class LWAClient
11 | {
12 | public const string AccessTokenKey = "access_token";
13 | public const string ErrorCodeKey = "error";
14 | public const string ErrorDescKey = "error_description";
15 | public const string JsonMediaType = "application/json; charset=utf-8";
16 |
17 | public IRestClient RestClient { get; set; }
18 | public LWAAccessTokenRequestMetaBuilder LWAAccessTokenRequestMetaBuilder { get; set; }
19 | public LWAAuthorizationCredentials LWAAuthorizationCredentials { get; private set; }
20 |
21 |
22 | public LWAClient(LWAAuthorizationCredentials lwaAuthorizationCredentials)
23 | {
24 | LWAAuthorizationCredentials = lwaAuthorizationCredentials;
25 | LWAAccessTokenRequestMetaBuilder = new LWAAccessTokenRequestMetaBuilder();
26 | RestClient = new RestClient(LWAAuthorizationCredentials.Endpoint.GetLeftPart(System.UriPartial.Authority));
27 | }
28 |
29 | ///
30 | /// Retrieves access token from LWA
31 | ///
32 | /// LWA AccessTokenRequest metadata
33 | /// LWA Access Token
34 | public virtual string GetAccessToken()
35 | {
36 | LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta = LWAAccessTokenRequestMetaBuilder.Build(LWAAuthorizationCredentials);
37 | var accessTokenRequest = new RestRequest(LWAAuthorizationCredentials.Endpoint.AbsolutePath, Method.POST);
38 |
39 | string jsonRequestBody = JsonConvert.SerializeObject(lwaAccessTokenRequestMeta);
40 |
41 | accessTokenRequest.AddParameter(JsonMediaType, jsonRequestBody, ParameterType.RequestBody);
42 |
43 | string accessToken;
44 |
45 | LWAExceptionErrorCode errorCode;
46 |
47 | try
48 | {
49 | var response = RestClient.Execute(accessTokenRequest);
50 | JObject responseJson = !String.IsNullOrEmpty(response.Content) ? JObject.Parse(response.Content) : null;
51 |
52 | if (!IsSuccessful(response))
53 | {
54 | if (responseJson != null)
55 | {
56 | // If error code is present check error code if its one of the defined LWAExceptionErrorCode values
57 | var parsedErrorCode = responseJson.ContainsKey(ErrorCodeKey) ? Enum.TryParse(responseJson.GetValue(ErrorCodeKey).ToString(), out errorCode) : false;
58 |
59 | if (parsedErrorCode) // throw error code and description if matches defined values
60 | {
61 | throw new LWAException(responseJson.GetValue(ErrorCodeKey).ToString(), responseJson.GetValue(ErrorDescKey).ToString(), "Error getting LWA Access Token");
62 | }
63 | } //else throw general LWA exception
64 | throw new LWAException(LWAExceptionErrorCode.other.ToString(), "Other LWA Exception", "Error getting LWA Access Token");
65 | }
66 | accessToken = responseJson.GetValue(AccessTokenKey).ToString();
67 | }
68 | catch (LWAException e)
69 | {
70 | throw new LWAException(e.getErrorCode(), e.getErrorMessage(), e.Message);
71 | }
72 | catch (Exception e)
73 | {
74 | throw new SystemException("Error getting LWA Access Token", e);
75 | }
76 |
77 | return accessToken;
78 | }
79 |
80 | private bool IsSuccessful(IRestResponse response)
81 | {
82 | int statusCode = (int)response.StatusCode;
83 | return statusCode >= 200 && statusCode <= 299 && response.ResponseStatus == ResponseStatus.Completed;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/schemas/notifications/FBAOutboundShipmentStatusNotification.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema",
3 | "$id": "http://example.com/example.json",
4 | "type": "object",
5 | "description": "The root schema comprises the entire JSON document.",
6 | "examples": [
7 | {
8 | "NotificationVersion": "1.0",
9 | "NotificationType": "FBA_OUTBOUND_SHIPMENT_STATUS",
10 | "PayloadVersion": "1.0",
11 | "EventTime": "2020-01-11T00:09:53.109Z",
12 | "Payload": {
13 | "FBAOutboundShipmentStatusNotification": {
14 | "SellerId": "merchantId",
15 | "AmazonOrderId": "113-2646096-4474645",
16 | "AmazonShipmentId": "DrLqQwqvb",
17 | "ShipmentStatus": "Created"
18 | }
19 | },
20 | "NotificationMetadata": {
21 | "ApplicationId": "appId",
22 | "SubscriptionId": "subId",
23 | "PublishTime": "2020-01-11T00:02:50.501Z",
24 | "NotificationId": "requestId"
25 | }
26 | }
27 | ],
28 | "required": [
29 | "NotificationVersion",
30 | "NotificationType",
31 | "PayloadVersion",
32 | "EventTime",
33 | "NotificationMetadata",
34 | "Payload"
35 | ],
36 | "additionalProperties": true,
37 | "properties": {
38 | "NotificationVersion": {
39 | "$id": "#/properties/NotificationVersion",
40 | "type": "string"
41 | },
42 | "NotificationType": {
43 | "$id": "#/properties/NotificationType",
44 | "type": "string"
45 | },
46 | "PayloadVersion": {
47 | "$id": "#/properties/PayloadVersion",
48 | "type": "string"
49 | },
50 | "EventTime": {
51 | "$id": "#/properties/EventTime",
52 | "type": "string"
53 | },
54 | "NotificationMetadata": {
55 | "$id": "#/properties/NotificationMetadata",
56 | "type": "object",
57 | "required": [
58 | "ApplicationId",
59 | "SubscriptionId",
60 | "PublishTime",
61 | "NotificationId"
62 | ],
63 | "properties": {
64 | "ApplicationId": {
65 | "$id": "#/properties/NotificationMetadata/properties/ApplicationId",
66 | "type": "string"
67 | },
68 | "SubscriptionId": {
69 | "$id": "#/properties/NotificationMetadata/properties/SubscriptionId",
70 | "type": "string"
71 | },
72 | "PublishTime": {
73 | "$id": "#/properties/NotificationMetadata/properties/PublishTime",
74 | "type": "string"
75 | },
76 | "NotificationId": {
77 | "$id": "#/properties/NotificationMetadata/properties/NotificationId",
78 | "type": "string"
79 | }
80 | }
81 | },
82 | "Payload": {
83 | "$id": "#/properties/Payload",
84 | "type": "object",
85 | "required": [
86 | "FBAOutboundShipmentStatusNotification"
87 | ],
88 | "properties": {
89 | "FBAOutboundShipmentStatusNotification": {
90 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification",
91 | "type": "object",
92 | "required": [
93 | "SellerId",
94 | "AmazonOrderId",
95 | "AmazonShipmentId",
96 | "ShipmentStatus"
97 | ],
98 | "properties": {
99 | "SellerId": {
100 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/SellerId",
101 | "type": "string"
102 | },
103 | "AmazonOrderId": {
104 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/AmazonOrderId",
105 | "type": "string"
106 | },
107 | "AmazonShipmentId": {
108 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/AmazonShipmentId",
109 | "type": "string"
110 | },
111 | "ShipmentStatus": {
112 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/ShipmentStatus",
113 | "type": "string"
114 | }
115 | }
116 | }
117 | }
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClient.java:
--------------------------------------------------------------------------------
1 | package com.amazon.SellingPartnerAPIAA;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.JsonObject;
5 | import com.google.gson.JsonParser;
6 | import lombok.AccessLevel;
7 | import lombok.Getter;
8 | import lombok.Setter;
9 | import okhttp3.MediaType;
10 | import okhttp3.OkHttpClient;
11 | import okhttp3.Request;
12 | import okhttp3.RequestBody;
13 | import okhttp3.Response;
14 | import okhttp3.ResponseBody;
15 | import org.apache.commons.lang3.EnumUtils;
16 |
17 | class LWAClient {
18 | private static final String ACCESS_TOKEN_KEY = "access_token";
19 | private static final String ACCESS_TOKEN_EXPIRES_IN = "expires_in";
20 | private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
21 | @Getter
22 | private String endpoint;
23 |
24 | @Setter(AccessLevel.PACKAGE)
25 | private OkHttpClient okHttpClient;
26 | private LWAAccessTokenCache lwaAccessTokenCache;
27 |
28 | /**
29 | * Sets cache to store access token until token is expired
30 | */
31 | public void setLWAAccessTokenCache(LWAAccessTokenCache tokenCache) {
32 | this.lwaAccessTokenCache = tokenCache;
33 | }
34 |
35 | LWAClient(String endpoint) {
36 | okHttpClient = new OkHttpClient();
37 | this.endpoint = endpoint;
38 | }
39 |
40 | String getAccessToken(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) throws LWAException {
41 | if (lwaAccessTokenCache != null) {
42 | return getAccessTokenFromCache(lwaAccessTokenRequestMeta);
43 | } else {
44 | return getAccessTokenFromEndpoint(lwaAccessTokenRequestMeta);
45 | }
46 | }
47 |
48 | String getAccessTokenFromCache(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) throws LWAException {
49 | String accessTokenCacheData = (String) lwaAccessTokenCache.get(lwaAccessTokenRequestMeta);
50 | if (accessTokenCacheData != null) {
51 | return accessTokenCacheData;
52 | } else {
53 | return getAccessTokenFromEndpoint(lwaAccessTokenRequestMeta);
54 | }
55 | }
56 |
57 | String getAccessTokenFromEndpoint(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) throws LWAException {
58 | RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, new Gson().toJson(lwaAccessTokenRequestMeta));
59 | Request accessTokenRequest = new Request.Builder().url(endpoint).post(requestBody).build();
60 | LWAExceptionErrorCode lwaErrorCode = null;
61 | String accessToken;
62 | try {
63 | Response response = okHttpClient.newCall(accessTokenRequest).execute();
64 | ResponseBody body = response.body();
65 | if (body == null) throw new LWAException(LWAExceptionErrorCode.other.toString(),
66 | "Error getting LWA Token", "Response body missing");
67 | JsonObject responseJson = JsonParser.parseString(body.string()).getAsJsonObject();
68 | if (!response.isSuccessful()) {
69 | // Check if response has element error and is a known LWA error code
70 | if (responseJson.has("error") &&
71 | EnumUtils.isValidEnum(LWAExceptionErrorCode.class, responseJson.get("error").getAsString())) {
72 | throw new LWAException(responseJson.get("error").getAsString(),
73 | responseJson.get("error_description").getAsString(), "Error getting LWA Token");
74 | } else { // else throw other LWA error
75 | throw new LWAException(LWAExceptionErrorCode.other.toString(), "Other LWA Exception",
76 | "Error getting LWA Token");
77 | }
78 | }
79 | accessToken = responseJson.get(ACCESS_TOKEN_KEY).getAsString();
80 | if (lwaAccessTokenCache != null) {
81 | long timeToTokenexpiry = responseJson.get(ACCESS_TOKEN_EXPIRES_IN).getAsLong();
82 | lwaAccessTokenCache.put(lwaAccessTokenRequestMeta, accessToken, timeToTokenexpiry);
83 | }
84 | } catch (LWAException e) { // throw LWA exception
85 | throw new LWAException(e.getErrorCode(), e.getErrorMessage(), e.getMessage());
86 | } catch (Exception e) { // throw other runtime exceptions
87 | throw new RuntimeException("Error getting LWA Token");
88 | }
89 | return accessToken;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-java/README.md:
--------------------------------------------------------------------------------
1 | # Selling Partner API Authentication/Authorization Library
2 | This library provides helper classes for use when signing HTTP requests for Amazon Selling Partner APIs. It is intended for use
3 | with the Selling Partner API Client Libraries generated by [swagger codegen](https://swagger.io/tools/swagger-codegen/)
4 | using the OkHttp library. It can also be integrated into custom projects.
5 |
6 | ## LWAAuthorizationSigner
7 | Obtains and signs a request with an access token from LWA (Login with Amazon) for the specified endpoint using the provided LWA credentials.
8 |
9 | *Example*
10 | ```
11 | com.squareup.okhttp.Request request = new Request.Builder()
12 | .url(...)
13 | ...
14 | .build();
15 |
16 | // Seller APIs
17 |
18 | LWAAuthorizationCredentials lwaAuthorizationCredentials = LWAAuthorizationCredentials.builder()
19 | .clientId("...")
20 | .clientSecret("...")
21 | .refreshToken("...")
22 | .endpoint("...")
23 | .build();
24 |
25 | /* Sellerless APIs
26 | The Selling Partner API scopes can be retrieved from the ScopeConstants class and passed as argument(s) to either the withScope(String scope) or withScopes(String... scopes) method during lwaAuthorizationCredentials object instantiation. */
27 |
28 | import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API;
29 |
30 | LWAAuthorizationCredentials lwaAuthorizationCredentials = LWAAuthorizationCredentials.builder()
31 | .clientId("...")
32 | .clientSecret("...")
33 | .withScopes("...")
34 | .endpoint("...")
35 | .build();
36 |
37 | com.squareup.okhttp.Request signedRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials)
38 | .sign(request);
39 | ```
40 |
41 | ## LWAAccessTokenCache
42 | Interface to implement cache for access token that is returned in LWAClient and reuse the access token until time to live.
43 |
44 | ## RateLimitConfiguration
45 | Interface to set and get rateLimit configurations that are used with RateLimiter. RateLimiter is used on client side to restrict the rate at which requests are made. RateLimiter Configuration takes Permit, rate which requests are made and TimeOut
46 |
47 | *Example*
48 | ```
49 | com.squareup.okhttp.Request request = new Request.Builder()
50 | .url(...)
51 | ...
52 | .build();
53 |
54 | RateLimitConfiguration rateLimitOption = RateLimitConfigurationOnRequests.builder()
55 | .rateLimitPermit(...)
56 | .waitTimeOutInMilliSeconds(...)
57 | .build();
58 | ```
59 | ## Exception
60 | This package returns a custom LWAException when there is an error returned during LWA authorization. LWAException provides additional details like errorCode and errorDescription to help fix the issue.
61 |
62 | *Example*
63 | ```
64 | catch (LWAException e) {
65 | System.err.println("LWA Exception when calling Selling partner API");
66 | System.err.println(e.getErrorCode());
67 | System.err.println(e.getErrorMessage());
68 | e.printStackTrace();
69 | }
70 | ```
71 |
72 | ## Version
73 | Selling Partner API Authentication/Authorization Library version 3.0.
74 |
75 | ## Resources
76 | This package features Mustache templates designed for use with [swagger codegen](https://swagger.io/tools/swagger-codegen/).
77 | When you build Selling Partner API Swagger models with these templates, they help generate a rich SDK with functionality to invoke Selling Partner APIs built in. The templates are located in *resources/swagger-codegen*.
78 |
79 | ## Building
80 | This library can be built using Maven by running this command in the package root:
81 | ```
82 | mvn clean package
83 | ```
84 | Dependencies are declared in the pom.xml file.
85 |
86 | ## License
87 | Swagger Codegen templates are subject to the [Swagger Codegen License](https://github.com/swagger-api/swagger-codegen#license).
88 |
89 | All other work licensed as follows:
90 |
91 | Copyright 2019 Amazon.com, Inc
92 |
93 | Licensed under the Apache License, Version 2.0 (the "License");
94 | you may not use this library except in compliance with the License.
95 | You may obtain a copy of the License at
96 |
97 | http://www.apache.org/licenses/LICENSE-2.0
98 |
99 | Unless required by applicable law or agreed to in writing, software
100 | distributed under the License is distributed on an "AS IS" BASIS,
101 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
102 | See the License for the specific language governing permissions and
103 | limitations under the License.
104 |
--------------------------------------------------------------------------------
/schemas/reports/vendorRealTimeTrafficReport.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema",
3 | "type": "object",
4 | "description": "This report shares data on the customer traffic to the detail pages of the vendor's items with an hourly granularity. Requests can span multiple date range periods. For example, if the customer specified dataStartTime and dataEndTime span three hours, the report would contain data for each complete hour within the time span.",
5 | "examples": [
6 | {
7 | "reportSpecification": {
8 | "reportType": "GET_VENDOR_REAL_TIME_TRAFFIC_REPORT",
9 | "dataStartTime": "2022-04-20T20:00:00Z",
10 | "dataEndTime": "2022-04-20T21:00:00Z",
11 | "marketplaceIds": [
12 | "ATVPDKIKX0DER"
13 | ]
14 | },
15 | "reportData": [
16 | {
17 | "startTime": "2022-04-20T20:00:00Z",
18 | "endTime": "2022-04-20T21:00:00Z",
19 | "asin": "B00ABCD123",
20 | "glanceViews": 75
21 | }
22 | ]
23 | }
24 | ],
25 | "required": [
26 | "reportSpecification",
27 | "reportData"
28 | ],
29 | "properties": {
30 | "reportSpecification": {
31 | "type": "object",
32 | "description": "Summarizes the original report request.",
33 | "required": [
34 | "reportType",
35 | "dataStartTime",
36 | "dataEndTime",
37 | "marketplaceIds"
38 | ],
39 | "properties": {
40 | "reportType": {
41 | "type": "string",
42 | "description": "The type of the report.",
43 | "enum": [
44 | "GET_VENDOR_REAL_TIME_TRAFFIC_REPORT"
45 | ]
46 | },
47 | "dataStartTime": {
48 | "type": "string",
49 | "format": "date-time",
50 | "description": "The start of a date-time range in UTC used to determine hours to report on. Output will include all full hours that fall within the range.",
51 | "examples": [
52 | "2022-04-20T20:00:00Z",
53 | "2022-04-20T20:59:59Z"
54 | ]
55 | },
56 | "dataEndTime": {
57 | "type": "string",
58 | "format": "date-time",
59 | "description": "The end of a date-time range in UTC used to determine hours to report on. Output will include all full hours that fall within the range. End time should be at least 60 minutes earlier than the time of the request.",
60 | "examples": [
61 | "2022-04-20T20:00:00Z",
62 | "2022-04-20T20:10:00Z"
63 | ]
64 | },
65 | "marketplaceIds": {
66 | "type": "array",
67 | "description": "Marketplace IDs as specified in the report request.",
68 | "examples": [
69 | [
70 | "ATVPDKIKX0DER"
71 | ]
72 | ],
73 | "items": {
74 | "type": "string"
75 | }
76 | }
77 | }
78 | },
79 | "reportData": {
80 | "type": "array",
81 | "description": "List of hour and ASIN combinations.",
82 | "items": {
83 | "$ref": "#/definitions/ReportData"
84 | }
85 | }
86 | },
87 | "definitions": {
88 | "ReportData": {
89 | "type": "object",
90 | "description": "Contains details about hour and ASIN combinations for the specified time range.",
91 | "required": [
92 | "startTime",
93 | "endTime",
94 | "asin",
95 | "glanceViews"
96 | ],
97 | "properties": {
98 | "startTime": {
99 | "type": "string",
100 | "format": "date-time",
101 | "description": "The start of a date-time range in UTC representing the beginning of the hour for this object.",
102 | "examples": [
103 | "2022-04-20T20:00:00Z",
104 | "2022-04-20T08:00:00Z"
105 | ]
106 | },
107 | "endTime": {
108 | "type": "string",
109 | "format": "date-time",
110 | "description": "The end of a date-time range in UTC representing the end of the hour for this object.",
111 | "examples": [
112 | "2022-04-20T21:00:00Z",
113 | "2022-04-20T09:00:00Z"
114 | ]
115 | },
116 | "asin": {
117 | "description": "The Amazon Standard Identification Number (ASIN).",
118 | "type": "string",
119 | "examples": [
120 | "B123456789"
121 | ]
122 | },
123 | "glanceViews": {
124 | "type": "integer",
125 | "description": "The number of customer views of the product detail page for this ASIN.",
126 | "minimum": 0,
127 | "examples": [
128 | 75
129 | ]
130 | }
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-csharp/README.md:
--------------------------------------------------------------------------------
1 | # Selling Partner API Authentication/Authorization Library
2 | This library provides helper classes for use when signing HTTP requests for Amazon Selling Partner APIs. It is intended for use
3 | with the Selling Partner API Client Libraries generated by [swagger codegen](https://swagger.io/tools/swagger-codegen/)
4 | using the RestSharp library. It can also be integrated into custom projects.
5 |
6 | ## LWAAuthorizationSigner
7 | Obtains and signs a request with an access token from LWA (Login with Amazon) for the specified endpoint using the provided LWA credentials.
8 |
9 | *Example*
10 | ```
11 | using RestSharp;
12 | using Amazon.SellingPartnerAPIAA;
13 |
14 | string resource = "/my/api/path";
15 | RestClient restClient = new RestClient("https://...");
16 | IRestRequest restRequest = new RestRequest(resource, Method.GET);
17 |
18 | // Seller APIs
19 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials
20 | {
21 | ClientId = "...",
22 | ClientSecret = "",
23 | RefreshToken = "",
24 | Endpoint = new Uri("...")
25 | };
26 |
27 | /* Sellerless APIs
28 | The Selling Partner API scopes can be retrieved from the ScopeConstants class and used to specify a list of scopes of an LWAAuthorizationCredentials instance. */
29 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials
30 | {
31 | ClientId = "...",
32 | ClientSecret = "",
33 | Scopes = new List() { ScopeConstants.ScopeNotificationsAPI, ScopeConstants.ScopeMigrationAPI }
34 | Endpoint = new Uri("...")
35 | };
36 |
37 | restRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials).Sign(restRequest);
38 | ```
39 | Note the IRestRequest reference is treated as **mutable** when signed.
40 |
41 | ## RateLimitConfiguration
42 | Interface to set and get rateLimit configurations that are used with RateLimiter. RateLimiter is used on client side to restrict the rate at which requests are made. RateLimiter Configuration takes Permit, rate which requests are made and TimeOut
43 |
44 | *Example*
45 | ```
46 | RateLimitConfiguration rateLimitConfig = new RateLimitConfigurationOnRequests
47 | {
48 | RateLimitPermit = ..,
49 | WaitTimeOutInMilliSeconds = ...
50 | };
51 |
52 | ```
53 |
54 | ## Exception
55 | This package returns a custom LWAException when there is an error returned during LWA authorization. LWAException provides additional details like errorCode and errorDescription to help fix the issue.
56 |
57 | *Example*
58 | ```
59 | catch (LWAException e)
60 | {
61 | Console.WriteLine("LWA Exception when calling Selling partner API");
62 | Console.WriteLine(e.getErrorCode());
63 | Console.WriteLine(e.getErrorMessage());
64 | Console.WriteLine(e.Message);
65 | }
66 | ```
67 |
68 | ## Version
69 | Selling Partner API Authentication/Authorization Library version 2.0.
70 |
71 | ## Resources
72 | This package features Mustache templates designed for use with [swagger codegen](https://swagger.io/tools/swagger-codegen/).
73 | When you build Selling Partner API Swagger models with these templates, they help generate a rich SDK with functionality to invoke Selling Partner APIs built in. The templates are located in *resources/swagger-codegen*.
74 |
75 | ## Building
76 | This package is built as a .NET Standard Library via a Visual Studio Solution with implementation and test projects. The Visual Studio Community Edition can be obtained for free from Microsoft and used to build the solution and generate a .dll assembly which can be imported into other projects.
77 |
78 | ## Dependencies
79 | All dependencies can be installed via NuGet
80 | - RestSharp - 106.12.0
81 | - Newtonsoft.Json 12.0.3
82 | - NETStandard.Library 2.0.3 (platform-specific implementation requirements are documented on the [Microsoft .NET Guide](https://docs.microsoft.com/en-us/dotnet/standard/net-standard))
83 |
84 | ## License
85 | Swagger Codegen templates are subject to the [Swagger Codegen License](https://github.com/swagger-api/swagger-codegen#license).
86 |
87 | All other work licensed as follows:
88 |
89 | Copyright 2020 Amazon.com, Inc
90 |
91 | Licensed under the Apache License, Version 2.0 (the "License");
92 | you may not use this library except in compliance with the License.
93 | You may obtain a copy of the License at
94 |
95 | http://www.apache.org/licenses/LICENSE-2.0
96 |
97 | Unless required by applicable law or agreed to in writing, software
98 | distributed under the License is distributed on an "AS IS" BASIS,
99 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
100 | See the License for the specific language governing permissions and
101 | limitations under the License.
--------------------------------------------------------------------------------
/clients/sellingpartner-api-aa-python/auth/LwaRequest.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import time
3 | import logging
4 | import sys
5 |
6 | #Update path
7 | sys.path.append("path_to_folder/SellingPartnerAPIAuthAndAuthPython")
8 |
9 | from auth.LwaException import LwaException
10 | from auth.LwaExceptionErrorCode import LwaExceptionErrorCode
11 |
12 | logging.basicConfig(level=logging.INFO)
13 | logger = logging.getLogger(__name__)
14 |
15 | class AccessTokenCache:
16 | def __init__(self, max_retries=3):
17 | self.token_info = None
18 | self.max_retries = max_retries
19 |
20 | def get_lwa_access_token(self, client_id, client_secret, refresh_token=None, grant_type="refresh_token", scope=None):
21 | if self.token_info and time.time() < self.token_info["expires_at"]:
22 | return self.token_info["access_token"]
23 | return self.request_new_token(client_id, client_secret, refresh_token, grant_type, scope)
24 |
25 |
26 | def request_new_token(self, client_id, client_secret, refresh_token, grant_type, scope):
27 | self.validate_token_request(grant_type, refresh_token, scope)
28 | data = self.prepare_token_request_data(client_id, client_secret, refresh_token, grant_type, scope)
29 |
30 | retries = 0
31 | while retries <= self.max_retries:
32 | try:
33 | response = requests.post("https://api.amazon.com/auth/o2/token", data=data)
34 | response.raise_for_status()
35 | token_response = response.json()
36 | token_response["expires_at"] = time.time() + token_response.get("expires_in", 1800) - 30
37 | self.token_info = token_response
38 | return token_response["access_token"]
39 | except requests.RequestException as e:
40 | if retries < self.max_retries:
41 | retries += 1
42 | time.sleep(2 ** retries)
43 | continue
44 | error_code = self.map_http_status_to_lwa_exception_code(e.response.status_code if e.response else None)
45 | error_message = f"Token request failed with status code {e.response.status_code}: {e.response.text}" if e.response else "Token request failed."
46 | logger.error(error_message)
47 | raise LwaException(error_code, error_message) from e
48 |
49 |
50 | def validate_token_request(self, grant_type, refresh_token, scope):
51 | if grant_type == "refresh_token" and not refresh_token:
52 | raise LwaException(LwaExceptionErrorCode.INVALID_REQUEST.value, "Refresh token must be provided for grant_type 'refresh_token'")
53 | if grant_type == "client_credentials" and not scope:
54 | raise LwaException(LwaExceptionErrorCode.INVALID_SCOPE.value, "Scope must be provided for grant_type 'client_credentials'")
55 |
56 |
57 | def prepare_token_request_data(self, client_id, client_secret, refresh_token, grant_type, scope):
58 | data = {
59 | "client_id": client_id,
60 | "client_secret": client_secret,
61 | "grant_type": grant_type
62 | }
63 | if grant_type == "refresh_token":
64 | if not refresh_token:
65 | raise LwaException(LwaExceptionErrorCode.INVALID_REQUEST.value, "Refresh token must be provided for grant_type 'refresh_token'")
66 | data["refresh_token"] = refresh_token
67 | elif grant_type == "client_credentials":
68 | if not scope:
69 | raise LwaException(LwaExceptionErrorCode.INVALID_SCOPE.value, "Scope must be provided for grant_type 'client_credentials'")
70 | data["scope"] = scope
71 | return data
72 |
73 | def is_retriable(self, error_code):
74 | retriable_codes = [
75 | LwaExceptionErrorCode.SERVER_ERROR.value,
76 | LwaExceptionErrorCode.TEMPORARILY_UNAVAILABLE.value
77 | ]
78 | return error_code in retriable_codes
79 |
80 | def format_error_message(self, e):
81 | return f"Token request failed with status code {e.response.status_code}: {e.response.text}" if e.response else f"Token request failed: {e}"
82 |
83 | def map_http_status_to_lwa_exception_code(self, status_code):
84 | if status_code is None:
85 | return LWAExceptionErrorCode.SERVER_ERROR.value
86 | if status_code == 400:
87 | return LWAExceptionErrorCode.INVALID_REQUEST.value
88 | if status_code == 401:
89 | return LWAExceptionErrorCode.UNAUTHORIZED_CLIENT.value
90 | if status_code == 403:
91 | return LWAExceptionErrorCode.ACCESS_DENIED.value
92 | if status_code == 500:
93 | return LWAExceptionErrorCode.SERVER_ERROR.value
94 | if status_code == 503:
95 | return LWAExceptionErrorCode.TEMPORARILY_UNAVAILABLE.value
96 | return LWAExceptionErrorCode.OTHER.value
97 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/impl/OkHttpTransferClient.java:
--------------------------------------------------------------------------------
1 | package com.amazon.spapi.documents.impl;
2 |
3 | import com.amazon.spapi.documents.HttpTransferClient;
4 | import com.amazon.spapi.documents.exception.HttpResponseException;
5 | import com.squareup.okhttp.MediaType;
6 | import com.squareup.okhttp.OkHttpClient;
7 | import com.squareup.okhttp.Request;
8 | import com.squareup.okhttp.RequestBody;
9 | import com.squareup.okhttp.Response;
10 | import org.apache.commons.io.FileUtils;
11 | import org.apache.commons.io.IOUtils;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.io.Reader;
16 |
17 | /**
18 | * HTTP transfer client utilizing OkHttp.
19 | */
20 | public class OkHttpTransferClient implements HttpTransferClient {
21 | private static final String CONTENT_TYPE_HEADER = "Content-Type";
22 |
23 | private final OkHttpClient client;
24 | private final int maxErrorBodyLen;
25 |
26 | private OkHttpTransferClient(OkHttpClient client, int maxErrorBodyLen) {
27 | this.client = client;
28 | this.maxErrorBodyLen = maxErrorBodyLen;
29 | }
30 |
31 | private HttpResponseException createResponseException(Response response) {
32 | String body = "";
33 | if (maxErrorBodyLen > 0) {
34 | try (Reader bodyReader = response.body().charStream()) {
35 | char[] buf = new char[maxErrorBodyLen];
36 | int charsRead = IOUtils.read(bodyReader, buf);
37 | if (charsRead > 0) {
38 | body = new String(buf, 0, charsRead);
39 | }
40 | } catch (Exception e) {
41 | // Ignore any failures reading the body so that the original failure is not lost
42 | }
43 | }
44 |
45 | return new HttpResponseException(response.message(), body, response.code());
46 | }
47 |
48 | /**
49 | * {@inheritDoc}
50 | */
51 | @Override
52 | public String download(String url, File destination) throws HttpResponseException, IOException {
53 | Request request = new Request.Builder()
54 | .url(url)
55 | .get()
56 | .build();
57 |
58 | Response response = client.newCall(request).execute();
59 | try {
60 | if (!response.isSuccessful()) {
61 | throw createResponseException(response);
62 | }
63 |
64 | FileUtils.copyInputStreamToFile(response.body().byteStream(), destination);
65 | } finally {
66 | IOUtils.closeQuietly(response.body());
67 | }
68 |
69 | return response.header(CONTENT_TYPE_HEADER);
70 | }
71 |
72 | /**
73 | * {@inheritDoc}
74 | */
75 | @Override
76 | public void upload(String url, String contentType, File source) throws HttpResponseException, IOException {
77 | Request request = new Request.Builder()
78 | .url(url)
79 | .put(RequestBody.create(MediaType.parse(contentType), source))
80 | .build();
81 |
82 | Response response = client.newCall(request).execute();
83 | try {
84 | if (!response.isSuccessful()) {
85 | throw createResponseException(response);
86 | }
87 | } finally {
88 | IOUtils.closeQuietly(response.body());
89 | }
90 | }
91 |
92 | /**
93 | * Use this to create an instance of an {@link OkHttpTransferClient}.
94 | */
95 | public static class Builder {
96 | private OkHttpClient client = null;
97 | private int maxErrorBodyLen = 4096;
98 |
99 | /**
100 | * The {@link OkHttpClient}. If not specified, a new instance of {@link OkHttpClient} will be created using the
101 | * no-arg constructor.
102 | *
103 | * @param client The client
104 | * @return this
105 | */
106 | public Builder withClient(OkHttpClient client) {
107 | this.client = client;
108 | return this;
109 | }
110 |
111 | /**
112 | * When an HTTP response indicates failure, the maximum number of characters for
113 | * {@link HttpResponseException#getBody()}. Default 4096.
114 | *
115 | * @param maxErrorBodyLen The maximum number of characters to extract from the response body on failure
116 | * @return this
117 | */
118 | public Builder withMaxErrorBodyLen(int maxErrorBodyLen) {
119 | this.maxErrorBodyLen = maxErrorBodyLen;
120 | return this;
121 | }
122 |
123 | /**
124 | * Create the client.
125 | *
126 | * @return The client
127 | */
128 | public OkHttpTransferClient build() {
129 | if (client == null) {
130 | client = new OkHttpClient();
131 | }
132 |
133 | return new OkHttpTransferClient(client, maxErrorBodyLen);
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/DownloadHelper.java:
--------------------------------------------------------------------------------
1 | package com.amazon.spapi.documents;
2 |
3 | import com.amazon.spapi.documents.exception.HttpResponseException;
4 | import com.amazon.spapi.documents.impl.OkHttpTransferClient;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | /**
10 | * Helper for downloading encrypted documents.
11 | */
12 | public class DownloadHelper {
13 | private final HttpTransferClient httpTransferClient;
14 | private final String tmpFilePrefix;
15 | private final String tmpFileSuffix;
16 | private final File tmpFileDirectory;
17 |
18 | private DownloadHelper(HttpTransferClient httpTransferClient, String tmpFilePrefix, String tmpFileSuffix,
19 | File tmpFileDirectory) {
20 | this.httpTransferClient = httpTransferClient;
21 | this.tmpFilePrefix = tmpFilePrefix;
22 | this.tmpFileSuffix = tmpFileSuffix;
23 | this.tmpFileDirectory = tmpFileDirectory;
24 | }
25 |
26 | /**
27 | * Download the specified document's encrypted contents to a temporary file on disk. It is the responsibility of the
28 | * caller to call close on the returned {@link AutoCloseable} {@link DownloadBundle}.
29 | *
30 | * Common reasons for receiving a 403 response include:
31 | *
The signed URL has expired
32 | *
33 | * @param spec The specification for the download
34 | * @return The closeable {@link DownloadBundle}
35 | * @throws HttpResponseException On failure HTTP response
36 | * @throws IOException IO Exception
37 | */
38 | public DownloadBundle download(DownloadSpecification spec) throws HttpResponseException, IOException {
39 |
40 | File tmpFile = File.createTempFile(tmpFilePrefix, tmpFileSuffix, tmpFileDirectory);
41 |
42 | try {
43 | tmpFile.deleteOnExit();
44 |
45 | String contentType = httpTransferClient.download(spec.getUrl(), tmpFile);
46 |
47 | return new DownloadBundle(
48 | spec.getCompressionAlgorithm(), contentType, spec.getCryptoStreamFactory(), tmpFile);
49 | } catch (Exception e) {
50 | tmpFile.delete();
51 | throw e;
52 | }
53 | }
54 |
55 | /**
56 | * Use this to create an instance of a {@link DownloadHelper}.
57 | */
58 | public static class Builder {
59 | private HttpTransferClient httpTransferClient = null;
60 | private String tmpFilePrefix = "SPAPI";
61 | private String tmpFileSuffix = null;
62 | private File tmpFileDirectory = null;
63 |
64 | /**
65 | * The HTTP transfer client.
66 | *
67 | * @param httpTransferClient The HTTP transfer client
68 | * @return this
69 | */
70 | public Builder withHttpTransferClient(HttpTransferClient httpTransferClient) {
71 | this.httpTransferClient = httpTransferClient;
72 | return this;
73 | }
74 |
75 | /**
76 | * The tmp file prefix. If not specified, defaults to "SPAPI".
77 | *
78 | * @param tmpFilePrefix The prefix string to be used in generating the tmp file's name; must be at least three
79 | * characters long
80 | * @return this
81 | */
82 | public Builder withTmpFilePrefix(String tmpFilePrefix) {
83 | if (tmpFilePrefix.length() < 3) {
84 | throw new IllegalArgumentException("Prefix string too short");
85 | }
86 |
87 | this.tmpFilePrefix = tmpFilePrefix;
88 | return this;
89 | }
90 |
91 | /**
92 | * The tmp file suffix. If not specified, defaults to null.
93 | *
94 | * @param tmpFileSuffix The suffix string to be used in generating the file's name; may be null, in
95 | * which case the suffix ".tmp" will be used
96 | * @return this
97 | */
98 | public Builder withTmpFileSuffix(String tmpFileSuffix) {
99 | this.tmpFileSuffix = tmpFileSuffix;
100 | return this;
101 | }
102 |
103 | /**
104 | * The tmp file directory. If not specified, defaults to null.
105 | *
106 | * @param tmpFileDirectory The directory in which the file is to be created, or null if the default
107 | * temporary-file directory is to be used
108 | * @return this
109 | */
110 | public Builder withTmpFileDirectory(File tmpFileDirectory) {
111 | this.tmpFileDirectory = tmpFileDirectory;
112 | return this;
113 | }
114 |
115 | /**
116 | * Create the helper.
117 | *
118 | * @return The helper
119 | */
120 | public DownloadHelper build() {
121 | if (httpTransferClient == null) {
122 | httpTransferClient = new OkHttpTransferClient.Builder().build();
123 | }
124 |
125 | return new DownloadHelper(httpTransferClient, tmpFilePrefix, tmpFileSuffix, tmpFileDirectory);
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/clients/sample-code/DelegatedRestrictedDataTokenWorkflowForDelegatee.java:
--------------------------------------------------------------------------------
1 | package sampleCode;
2 |
3 | // Imports from the Selling Partner API (SP-API) Auth & Auth client library.
4 | import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentials;
5 | import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentialsProvider;
6 | import com.amazon.SellingPartnerAPIAA.AWSSigV4Signer;
7 | import com.squareup.okhttp.OkHttpClient;
8 | import com.squareup.okhttp.Request;
9 | import com.squareup.okhttp.RequestBody;
10 | import com.squareup.okhttp.Response;
11 |
12 | import java.io.IOException;
13 |
14 | public class DelegatedRestrictedDataTokenWorkflowForDelegatee {
15 |
16 | // This represents a workflow for a simple use case. Other use cases can be implemented using similar patterns.
17 | public static void main(String[] args) throws IOException {
18 | // The values for method, path and restrictedDataToken should match the RDT request made by delegator and the response.
19 | String method = "GET";
20 | String path = "/orders/v0/orders/123-1234567-1234567";
21 | String restrictedDataToken = "Atz.sprdt|AYABeKCs7hKXXXXXXXXXXXXXXXXXX...";
22 |
23 | // Build, sign, and execute the request, specifying method, path, RDT, and RequestBody.
24 | // Pass a RequestBody only if required by the restricted operation. The requestBody is not required in this example.
25 | Response restrictedRequestResponse = buildAndExecuteDelegatedRestrictedRequest(method, path, restrictedDataToken, null);
26 |
27 | // Check the restricted operation response status code and headers.
28 | System.out.println(restrictedRequestResponse.code());
29 | System.out.println(restrictedRequestResponse.headers());
30 | }
31 |
32 | // The SP-API endpoint.
33 | private static final String sellingPartnerAPIEndpoint = "https://sellingpartnerapi-na.amazon.com";
34 |
35 | // Configure the AWSAuthenticationCredentials object.
36 | // If you registered your application using an IAM Role ARN, the AWSAuthenticationCredentials and AWSAuthenticationCredentialsProvider objects are required.
37 | private static final AWSAuthenticationCredentials awsAuthenticationCredentials = AWSAuthenticationCredentials.builder()
38 | // If you registered your application using an IAM Role ARN, use the AWS credentials of an IAM User that is linked to the IAM Role through the AWS Security Token Service policy.
39 | // Or, if you registered your application using an IAM User ARN, use the AWS credentials of that IAM User. Be sure that the IAM User has the correct IAM policy attached.
40 | .accessKeyId("aws_access_key")
41 | .secretKey("aws_secret_key")
42 | .region("aws_region")
43 | .build();
44 |
45 | // Configure the AWSAuthenticationCredentialsProvider object. This is only needed for applications registered using an IAM Role.
46 | // If the application was registered using an IAM User, the AWSAuthenticationCredentialsProvider object should be removed.
47 | private static final AWSAuthenticationCredentialsProvider awsAuthenticationCredentialsProvider = AWSAuthenticationCredentialsProvider.builder()
48 | // The IAM Role must have the IAM policy attached as described in "Step 3. Create an IAM policy" in the SP-API Developer Guide.
49 | .roleArn("arn:aws:iam::XXXXXXXXX:role/XXXXXXXXX")
50 | .roleSessionName("session-name")
51 | .build();
52 |
53 | // An example of a helper method to build, sign, and execute a restricted operation, specifying RestrictedResource, (String) RDT, and RequestBody.
54 | // Returns the restricted operation Response object.
55 | private static Response buildAndExecuteDelegatedRestrictedRequest(String method, String path, String restrictedDataToken, RequestBody requestBody) throws IOException {
56 | // Construct a request with the specified RestrictedResource, RDT, and RequestBody.
57 | Request signedRequest = new Request.Builder()
58 | .url(sellingPartnerAPIEndpoint + path) // Define the URL for the request, based on the endpoint and restricted resource path.
59 | .method(method, requestBody) // Define the restricted resource method value, and requestBody, if required by the restricted operation.
60 | .addHeader("x-amz-access-token", restrictedDataToken) // Sign the request with the RDT by adding it to the "x-amz-access-token" header.
61 | .build(); // Build the request.
62 |
63 | // Initiate an AWSSigV4Signer instance using your AWS credentials. This example is for an application registered using an AIM Role.
64 | AWSSigV4Signer awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials, awsAuthenticationCredentialsProvider);
65 |
66 | /*
67 | // Or, if the application was registered using an IAM User, use the following example:
68 | AWSSigV4Signer awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials);
69 | */
70 |
71 | // Sign the request with the AWSSigV4Signer.
72 | signedRequest = awsSigV4Signer.sign(signedRequest);
73 |
74 | // Execute the signed request.
75 | OkHttpClient okHttpClient = new OkHttpClient();
76 | Response response = okHttpClient.newCall(signedRequest).execute();
77 |
78 | return response;
79 | }
80 |
81 | }
--------------------------------------------------------------------------------