diff --git a/clients/sellingpartner-api-aa-java/pom.xml b/clients/sellingpartner-api-aa-java/pom.xml index 5ce406c..52215aa 100644 --- a/clients/sellingpartner-api-aa-java/pom.xml +++ b/clients/sellingpartner-api-aa-java/pom.xml @@ -108,6 +108,12 @@ org.apache.httpcomponents httpclient 4.5.9 + + + + com.amazonaws + aws-java-sdk-sts + 1.11.236 diff --git a/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/ApiClient.mustache b/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/ApiClient.mustache index 2ea2c54..98027ba 100644 --- a/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/ApiClient.mustache +++ b/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/ApiClient.mustache @@ -529,7 +529,7 @@ public class ApiClient { this.awsSigV4Signer = awsSigV4Signer; return this; } - + /** * Format the given parameter object into string. * diff --git a/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/api.mustache b/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/api.mustache index 6d13e68..9a677a9 100644 --- a/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/api.mustache +++ b/clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/api.mustache @@ -43,10 +43,13 @@ import java.util.List; import java.util.Map; {{/fullJavaUtil}} -import com.amazon.SellingPartnerAPIAA.AWSSigV4Signer; -import com.amazon.SellingPartnerAPIAA.LWAAuthorizationSigner; -import com.amazon.SellingPartnerAPIAA.LWAAuthorizationCredentials; import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentials; +import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentialsProvider; +import com.amazon.SellingPartnerAPIAA.AWSSigV4Signer; +import com.amazon.SellingPartnerAPIAA.LWAAccessTokenCache; +import com.amazon.SellingPartnerAPIAA.LWAAccessTokenCacheImpl; +import com.amazon.SellingPartnerAPIAA.LWAAuthorizationCredentials; +import com.amazon.SellingPartnerAPIAA.LWAAuthorizationSigner; {{#operations}} public class {{classname}} { @@ -275,6 +278,9 @@ public class {{classname}} { private AWSAuthenticationCredentials awsAuthenticationCredentials; private LWAAuthorizationCredentials lwaAuthorizationCredentials; private String endpoint; + private LWAAccessTokenCache lwaAccessTokenCache; + private Boolean disableAccessTokenCache = false; + private AWSAuthenticationCredentialsProvider awsAuthenticationCredentialsProvider; public Builder awsAuthenticationCredentials(AWSAuthenticationCredentials awsAuthenticationCredentials) { this.awsAuthenticationCredentials = awsAuthenticationCredentials; @@ -290,6 +296,22 @@ public class {{classname}} { this.endpoint = endpoint; return this; } + + public Builder lwaAccessTokenCache(LWAAccessTokenCache lwaAccessTokenCache) { + this.lwaAccessTokenCache = lwaAccessTokenCache; + return this; + } + + public Builder disableAccessTokenCache() { + this.disableAccessTokenCache = true; + return this; + } + + public Builder awsAuthenticationCredentialsProvider(AWSAuthenticationCredentialsProvider awsAuthenticationCredentialsProvider) { + this.awsAuthenticationCredentialsProvider = awsAuthenticationCredentialsProvider; + return this; + } + public {{classname}} build() { if (awsAuthenticationCredentials == null) { @@ -304,8 +326,24 @@ public class {{classname}} { throw new RuntimeException("Endpoint not set"); } - AWSSigV4Signer awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials); - LWAAuthorizationSigner lwaAuthorizationSigner = new LWAAuthorizationSigner(lwaAuthorizationCredentials); + AWSSigV4Signer awsSigV4Signer; + if ( awsAuthenticationCredentialsProvider == null) { + awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials); + } + else { + awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials,awsAuthenticationCredentialsProvider); + } + + LWAAuthorizationSigner lwaAuthorizationSigner = null; + if (disableAccessTokenCache) { + lwaAuthorizationSigner = new LWAAuthorizationSigner(lwaAuthorizationCredentials); + } + else { + if (lwaAccessTokenCache == null) { + lwaAccessTokenCache = new LWAAccessTokenCacheImpl(); + } + lwaAuthorizationSigner = new LWAAuthorizationSigner(lwaAuthorizationCredentials,lwaAccessTokenCache); + } return new {{classname}}(new ApiClient() .setAWSSigV4Signer(awsSigV4Signer) diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSAuthenticationCredentialsProvider.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSAuthenticationCredentialsProvider.java new file mode 100644 index 0000000..853c288 --- /dev/null +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSAuthenticationCredentialsProvider.java @@ -0,0 +1,23 @@ +package com.amazon.SellingPartnerAPIAA; + +import lombok.Builder; +import lombok.Data; + +/** + * AWSAuthenticationCredentialsProvider + */ +@Data +@Builder +public class AWSAuthenticationCredentialsProvider { + /** + * AWS IAM Role ARN + */ + private String roleArn; + + /** + * AWS IAM Role Session Name + */ + private String roleSessionName; + + +} diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSSigV4Signer.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSSigV4Signer.java index ba4e9f0..3d3187e 100644 --- a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSSigV4Signer.java +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/AWSSigV4Signer.java @@ -3,11 +3,15 @@ package com.amazon.SellingPartnerAPIAA; import com.amazonaws.SignableRequest; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.squareup.okhttp.Request; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import com.amazonaws.auth.AWSStaticCredentialsProvider; /** * AWS Signature Version 4 Signer @@ -21,6 +25,10 @@ public class AWSSigV4Signer { private AWSCredentials awsCredentials; + @Setter(AccessLevel.PACKAGE) + @Getter(AccessLevel.PACKAGE) + private AWSCredentialsProvider awsCredentialsProvider; + /** * * @param awsAuthenticationCredentials AWS Developer Account Credentials @@ -33,6 +41,26 @@ public class AWSSigV4Signer { awsAuthenticationCredentials.getSecretKey()); } + /** + * + * @param awsAuthenticationCredentials and awsAuthenticationCredentialsProvider AWS Developer Account Credentials + */ + public AWSSigV4Signer(AWSAuthenticationCredentials awsAuthenticationCredentials, + AWSAuthenticationCredentialsProvider awsAuthenticationCredentialsProvider) { + aws4Signer = new AWS4Signer(); + aws4Signer.setServiceName(SERVICE_NAME); + aws4Signer.setRegionName(awsAuthenticationCredentials.getRegion()); + BasicAWSCredentials awsBasicCredentials = new BasicAWSCredentials(awsAuthenticationCredentials.getAccessKeyId(), + awsAuthenticationCredentials.getSecretKey()); + awsCredentialsProvider = new STSAssumeRoleSessionCredentialsProvider.Builder( + awsAuthenticationCredentialsProvider.getRoleArn(), + awsAuthenticationCredentialsProvider.getRoleSessionName()) + .withStsClient(AWSSecurityTokenServiceClientBuilder.standard() + .withRegion(awsAuthenticationCredentials.getRegion()) + .withCredentials(new AWSStaticCredentialsProvider(awsBasicCredentials)).build()) + .build(); + } + /** * Signs a Request with AWS Signature Version 4 * @@ -41,8 +69,11 @@ public class AWSSigV4Signer { */ public Request sign(Request originalRequest) { SignableRequest signableRequest = new SignableRequestImpl(originalRequest); - aws4Signer.sign(signableRequest, awsCredentials); - + if (awsCredentialsProvider != null) { + aws4Signer.sign(signableRequest, awsCredentialsProvider.getCredentials()); + } else { + aws4Signer.sign(signableRequest, awsCredentials); + } return (Request) signableRequest.getOriginalRequestObject(); } -} + } diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCache.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCache.java new file mode 100644 index 0000000..8997296 --- /dev/null +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCache.java @@ -0,0 +1,6 @@ +package com.amazon.SellingPartnerAPIAA; + +public interface LWAAccessTokenCache { + String get(Object key); + void put(Object key, String accessToken, long tokenTTLInSeconds); +} diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheImpl.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheImpl.java new file mode 100644 index 0000000..e5a6a91 --- /dev/null +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheImpl.java @@ -0,0 +1,36 @@ +package com.amazon.SellingPartnerAPIAA; + +import java.util.concurrent.ConcurrentHashMap; + +public class LWAAccessTokenCacheImpl implements LWAAccessTokenCache { + //in milliseconds; to avoid returning a token that would expire before or while a request is made + private long expiryAdjustment = 60 * 1000; + private static final long SECOND_TO_MILLIS = 1000; + private ConcurrentHashMap accessTokenHashMap = + new ConcurrentHashMap(); + @Override + public void put(Object oLWAAccessTokenRequestMeta, String accessToken, long tokenTTLInSeconds) { + LWAAccessTokenCacheItem accessTokenCacheItem = new LWAAccessTokenCacheItem(); + long insertTime = System.currentTimeMillis(); + long accessTokenExpiresValueMillis = (tokenTTLInSeconds * SECOND_TO_MILLIS) + insertTime; + accessTokenCacheItem.setAccessToken(accessToken); + accessTokenCacheItem.setAccessTokenExpiredTime(accessTokenExpiresValueMillis); + accessTokenHashMap.put(oLWAAccessTokenRequestMeta, accessTokenCacheItem); + } + + @Override + public String get(Object oLWAAccessTokenRequestMeta) { + Object accessTokenValue = accessTokenHashMap.get(oLWAAccessTokenRequestMeta); + if (accessTokenValue != null) { + LWAAccessTokenCacheItem accessTokenData = + (LWAAccessTokenCacheItem) accessTokenValue; + long currentTime = System.currentTimeMillis(); + long accessTokenExpiredTime = accessTokenData.getAccessTokenExpiredTime() - expiryAdjustment; + if (currentTime < accessTokenExpiredTime) { + return accessTokenData.getAccessToken(); + } + } + return null; + } + +} diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheItem.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheItem.java new file mode 100644 index 0000000..ecf458c --- /dev/null +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheItem.java @@ -0,0 +1,11 @@ +package com.amazon.SellingPartnerAPIAA; + +import lombok.Data; + +@Data +class LWAAccessTokenCacheItem { + + private String accessToken; + private long accessTokenExpiredTime; + +} diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSigner.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSigner.java index f429983..26cc1c4 100644 --- a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSigner.java +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSigner.java @@ -10,7 +10,6 @@ import lombok.Setter; */ public class LWAAuthorizationSigner { private static final String SIGNED_ACCESS_TOKEN_HEADER_NAME = "x-amz-access-token"; - private final String tokenRequestGrantType; @Getter(AccessLevel.PACKAGE) @Setter(AccessLevel.PACKAGE) @@ -18,14 +17,8 @@ public class LWAAuthorizationSigner { private LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta; - /** - * - * @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange - */ - public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials) { - - lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint()); - + private void buildLWAAccessTokenRequestMeta(LWAAuthorizationCredentials lwaAuthorizationCredentials) { + String tokenRequestGrantType; if (!lwaAuthorizationCredentials.getScopes().isEmpty()) { tokenRequestGrantType = "client_credentials"; } else { @@ -40,6 +33,33 @@ public class LWAAuthorizationSigner { .build(); } + /** + * + * @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange + */ + public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials) { + + lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint()); + + buildLWAAccessTokenRequestMeta(lwaAuthorizationCredentials); + + } + + /** + * + * Overloaded Constructor @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange + * and LWAAccessTokenCache + */ + public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials, + LWAAccessTokenCache lwaAccessTokenCache) { + + lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint()); + lwaClient.setLWAAccessTokenCache(lwaAccessTokenCache); + + buildLWAAccessTokenRequestMeta(lwaAuthorizationCredentials); + + } + /** * Signs a Request with an LWA Access Token * @param originalRequest Request to sign (treated as immutable) diff --git a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClient.java b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClient.java index 5068400..7ef428d 100644 --- a/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClient.java +++ b/clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClient.java @@ -16,26 +16,45 @@ import java.io.IOException; class LWAClient { private static final String ACCESS_TOKEN_KEY = "access_token"; + private static final String ACCESS_TOKEN_EXPIRES_IN = "expires_in"; private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); - @Getter private String endpoint; @Setter(AccessLevel.PACKAGE) private OkHttpClient okHttpClient; + private LWAAccessTokenCache lwaAccessTokenCache; + + /** Sets cache to store access token until token is expired */ + public void setLWAAccessTokenCache(LWAAccessTokenCache tokenCache) { + this.lwaAccessTokenCache = tokenCache; + } LWAClient(String endpoint) { okHttpClient = new OkHttpClient(); this.endpoint = endpoint; - } + } String getAccessToken(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) { - RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, - new Gson().toJson(lwaAccessTokenRequestMeta)); - Request accessTokenRequest = new Request.Builder() - .url(endpoint) - .post(requestBody) - .build(); + if (lwaAccessTokenCache != null) { + return getAccessTokenFromCache(lwaAccessTokenRequestMeta); + } else { + return getAccessTokenFromEndpoint(lwaAccessTokenRequestMeta); + } + } + + String getAccessTokenFromCache(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) { + String accessTokenCacheData = (String) lwaAccessTokenCache.get(lwaAccessTokenRequestMeta); + if (accessTokenCacheData != null) { + return accessTokenCacheData; + } else { + return getAccessTokenFromEndpoint(lwaAccessTokenRequestMeta); + } + } + + String getAccessTokenFromEndpoint(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) { + RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, new Gson().toJson(lwaAccessTokenRequestMeta)); + Request accessTokenRequest = new Request.Builder().url(endpoint).post(requestBody).build(); String accessToken; try { @@ -44,15 +63,16 @@ class LWAClient { throw new IOException("Unsuccessful LWA token exchange"); } - JsonObject responseJson = new JsonParser() - .parse(response.body().string()) - .getAsJsonObject(); + JsonObject responseJson = new JsonParser().parse(response.body().string()).getAsJsonObject(); accessToken = responseJson.get(ACCESS_TOKEN_KEY).getAsString(); + if (lwaAccessTokenCache != null) { + long timeToTokenexpiry = responseJson.get(ACCESS_TOKEN_EXPIRES_IN).getAsLong(); + lwaAccessTokenCache.put(lwaAccessTokenRequestMeta, accessToken, timeToTokenexpiry); + } } catch (Exception e) { throw new RuntimeException("Error getting LWA Access Token", e); - } - + } return accessToken; - } + } } diff --git a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/AWSSigV4SignerTest.java b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/AWSSigV4SignerTest.java index 3cd9c6a..1f70382 100644 --- a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/AWSSigV4SignerTest.java +++ b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/AWSSigV4SignerTest.java @@ -3,12 +3,16 @@ package com.amazon.SellingPartnerAPIAA; import com.amazonaws.SignableRequest; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; import com.squareup.okhttp.Request; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; @@ -21,11 +25,18 @@ public class AWSSigV4SignerTest { private static final String TEST_ACCESS_KEY_ID = "aKey"; private static final String TEST_SECRET_KEY = "sKey"; private static final String TEST_REGION = "us-east"; + private static final String TEST_ROLE_ARN = "arnrole"; + private static final String TEST_ROLESESSION_NAME = "test"; @Mock private AWS4Signer mockAWS4Signer; + @Mock + private AWSCredentialsProvider mockAWSCredentialsProvider; + @Mock + private AWSCredentials mockAWSCredentials; private AWSSigV4Signer underTest; + private AWSSigV4Signer underTestCredentialsProvider; @Before public void init() { @@ -80,4 +91,36 @@ public class AWSSigV4SignerTest { assertEquals(((Request)actualSignableRequest.getOriginalRequestObject()).url(), actualSignedRequest.url()); } + + @Test + public void returnSignedRequestWithCredentialProvider() { + ArgumentCaptor signableRequestArgumentCaptor = ArgumentCaptor.forClass(SignableRequest.class); + + Mockito.when(mockAWSCredentialsProvider.getCredentials()).thenReturn(mockAWSCredentials); + + underTestCredentialsProvider = new AWSSigV4Signer(AWSAuthenticationCredentials.builder() + .accessKeyId(TEST_ACCESS_KEY_ID) + .secretKey(TEST_SECRET_KEY) + .region(TEST_REGION) + .build(), AWSAuthenticationCredentialsProvider.builder() + .roleArn(TEST_ROLE_ARN) + .roleSessionName(TEST_ROLESESSION_NAME) + .build() + ); + underTestCredentialsProvider.setAws4Signer(mockAWS4Signer); + underTestCredentialsProvider.setAwsCredentialsProvider(mockAWSCredentialsProvider); + + Request actualSignedRequest = underTestCredentialsProvider.sign(new Request.Builder() + .url("http://api.amazon.com") + .build()); + + verify(mockAWS4Signer) + .sign(signableRequestArgumentCaptor.capture(), any(AWSCredentials.class)); + + SignableRequest actualSignableRequest = signableRequestArgumentCaptor.getValue(); + + assertEquals(((Request)actualSignableRequest.getOriginalRequestObject()).url(), actualSignedRequest.url()); + } + + } diff --git a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSignerTest.java b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSignerTest.java index 10b5982..ce52ef2 100644 --- a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSignerTest.java +++ b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSignerTest.java @@ -1,25 +1,38 @@ package com.amazon.SellingPartnerAPIAA; +import com.squareup.okhttp.Call; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Protocol; import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.ResponseBody; + import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.stream.Stream; -import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; -import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; +import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; + public class LWAAuthorizationSignerTest { private static final String TEST_REFRESH_TOKEN = "rToken"; private static final String TEST_CLIENT_SECRET = "cSecret"; @@ -29,11 +42,18 @@ public class LWAAuthorizationSignerTest { private static final String TEST_SCOPE_2 = SCOPE_MIGRATION_API; private static final String SELLER_TYPE_SELLER = "seller"; private static final String SELLER_TYPE_SELLERLESS = "sellerless"; + private static final MediaType EXPECTED_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); private Request request; private static LWAAuthorizationSigner underTestSeller; private static LWAAuthorizationSigner underTestSellerless; + + @Mock + private OkHttpClient mockOkHttpClient; + + @Mock + private Call mockCall; static { @@ -52,11 +72,12 @@ public class LWAAuthorizationSignerTest { .build()); } - @BeforeEach + @Before @BeforeEach public void init() { request = new Request.Builder() .url("https://www.amazon.com/api") .build(); + MockitoAnnotations.initMocks(this); } @@ -78,8 +99,7 @@ public class LWAAuthorizationSignerTest { @MethodSource("lwaAuthSigner") public void requestLWAAccessTokenFromConfiguration(String sellerType, LWAAuthorizationSigner testAuthSigner) { LWAClient mockLWAClient = mock(LWAClient.class); - ArgumentCaptor lwaAccessTokenRequestMetaArgumentCaptor = ArgumentCaptor.forClass( - LWAAccessTokenRequestMeta.class); + ArgumentCaptor lwaAccessTokenRequestMetaArgumentCaptor = ArgumentCaptor.forClass(LWAAccessTokenRequestMeta.class); when(mockLWAClient.getAccessToken(lwaAccessTokenRequestMetaArgumentCaptor.capture())) .thenReturn("foo"); @@ -128,4 +148,44 @@ public class LWAAuthorizationSignerTest { testAuthSigner.setLwaClient(mockLWAClient); assertNotSame(request, testAuthSigner.sign(request)); } + + @Test + public void returnSignedRequestWithAccessTokenFromLWACache() throws IOException { + LWAClient testLWAClient = new LWAClient(TEST_ENDPOINT); + testLWAClient.setOkHttpClient(mockOkHttpClient); + + when(mockOkHttpClient.newCall(any(Request.class))) + .thenReturn(mockCall); + when(mockCall.execute()) + .thenReturn(buildResponse(200, "Azta|foo", "120")) + .thenReturn(buildResponse(200, "Azta|foo1", "1")); + + LWAAccessTokenCache testLWACache = new LWAAccessTokenCacheImpl(); + LWAAuthorizationSigner testlwaSigner = new LWAAuthorizationSigner(LWAAuthorizationCredentials.builder() + .clientId(TEST_CLIENT_ID) + .clientSecret(TEST_CLIENT_SECRET) + .refreshToken(TEST_REFRESH_TOKEN) + .endpoint(TEST_ENDPOINT) + .build() , testLWACache ); + + testlwaSigner.setLwaClient(testLWAClient); + testLWAClient.setLWAAccessTokenCache(testLWACache); + Request actualSignedRequest = testlwaSigner.sign(request); + Request actualSignedSecondRequest = testlwaSigner.sign(request); + + assertEquals("Azta|foo", actualSignedSecondRequest.header("x-amz-access-token")); + } + + private static Response buildResponse(int code, String accessToken, String expiryInSeconds) { + ResponseBody responseBody = ResponseBody.create(EXPECTED_MEDIA_TYPE, + String.format("{%s:%s,%s:%s}", "access_token", accessToken, "expires_in", expiryInSeconds)); + + return new Response.Builder() + .request(new Request.Builder().url(TEST_ENDPOINT).build()) + .code(code) + .body(responseBody) + .protocol(Protocol.HTTP_1_1) + .message("OK") + .build(); + } } diff --git a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializerTest.java b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializerTest.java index 089f72e..bb25c44 100644 --- a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializerTest.java +++ b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializerTest.java @@ -12,8 +12,8 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; -import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; +import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; public class LWAClientScopesSerializerDeserializerTest { private static final String TEST_SCOPE_1 = SCOPE_NOTIFICATIONS_API; diff --git a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientTest.java b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientTest.java index 4606f14..1b19863 100644 --- a/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientTest.java +++ b/clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientTest.java @@ -30,13 +30,14 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; -import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; -import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; +import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; + @RunWith(MockitoJUnitRunner.class) public class LWAClientTest { private static final String TEST_ENDPOINT = "https://www.amazon.com/api"; @@ -52,13 +53,13 @@ public class LWAClientTest { private static final String SELLER_TYPE_SELLER = "seller"; private static final String SELLER_TYPE_SELLERLESS = "sellerless"; - + @Mock private OkHttpClient mockOkHttpClient; @Mock private Call mockCall; - + private LWAClient underTest; static { @@ -74,17 +75,15 @@ public class LWAClientTest { .clientSecret("cSecret") .grantType("cCredentials") .scopes(new LWAClientScopes(scopesTestSellerless)) - .build(); + .build(); } - @Before - @BeforeEach + @Before @BeforeEach public void init() { MockitoAnnotations.initMocks(this); underTest = new LWAClient(TEST_ENDPOINT); underTest.setOkHttpClient(mockOkHttpClient); - } public static Stream lwaClient(){ @@ -169,10 +168,43 @@ public class LWAClientTest { underTest.getAccessToken(testLwaAccessTokenRequestMeta); }); } - - private static Response buildResponse(int code, String accessToken) { + + //Test for Access Token getting from cache + @Test + public void returnAccessTokenFromCache() throws IOException, InterruptedException { + + when(mockOkHttpClient.newCall(any(Request.class))) + .thenReturn(mockCall); + when(mockCall.execute()) + .thenReturn(buildResponse(200, "Azta|foo", "120")) + .thenThrow(IllegalStateException.class); + underTest.setLWAAccessTokenCache(new LWAAccessTokenCacheImpl()); + + //First call should get from Endpoint + assertEquals("Azta|foo", underTest.getAccessToken(lwaAccessTokenRequestMetaSeller)); + //Second call when the cache is still valid, if it goes to end point it will return IllegalStateException. + assertEquals("Azta|foo", underTest.getAccessToken(lwaAccessTokenRequestMetaSeller)); + } + + @Test + public void returnAccessTokenFromCacheWithExpiry() throws IOException, InterruptedException { + LWAClient client = new LWAClient(TEST_ENDPOINT); + client.setOkHttpClient(mockOkHttpClient); + when(mockOkHttpClient.newCall(any(Request.class))) + .thenReturn(mockCall); + when(mockCall.execute()) + .thenReturn(buildResponse(200, "Azta|foo", "1")) + .thenReturn(buildResponse(200, "Azta|foo1", "1")); + + //First call should get from Endpoint + assertEquals("Azta|foo", client.getAccessToken(lwaAccessTokenRequestMetaSeller)); + //Second call should again go to the endpoint because accesstoken is expired after expiry adjustment. + assertEquals("Azta|foo1", client.getAccessToken(lwaAccessTokenRequestMetaSeller)); + } + + private static Response buildResponse(int code, String accessToken, String expiryInSeconds) { ResponseBody responseBody = ResponseBody.create(EXPECTED_MEDIA_TYPE, - String.format("{%s:%s}", "access_token", accessToken)); + String.format("{%s:%s,%s:%s}", "access_token", accessToken, "expires_in", expiryInSeconds)); return new Response.Builder() .request(new Request.Builder().url(TEST_ENDPOINT).build()) @@ -182,4 +214,8 @@ public class LWAClientTest { .message("OK") .build(); } + + private static Response buildResponse(int code, String accessToken) { + return buildResponse(code, accessToken, "3600"); + } }