diff --git a/clients/sellingpartner-api-frp-helper-java/.gitignore b/clients/sellingpartner-api-frp-helper-java/.gitignore deleted file mode 100644 index 3a576f3..0000000 --- a/clients/sellingpartner-api-frp-helper-java/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -.gradle/ -.idea/ -/build/ -/buildSrc -/gradle/ -/gradlew -/gradlew.bat -/wrapper/ -/target -build.gradle.backup -settings.gradle.backup \ No newline at end of file diff --git a/clients/sellingpartner-api-frp-helper-java/README.md b/clients/sellingpartner-api-frp-helper-java/README.md deleted file mode 100644 index 9f98100..0000000 --- a/clients/sellingpartner-api-frp-helper-java/README.md +++ /dev/null @@ -1,116 +0,0 @@ -## Selling Partner API for Feeds, Uploads and Reports Java Helper - -This library provides helper classes for use with the Selling Partner APIs for Feeds, Uploads and Reports. It is intended for use with the Selling Partner API Client Libraries generated by [Swagger Codegen](https://swagger.io/tools/swagger-codegen/) using the OkHttp library. - -Use this library to encrypt files to be uploaded (Feeds and Uploads), or decrypt files after downloading (Reports) - -## Resources - -To use this library you will need to integrate it with one or more of the following packages: - -##### Selling Partner API Authentication/Authorization Library - -This package is required because it handles the Selling Partner API credentials required by the APIs. You can find it on our GitHub Repository [here](https://github.com/amzn/selling-partner-api-models/tree/main/clients/sellingpartner-api-aa-java). - -##### Selling Partner API for Feeds - -The JSON file can be obtained [here](https://github.com/amzn/selling-partner-api-models/blob/main/models/feeds-api-model/feedsV0.json). - -##### Selling Partner API for Uploads - -The JSON file can be obtained [here](https://github.com/amzn/selling-partner-api-models/blob/main/models/uploads-api-model/uploads.json). - -##### Selling Partner API for Reports - -The JSON file can be obtained [here](https://github.com/amzn/selling-partner-api-models/blob/main/models/reports-api-model/reportsV0.json). - -## Building - -#### Building the Client Libraries - -- Build the client library for each of the API model JSON files using Swagger Codegen. For each file: - -*For example:* - - java -jar C:\SwaggerToCL\swagger-codegen-cli.jar generate -i C:\SwaggerToCL\Feeds.json -l java -t [path to clients\sellingpartner-apiaa-java directory]/resources/swagger-codegen/templates/ -o C:\SwaggerToCL\Feeds_JavaCL - -#### Integrating the AA Library to the Helper via Maven - -1. Build the AA Library and add it as a dependency of the SDKs: - - Navigate to the **clients/sellingpartner-api-aa-java** folder and run: - - mvn package - - This generates a folder named "target". In this folder is a JAR file named **sellingpartnerapi-aa-java-1.0-jar-with-dependencies.jar** (or something similar) and all of the required dependencies. - -2. Install the JAR file in your local Maven repository: - - *For example:* - - mvn install:install-file -Dfile=[path to JAR file in "target" folder] -DgroupId=com.amazon.sellingpartnerapi -DartifactId=sellingpartnerapi-aajava -Dversion=1.0 -Dpackaging=jar - - Keep track of the *groupId*, *artifactId*, and *version* values (which can also be found near the top of the **pom.xml** file in the **clients/sellingpartner-api-aa-java** folder) because you will use them in the steps to follow. - -3. Add a dependency on the AA library in the **pom.xml** of each client library. - - *For example:* - - ``` - - com.amazon.sellingpartnerapi - sellingpartnerapi-aa-java - 1.0 - - ``` -4. Build each client library with: - - ``` - mvn package - ``` - -5. Install the Feeds, Uploads and Reports client libraries in your local Maven repository. - - *For example:* - - mvn install:install-file -Dfile=[path to JAR file in "target" folder] -DgroupId=com.amazon.sellingpartnerapi -DartifactId=sellingpartnerapi-feeds-java -Dversion=1.0 -Dpackaging=jar - -6. Confirm that the *artifactId* of the Feeds, Uploads and Reports libraries in the **pom.xml** of the Helper matches the ones used when installing each library. - - *For example:* - - ``` - - com.amazon.sellingpartnerapi - sellingpartnerapi-feeds-java - 1.0 - - ``` - -7. Navigate to the **SellingPartnerApiFeedsUploadsReportsJavaHelper** folder and run: - - ``` - mvn package - ``` - *The Feeds, Uploads and Reports Java Helper SDK comes with two building options: Maven and Gradle, however, we only detail how to complete the Maven build above.* - -## License -Swagger Codegen templates are subject to the [Swagger Codegen License](https://github.com/swagger-api/swagger-codegen#license). - -All other work licensed as follows: - -Copyright Amazon.com Inc. or its affiliates. - -All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this library except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/clients/sellingpartner-api-frp-helper-java/build.gradle b/clients/sellingpartner-api-frp-helper-java/build.gradle deleted file mode 100644 index b38dabc..0000000 --- a/clients/sellingpartner-api-frp-helper-java/build.gradle +++ /dev/null @@ -1,107 +0,0 @@ - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.3.+' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' - } -} - -plugins { - id 'idea' - id 'eclipse' - id 'java' - id 'maven' -} - -group = 'io.swagger' -version = '1.0.0' - - -repositories { - jcenter() -} - -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 - - - -/*sourceSets { - main { - java { - srcDirs = ['src/main/java'] - } - resources { - srcDirs = ['src/main/resources'] - } - } - - test { - java { - srcDirs = ['src/test/java'] - } - resources { - srcDirs = ['src/test/resources'] - } - } -}*/ - - -install { - repositories.mavenInstaller { - pom.artifactId = 'feeds-uploads-reports-java-helper' - } -} - -task execute(type: JavaExec) { - main = System.getProperty('mainClass') - classpath = sourceSets.main.runtimeClasspath -} - -test { - jvmArgs('-Djavax.net.ssl.trustStore=resources/main/certs/InternalAndExternalTrustStore.jks', - '-Djavax.net.ssl.trustStorePassword=amazon') -} - -test.dependsOn processTestResources - -// TO get this to work, -// You must add the sellingpartner-api-aa-java.jar (with dependencies) to your -// /libs/ folder!!!!! -dependencies { - compileOnly 'org.projectlombok:lombok:1.18.8' - annotationProcessor 'org.projectlombok:lombok:1.18.8' - implementation 'io.swagger:swagger-annotations:1.5.17' - implementation 'com.squareup.okhttp:okhttp:2.7.5' - implementation 'com.squareup.okhttp:logging-interceptor:2.7.5' - implementation 'com.google.code.gson:gson:2.8.1' - implementation 'io.gsonfire:gson-fire:1.8.0' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.10.2' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.2' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.10.2' - implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.12' - implementation group: 'org.apache.directory.studio', name: 'org.apache.commons.io', version: '2.4' - implementation group: 'com.google.guava', name: 'guava', version: '28.2-jre' - implementation group: 'joda-time', name: 'joda-time', version: '2.10.5' - - // https://mvnrepository.com/artifact/org.threeten/threetenbp - implementation group: 'org.threeten', name: 'threetenbp', version: '1.3.5' - // https://mvnrepository.com/artifact/junit/junit - testCompile group: 'junit', name: 'junit', version: '4.12' - - implementation 'com.amazonaws:aws-java-sdk-signer:1.11.610' - implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.squareup.okhttp:okhttp:2.7.5' - implementation 'org.junit.jupiter:junit-jupiter-engine:5.0.0' - implementation 'org.junit.jupiter:junit-jupiter-params:5.3.2' - implementation 'org.junit.jupiter:junit-jupiter-migrationsupport:5.5.1' - implementation 'org.mockito:mockito-core:3.0.0' - implementation 'org.apache.commons:commons-lang3:3.9' - implementation 'org.apache.httpcomponents:httpclient:4.5.9' - - implementation fileTree(dir: 'libs', include: '*.jar') - -} diff --git a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartner-api-aa-java-1.0.jar b/clients/sellingpartner-api-frp-helper-java/libs/sellingpartner-api-aa-java-1.0.jar deleted file mode 100644 index 31fd121..0000000 Binary files a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartner-api-aa-java-1.0.jar and /dev/null differ diff --git a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-feeds-java-1.0.jar b/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-feeds-java-1.0.jar deleted file mode 100644 index 36eee17..0000000 Binary files a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-feeds-java-1.0.jar and /dev/null differ diff --git a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-reports-java-1.0.jar b/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-reports-java-1.0.jar deleted file mode 100644 index b24866c..0000000 Binary files a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-reports-java-1.0.jar and /dev/null differ diff --git a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-uploads-java-1.0.jar b/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-uploads-java-1.0.jar deleted file mode 100644 index 965d38a..0000000 Binary files a/clients/sellingpartner-api-frp-helper-java/libs/sellingpartnerapi-uploads-java-1.0.jar and /dev/null differ diff --git a/clients/sellingpartner-api-frp-helper-java/pom.xml b/clients/sellingpartner-api-frp-helper-java/pom.xml deleted file mode 100644 index 6115587..0000000 --- a/clients/sellingpartner-api-frp-helper-java/pom.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - 4.0.0 - io.swagger - feeds-uploads-reports-java-helper - 1.0.0 - - - org.projectlombok - lombok - 1.18.12 - provided - - - io.swagger - swagger-annotations - 1.5.17 - - - com.squareup.okhttp - okhttp - 2.7.5 - - - com.squareup.okhttp - logging-interceptor - 2.7.5 - - - io.gsonfire - gson-fire - 1.8.0 - - - com.fasterxml.jackson.core - jackson-core - 2.10.2 - - - com.fasterxml.jackson.core - jackson-databind - 2.10.2 - - - com.fasterxml.jackson.core - jackson-annotations - 2.10.2 - - - org.apache.httpcomponents - httpclient - 4.5.12 - - - org.apache.directory.studio - org.apache.commons.io - 2.4 - - - com.google.guava - guava - 28.2-jre - - - joda-time - joda-time - 2.10.5 - - - org.threeten - threetenbp - 1.3.5 - - - com.amazonaws - aws-java-sdk-signer - 1.11.610 - - - com.google.code.gson - gson - 2.8.5 - - - org.junit.jupiter - junit-jupiter-engine - 5.0.0 - - - org.junit.jupiter - junit-jupiter-params - 5.3.2 - - - org.junit.jupiter - junit-jupiter-migrationsupport - 5.5.1 - - - org.mockito - mockito-core - 3.0.0 - - - org.apache.commons - commons-lang3 - 3.9 - - - junit - junit - 4.12 - test - - - com.amazon.sellingpartnerapi - sellingpartnerapi-aa-java - 1.0 - - - com.amazon.sellingpartnerapi - sellingpartnerapi-feeds-java - 1.0 - - - com.amazon.sellingpartnerapi - sellingpartnerapi-reports-java - 1.0 - - - com.amazon.sellingpartnerapi - sellingpartnerapi-uploads-java - 1.0 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - - - org.projectlombok - lombok - 1.18.12 - - - - - - - - - 1.8 - ${java.version} - ${java.version} - 1.0.0 - UTF-8 - - diff --git a/clients/sellingpartner-api-frp-helper-java/settings.gradle b/clients/sellingpartner-api-frp-helper-java/settings.gradle deleted file mode 100644 index 5b80a16..0000000 --- a/clients/sellingpartner-api-frp-helper-java/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "feeds-uploads-reports-java-helper" \ No newline at end of file diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/DocumentValidatorInputStream.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/DocumentValidatorInputStream.java deleted file mode 100644 index ba6caeb..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/DocumentValidatorInputStream.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.amazon.spapi.sequencing.client; - -import com.google.common.base.Preconditions; -import lombok.extern.apachecommons.CommonsLog; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -/** - * An input stream that validates the integrity of the document when you close the stream. - */ -@CommonsLog -public class DocumentValidatorInputStream extends FilterInputStream { - - private final byte[] expectedSha256sum; - private final MessageDigest sha256digest; - - /** - * Creates an InputStream that will calculate the sha256digest and validate it upon closing the stream. - * @param inputStream - * the inputStream to calculate the sha256sum of. - * @param expectedSha256sum - * the expected sha256sum of the document being streamed if one exists. Leave null if document does not - * have a sha256sum to validate against. - * @throws NoSuchAlgorithmException - * when unable to create a SHA-256 instance of MessageDigest - */ - public DocumentValidatorInputStream(InputStream inputStream, byte[] expectedSha256sum) - throws NoSuchAlgorithmException { - super(null); - Preconditions.checkArgument(inputStream != null, "inputStream must not be null"); - Preconditions.checkArgument(expectedSha256sum != null, "expectedSha256sum must not be null"); - - this.expectedSha256sum = Arrays.copyOf(expectedSha256sum, expectedSha256sum.length); - sha256digest = MessageDigest.getInstance("SHA-256"); - this.in = new DigestInputStream(inputStream, sha256digest); - } - - /** - * Validates that the streamed document was not tampered with. - * @throws IOException - * if the streamed document was completely read and is not valid or an IOException occurs while closing - * the stream. - */ - @Override - public void close() throws IOException { - try { - if (!MessageDigest.isEqual(expectedSha256sum, sha256digest.digest()) && super.read() == -1) { - throw new ValidationFailureException("The streamed document was not valid."); - } - } finally { - try { - super.close(); - } catch (IOException e) { - log.warn(String.format("Exception while closing the validation string: %s", e.getMessage()), e); - } - } - } -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/DownloadsSequencer.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/DownloadsSequencer.java deleted file mode 100644 index 84967ca..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/DownloadsSequencer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.amazon.spapi.sequencing.client; - -import com.amazon.spapi.sequencing.crypto.EncryptionException; -import io.swagger.client.model.EncryptionDetails; -import io.swagger.client.model.UploadDestination; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -public interface DownloadsSequencer { - - InputStream downloadAndDecryptReportContent(UploadDestination destination, boolean isGzipped) throws IOException; - - InputStream decryptReportContent(File reportFile, EncryptionDetails details, boolean isGzipped) - throws IOException, EncryptionException; - - void downloadDecryptThenWriteFile(UploadDestination destination, boolean isGzipped, File fileToWriteTo) - throws IOException; - - void decryptThenWriteFile(File reportFile, EncryptionDetails details, boolean isGzipped, File fileToWriteTo) - throws IOException, EncryptionException; -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/SpoolingLimitExceededException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/SpoolingLimitExceededException.java deleted file mode 100644 index f3522fc..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/SpoolingLimitExceededException.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.amazon.spapi.sequencing.client; - -import java.io.IOException; - -/** - * Exception thrown when spooling size limits exceeded. - */ -public class SpoolingLimitExceededException extends IOException { - - /** - * Constructor that takes in a message. - * @param message - * the message describing the exception - */ - public SpoolingLimitExceededException(String message) { - super(message); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/UploadDetails.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/UploadDetails.java deleted file mode 100644 index 2fa10cb..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/UploadDetails.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.amazon.spapi.sequencing.client; - - -import lombok.AccessLevel; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; -import lombok.experimental.FieldDefaults; - -@Builder -@Getter -@ToString -@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) -@EqualsAndHashCode -public class UploadDetails { - String sha256Sum; - String uploadDestinationId; -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/UploadsSequencer.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/UploadsSequencer.java deleted file mode 100644 index 8ed6b52..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/UploadsSequencer.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.amazon.spapi.sequencing.client; - -import io.swagger.client.ApiException; -import io.swagger.client.model.UploadDestination; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; - -/** - * A thick client used to simplify the use of SP-API Uploads. - */ -public interface UploadsSequencer { - /** - * Retrieve a document and related metadata from SP-API Uploads. - * @param obfuscatedCustomerId the obfuscated customer ID of the document owner. - * @param documentId the document ID of the document being retrieved. - * @return A DownloadBundle to be used to retrieve the requested document. - * @throws RuntimeException if an exceptional case was encountered but the action should be retried. - * @throws RuntimeException if an exceptional case was encountered and the action should not be retried. - * @throws RuntimeException if an exceptional case was encountered and the cause was unexpected. - */ - // DownloadBundle get(String obfuscatedCustomerId, String documentId) throws RuntimeException; - - UploadDestination createDestination(String feedType, String contentType, long documentLength) - throws ApiException; - /** - * Delivers a document and related metadata to SP-API Uploads. - * @param uploadDestination metadata for the document being uploaded. - * @param contentType the content type - * @param documentLength the length in bytes of the document. - * @param inputStream the InputStream from which the document should be read for upload. - * @return A String document ID. - * @throws RuntimeException if an exceptional case was encountered but the action should be retried. - * @throws RuntimeException if an exceptional case was encountered and the action should not be retried. - * @throws RuntimeException if an exceptional case was encountered and the cause was unexpected. - */ - UploadDetails uploadToDestination(UploadDestination uploadDestination, String contentType, long documentLength, - InputStream inputStream) throws RuntimeException; - - UploadDetails createDestinationAndUpload(String feedType, String contentType, File fileName) - throws ApiException, FileNotFoundException; - - UploadDetails createDestinationAndUpload(String feedType, String contentType, long documentLength, - InputStream stream) throws ApiException; -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/ValidationFailureException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/ValidationFailureException.java deleted file mode 100644 index 4957841..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/ValidationFailureException.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.amazon.spapi.sequencing.client; - - -/** - * Exception thrown if document integrity has been compromised during handling and is not considered safe to consume. - */ -public class ValidationFailureException extends RuntimeException { - - /** - * Constructor taking both a message and cause. - * @param message - * the message - * @param cause - * the cause - */ - public ValidationFailureException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message - * the message - */ - public ValidationFailureException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause - * the cause - */ - public ValidationFailureException(Throwable cause) { - super(cause); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/AccessMechanism.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/AccessMechanism.java deleted file mode 100644 index 167743d..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/AccessMechanism.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.amazon.spapi.sequencing.client.impl; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.experimental.FieldDefaults; - -@AllArgsConstructor -@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) -@Getter -public enum AccessMechanism { - UPLOAD("upload"), DOWNLOAD("download"); - - private String description; -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/DownloadsSequencerImpl.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/DownloadsSequencerImpl.java deleted file mode 100644 index 6bc524a..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/DownloadsSequencerImpl.java +++ /dev/null @@ -1,307 +0,0 @@ -package com.amazon.spapi.sequencing.client.impl; - -import com.amazon.spapi.sequencing.client.DownloadsSequencer; -import com.amazon.spapi.sequencing.crypto.EncryptionException; -import com.google.common.base.Strings; - -import io.swagger.client.model.EncryptionDetails; -import io.swagger.client.model.UploadDestination; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.experimental.FieldDefaults; -import lombok.extern.apachecommons.CommonsLog; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.util.EntityUtils; - -import javax.crypto.Cipher; -import java.io.*; -import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; - -import static com.amazon.spapi.sequencing.client.impl.SequencerHelper.S3_IAD_ENDPOINT; -import static com.amazon.spapi.sequencing.client.impl.SequencerHelper.S3_SEA_ENDPOINT; - -@Builder -@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) -@CommonsLog -public class DownloadsSequencerImpl implements DownloadsSequencer { - @Builder.Default - private final SequencerHelper helper = SequencerHelper.defaultSequencerHelper(); - - @Override - public InputStream downloadAndDecryptReportContent(UploadDestination destination, boolean isGzipped) { - boolean hasError = false; - InputStream result = null; - String truncatedUrl = null; - try { - - String url = destination.getUrl(); - - if (Strings.isNullOrEmpty(url)) { - throw new RuntimeException("The returned download URL is null or empty."); - } - - // Acquire the file - HttpClient httpClient = helper.getHttpClientFactory().newGetClient(); - HttpGet httpGet = new HttpGet(url); - - truncatedUrl = helper.removeQueryFromUrl(url); - log.info(String.format("Downloading document with destination ID %s using URL %s (signing info redacted)", - destination.getUploadDestinationId(), truncatedUrl - )); - HttpResponse httpResponse = httpClient.execute(httpGet); - if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) { - boolean isS3UsStandardRegion = false; - if (url.contains(S3_IAD_ENDPOINT)) { - isS3UsStandardRegion = true; - url = url.replaceFirst(Pattern.quote(S3_IAD_ENDPOINT), S3_SEA_ENDPOINT); - } else if (url.contains(S3_SEA_ENDPOINT)) { - isS3UsStandardRegion = true; - url = url.replaceFirst(Pattern.quote(S3_SEA_ENDPOINT), S3_IAD_ENDPOINT); - } - - if (isS3UsStandardRegion) { - log.info("S3 Returned a 404 in the US Standard Region." - + " This likely indicates issues with eventual consistency and should be rare." - + " Attempting to find object in peer region." - + " See: https://w.amazon.com/index.php/S3#What_About_Eventual_Consistency.3F"); - - // Consume the previous response so that the HTTP client connection can be reused - EntityUtils.consumeQuietly(httpResponse.getEntity()); - - httpGet = new HttpGet(url); - httpResponse = httpClient.execute(httpGet); - } - } - - // Handle error responses - helper.handleErrorResponseCodes(httpResponse, AccessMechanism.DOWNLOAD); - - HttpEntity entity = httpResponse.getEntity(); - if (entity == null) { - throw new RuntimeException("The HTTP store returned success but no document."); - } - result = entity.getContent(); - result = decryptReportContent(entity.getContent(), destination.getEncryptionDetails(), isGzipped); - return result; - } catch (ClientProtocolException e) { - // Indicates an error in the HTTP protocol - hasError = true; - throw new RuntimeException( - "Unable to download the file.", e); - } catch (EncryptionException e) { - // Unable to perform cipher operations - hasError = true; - throw new RuntimeException( - "Unable to perform cipher operations.", e); - } catch (IOException e) { - hasError = true; - // Unable to download the file or operate on the stream - throw new RuntimeException( - String.format("Unable to download the file using URL %s (signing info redacted) " - + "or manipulate the InputStream.", truncatedUrl), - e - ); - } finally { - // kill the stream if we are throwing an exception - if (hasError) { - IOUtils.closeQuietly(result); - } - } - } - - @Override - public InputStream decryptReportContent(File reportFile, EncryptionDetails details, boolean isGzipped) - throws IOException, EncryptionException { - return decryptReportContent(new FileInputStream(reportFile), details, isGzipped); - } - - private InputStream decryptReportContent(InputStream input, EncryptionDetails details, boolean isGzipped) - throws EncryptionException, IOException { - InputStream resultStream = input; - // If encrypted, decipher the stream - if (details != null && EncryptionDetails.StandardEnum.AES.equals(details.getStandard())) { - try { - resultStream = helper.buildCipherInputStream(details, resultStream, Cipher.DECRYPT_MODE); - - } catch (IllegalArgumentException | IllegalStateException e) { - throw new EncryptionException(e); - } - } - - // Determine if the stream should be unzipped as well - if (isGzipped) { - resultStream = new GZIPInputStream(resultStream); - } - - return resultStream; - } - - @Override - public void downloadDecryptThenWriteFile(UploadDestination destination, boolean isGzipped, File fileToWriteTo) - throws IOException { - try ( - InputStream result = downloadAndDecryptReportContent(destination, isGzipped); - FileOutputStream output = new FileOutputStream(fileToWriteTo); - ) { - IOUtils.copyLarge(result, output); - } - } - - @Override - public void decryptThenWriteFile(File reportFile, EncryptionDetails details, boolean isGzipped, - File fileToWriteTo) throws IOException, EncryptionException { - try ( - InputStream result = decryptReportContent(reportFile,details, isGzipped); - FileOutputStream output = new FileOutputStream(fileToWriteTo); - ) { - IOUtils.copyLarge(result, output); - } - - } - - /* - - - - */ - /** {@inheritDoc} *//* - - @Override - public DownloadBundle get(String obfuscatedCustomerId, String documentId) - throws RuntimeException, RuntimeException, RuntimeException { - Preconditions.checkArgument(!Strings.isNullOrEmpty(obfuscatedCustomerId), - "obfuscatedMerchantCustomerId is null or empty."); - Preconditions.checkArgument(!Strings.isNullOrEmpty(documentId), "documentId is null or empty."); - Preconditions.checkState(httpClientFactory != null, "The httpClientFactory is null."); - - // We need the expanded scope so that we can close the stream in the case of an exception. - InputStream resultStream = null; - DownloadBundle result = null; - String truncatedUrl = null; - try { - // Acquire the document metadata, document receipt URL and encryption details from Tortuga. - GetDownloadUrlInput getUrlInput = new GetDownloadUrlInput(); - getUrlInput.setDocumentId(documentId); - getUrlInput.setObfuscatedCustomerId(obfuscatedCustomerId); - GetDownloadUrlOutput getUrlOutput = - GetDownloadUrlSyncJobBuilder.build(getUrlInput, metricsStrategy, client).run(); - DocumentMetadata metadata = getUrlOutput.getDocumentMetadata(); - if (metadata == null) { - throw new RuntimeException("The returned document metadata is null."); - } - String url = getUrlOutput.getUrl(); - if (Strings.isNullOrEmpty(url)) { - throw new RuntimeException("The returned download URL is null or empty."); - } - Date creationTimestamp = getUrlOutput.getCreationTimestamp(); - if (creationTimestamp == null) { - throw new RuntimeException("The returned document creationDate is null."); - } - - getUrlOutput.setDocumentMetadata(null); - - // Acquire the file - HttpClient httpClient = httpClientFactory.newGetClient(); - HttpGet httpGet = new HttpGet(url); - - truncatedUrl = removeQueryFromUrl(url); - log.info(String.format("Downloading document with ID %s using URL %s (signing info redacted)", - documentId, truncatedUrl)); - long startTimeMillis = System.nanoTime() / 1000000L; - HttpResponse httpResponse = httpClient.execute(httpGet); - if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) { - boolean isS3UsStandardRegion = false; - if (url.contains(S3_IAD_ENDPOINT)) { - isS3UsStandardRegion = true; - url = url.replaceFirst(Pattern.quote(S3_IAD_ENDPOINT), S3_SEA_ENDPOINT); - } else if (url.contains(S3_SEA_ENDPOINT)) { - isS3UsStandardRegion = true; - url = url.replaceFirst(Pattern.quote(S3_SEA_ENDPOINT), S3_IAD_ENDPOINT); - } - - if (isS3UsStandardRegion) { - log.info("S3 Returned a 404 in the US Standard Region." - + " This likely indicates issues with eventual consistency and should be rare." - + " Attempting to find object in peer region." - + " See: https://w.amazon.com/index.php/S3#What_About_Eventual_Consistency.3F"); - - // Consume the previous response so that the HTTP client connection can be reused - EntityUtils.consumeQuietly(httpResponse.getEntity()); - - httpGet = new HttpGet(url); - httpResponse = httpClient.execute(httpGet); - } - } - long endTimeMillis = System.nanoTime() / 1000000L; - serviceMetrics.addTime(S3_GET_TIME_METRIC, endTimeMillis - startTimeMillis, SI.MILLI(SI.SECOND)); - - // Handle error responses - handleErrorResponseCodes(httpResponse); - - HttpEntity entity = httpResponse.getEntity(); - if (entity == null) { - throw new RuntimeException("The HTTP store returned success but no document."); - } - resultStream = entity.getContent(); - - // If this document has a sha256sum in the metadata, wrap with DigestInputStream to calculate the sha256 sum - // for validation of document integrity - String sha256sumStr = getUrlOutput.getSha256sum(); - if (sha256sumStr != null) { - resultStream = new DocumentValidatorInputStream(resultStream, BASE64_DECODER.decode(sha256sumStr)); - } - - // If encrypted, decipher the stream - if (getUrlOutput.isEncrypted()) { - try { - EncryptionInfo encryptionInfo = getUrlOutput.getEncryptionInfo(); - resultStream = buildCipherInputStream(encryptionInfo, resultStream, Cipher.DECRYPT_MODE); - - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (IllegalStateException e) { - throw new RuntimeException(e); - } - } - - // Determine if the stream should be unzipped as well - if (metadata.isDocumentGzipped()) { - resultStream = new GZIPInputStream(resultStream); - } - - // build the result - result = new DownloadBundle(metadata, resultStream, creationTimestamp.getTime()); - } catch (ClientProtocolException e) { - // Indicates an error in the HTTP protocol - throw new RuntimeException( - "Unable to download the file.", e); - } catch (EncryptionException e) { - // Unable to perform cipher operations - throw new RuntimeException( - "Unable to perform cipher operations.", e); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Couldn't get a Sha256 Message Digest instance", e); - } catch (IOException e) { - // Unable to download the file or operate on the stream - throw new RuntimeException( - String.format("Unable to download the file using URL %s (signing info redacted) " - + "or manipulate the InputStream.", truncatedUrl), - e); - } finally { - // kill the stream if we are throwing an exception - if (result == null) { - IOUtils.closeQuietly(resultStream); - } - } - return result; - } -*/ - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/HttpClientFactory.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/HttpClientFactory.java deleted file mode 100644 index 6ac32e7..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/HttpClientFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.amazon.spapi.sequencing.client.impl; - -import org.apache.http.client.HttpClient; - -/** - * Constructs purpose built HttpClient instances. - */ -public interface HttpClientFactory { - /** - * Constructs an HttpClient instance configured for GET requests. - * @return an HttpClient instance. - */ - HttpClient newGetClient(); - /** - * Constructs an HttpClient instance configured for PUT requests. - * @return an HttpClient instance. - */ - HttpClient newPutClient(); -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/SPAPIUploadDestinationsHttpClientFactory.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/SPAPIUploadDestinationsHttpClientFactory.java deleted file mode 100644 index ac8d225..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/SPAPIUploadDestinationsHttpClientFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.amazon.spapi.sequencing.client.impl; - -import com.google.common.collect.ImmutableMap; -import lombok.Setter; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.CoreProtocolPNames; - -import java.util.Map; - -/** - * HttpClientFactory implementation for SP-API Upload Destinations. - */ -@Setter -public class SPAPIUploadDestinationsHttpClientFactory implements HttpClientFactory { - private static final Map DEFAULT_CONNECTION_PARAMETERS; - - static { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); - builder.put(CoreConnectionPNames.SO_TIMEOUT, 10000); - builder.put(CoreConnectionPNames.SO_KEEPALIVE, Boolean.TRUE); - - DEFAULT_CONNECTION_PARAMETERS = builder.build(); - } - - /** - *http://hc.apache.org/httpcomponents-core-4.2.x/httpcore/apidocs/org/apache/http/params/CoreConnectionPNames.html. - */ - private Map getClientConnectionParametersOverrides = DEFAULT_CONNECTION_PARAMETERS; - - /** - *http://hc.apache.org/httpcomponents-core-4.2.x/httpcore/apidocs/org/apache/http/params/CoreConnectionPNames.html. - */ - private Map putClientConnectionParametersOverrides = DEFAULT_CONNECTION_PARAMETERS; - - private String userAgent; - - /** {@inheritDoc} */ - @Override - public HttpClient newGetClient() { - return newHttpClient(getClientConnectionParametersOverrides); - } - - /** {@inheritDoc} */ - @Override - public HttpClient newPutClient() { - return newHttpClient(putClientConnectionParametersOverrides); - } - - /** - * Helper function to create a new DefaultHttpClient with the given connectionParameter Overrides. - * @param connectionParameters used to override the default properties of the HttpClient. - * @return a new, configured HttpClient. - */ - private HttpClient newHttpClient(Map connectionParameters) { - // TODO: evaluate necessity - // Preconditions.checkState(!Strings.isNullOrEmpty(userAgent), "You must supply a User-Agent."); - - DefaultHttpClient client = new DefaultHttpClient(); - for (Map.Entry param : connectionParameters.entrySet()) { - client.getParams().setParameter(param.getKey(), param.getValue()); - } - - if (userAgent != null) { - client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, userAgent); - } - return client; - } -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/SequencerHelper.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/SequencerHelper.java deleted file mode 100644 index 45bfb5f..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/SequencerHelper.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.amazon.spapi.sequencing.client.impl; - -import com.amazon.spapi.sequencing.crypto.CryptoProvider; -import com.amazon.spapi.sequencing.crypto.EncryptionException; -import com.amazon.spapi.sequencing.crypto.EncryptionMaterials; -import com.amazon.spapi.sequencing.crypto.SymmetricCryptoProvider; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import io.swagger.client.model.EncryptionDetails; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.experimental.FieldDefaults; -import lombok.extern.apachecommons.CommonsLog; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpResponseException; -import org.apache.http.util.EntityUtils; - -import javax.crypto.CipherInputStream; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Base64; -import java.util.Objects; - -@Builder -@Getter -@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) -@CommonsLog -public class SequencerHelper { - - static final String SHA_256 = "SHA-256"; - static final String MD5 = "MD5"; - static final String S3_IAD_ENDPOINT = "s3-external-1.amazonaws.com"; - static final String S3_SEA_ENDPOINT = "s3-external-2.amazonaws.com"; - static final String S3_GET_TIME_METRIC = "S3GetTime"; - static final String S3_PUT_TIME_METRIC = "S3PutTime"; - static final String REQUIRED_KEY_ALGORITHM = "AES"; - static final int AES_BLOCK_SIZE = 16; - static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); - static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); - - private CryptoProvider cryptoProvider; - - private HttpClientFactory httpClientFactory; - - public static SequencerHelper defaultSequencerHelper() { - return SequencerHelper.builder() - .cryptoProvider(new SymmetricCryptoProvider()) - .httpClientFactory(new SPAPIUploadDestinationsHttpClientFactory()) - .build(); - } - - /** - * Remove the query string from a signed S3 URL to hide access key, signature, expiration info when logging. - * - * @param signedS3Url Signed S3 URL to truncate - * @return truncated URL - */ - String removeQueryFromUrl(String signedS3Url) { - if (signedS3Url == null) { - return null; - } - - try { - URL url = new URL(signedS3Url); - - if (url.getQuery() == null) { - return signedS3Url; - } else { - return signedS3Url.replace(url.getQuery(), ""); - } - } catch (MalformedURLException e) { - return null; - } - } - - - /** - * Wraps the provided InputStream in a CipherInputStream in DECRYPT mode. - * - * @param encryptionDetails The EncryptionInfo to use in constructing the Cipher. - * @param stream The InputStream to wrap. - * @param mode The cipher mode. - * @return The wrapped InputStream - * @throws EncryptionException if initializing the Cipher fails. - */ - InputStream buildCipherInputStream(EncryptionDetails encryptionDetails, InputStream stream, int mode) - throws EncryptionException { - Preconditions.checkArgument(!Objects.isNull(encryptionDetails), "Encryption details missing"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(encryptionDetails.getInitializationVector()), - "iv is null or empty." - ); - Preconditions.checkArgument(!Strings.isNullOrEmpty(encryptionDetails.getKey()), "key is null or empty."); - Preconditions.checkState(cryptoProvider != null, "The CryptoProvider must be non-null."); - - String key = encryptionDetails.getKey(); - String iv = encryptionDetails.getInitializationVector(); - - EncryptionMaterials materials = new EncryptionMaterials(new SecretKeySpec(BASE64_DECODER.decode(key), - REQUIRED_KEY_ALGORITHM - ), BASE64_DECODER.decode(iv)); - return new CipherInputStream(stream, cryptoProvider.getInitializedCipher(mode, materials)); - } - - - /** - * Examine the HttpResponse object and throw appropriate exceptions for non-success status - * codes. - * - * @param httpResponse the HttpResponse object to examine. - * @throws RuntimeException if the status code is not 200 level or 400 level (except 403 errors). - * @throws RuntimeException if the HttpResponse, or its nested StatusLine object are null. Or if the status - * code is a 400 level HTTP response code (except 403 errors, which are retriable). - */ - void handleErrorResponseCodes(HttpResponse httpResponse, AccessMechanism accessMechanism) throws RuntimeException { - // Handle error responses - if (httpResponse == null || httpResponse.getStatusLine() == null) { - throw new RuntimeException("Unable to " + accessMechanism.getDescription() + " the document."); - } - int status = httpResponse.getStatusLine().getStatusCode(); - log.info(String.format("Document " + accessMechanism.getDescription() + " returned status code %d", status)); - int statusRange = status / 100; - if (statusRange != 2) { // Not 2XX - HttpEntity entity = httpResponse.getEntity(); - if (entity != null) { - try { - log.warn(EntityUtils.toString(entity, "UTF-8")); - } catch (IOException e) { - log.warn("Got IOException when trying to decode response body into a String", e); - } - } - String reason = httpResponse.getStatusLine().getReasonPhrase(); - HttpResponseException error = new HttpResponseException(status, reason); - // A 403 response is typically due to the signed URL expiring before use. - // The root cause of the expiration is usually clock skew, so treat this as retriable. - if (status == HttpStatus.SC_FORBIDDEN) { - throw new RuntimeException( - String.format("A 403 response was returned. This typically indicates that the signed URL " - + "expired before use (usually due to clock skew). %s %s", status, reason), - error - ); - } else if (statusRange == 4) { // 4XX - throw new RuntimeException( - String.format("A 400 level response was returned: %s %s", status, reason), error); - } else { // 1XX, 3XX, 5XX - throw new RuntimeException( - String.format("A non 200/400 level response was returned: %s %s", status, reason), error); - } - } - } -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/UploadsSequencerImpl.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/UploadsSequencerImpl.java deleted file mode 100644 index 9a09842..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/impl/UploadsSequencerImpl.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.amazon.spapi.sequencing.client.impl; - -import com.amazon.spapi.sequencing.client.UploadDetails; -import com.amazon.spapi.sequencing.client.UploadsSequencer; -import com.amazon.spapi.sequencing.crypto.EncryptionException; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import io.swagger.client.ApiException; -import io.swagger.client.api.UploadsApi; -import io.swagger.client.model.CreateUploadDestinationResponse; -import io.swagger.client.model.EncryptionDetails; -import io.swagger.client.model.UploadDestination; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.experimental.FieldDefaults; -import lombok.extern.apachecommons.CommonsLog; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.InputStreamEntity; - -import javax.crypto.Cipher; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * This implementation performs the expected crypto and compression work on the provided streams. - */ -@Builder -@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) -@CommonsLog -public class UploadsSequencerImpl implements UploadsSequencer { - - @Builder.Default - private final SequencerHelper helper = SequencerHelper.defaultSequencerHelper(); - - private UploadsApi uploadsApi; - - /** - * {@inheritDoc} - */ - @Override - public UploadDetails uploadToDestination(UploadDestination uploadDestination, String contentType, - long documentLength, - InputStream inputStream) throws RuntimeException { - Preconditions.checkArgument(uploadDestination != null, "documentMetadata is null."); - Preconditions.checkArgument(inputStream != null, "inputStream is null."); - Preconditions.checkArgument(documentLength >= 0, "documentLength must be at least zero."); - Preconditions.checkState(helper.getHttpClientFactory() != null, "The httpClientFactory is null."); - - String truncatedUrl = null; - try { - - final String uploadDestinationId = uploadDestination.getUploadDestinationId(); - if (Strings.isNullOrEmpty(uploadDestinationId)) { - throw new RuntimeException("The returned uploadDestinationId is null or empty."); - } - EncryptionDetails encryptionDetails = uploadDestination.getEncryptionDetails(); - if (encryptionDetails == null) { - throw new RuntimeException("The returned encryptionDetails is null."); - } - final String url = uploadDestination.getUrl(); - if (Strings.isNullOrEmpty(url)) { - throw new RuntimeException("The returned upload URL is null or empty."); - } - - // Wrap the stream with a CipherStream - inputStream = helper.buildCipherInputStream(encryptionDetails, inputStream, Cipher.ENCRYPT_MODE); - - // Wrap the cipher stream with a sha256 digest stream. - DigestInputStream sha256sumStream = new DigestInputStream(inputStream, MessageDigest.getInstance( - SequencerHelper.SHA_256)); - inputStream = sha256sumStream; - - truncatedUrl = helper.removeQueryFromUrl(url); - - // Put the file - HttpClient httpClient = helper.getHttpClientFactory().newPutClient(); - HttpPut httpPut = new HttpPut(url); - // This content length calculation is specific to AES - long contentLength = (documentLength / SequencerHelper.AES_BLOCK_SIZE + 1) * SequencerHelper.AES_BLOCK_SIZE; - // This sets the Content-Length header since we specified the contentLength - HttpEntity document = new InputStreamEntity(inputStream, contentLength); - httpPut.setHeader("Content-Type", contentType); - httpPut.setEntity(document); - - log.info(String.format("Uploading document with ID %s using URL %s (signing info redacted)", - uploadDestinationId, truncatedUrl - )); - HttpResponse httpResponse = httpClient.execute(httpPut); - - // Handle error responses - helper.handleErrorResponseCodes(httpResponse, AccessMechanism.UPLOAD); - log.info(String.format("Successfully uploaded document with ID %s", uploadDestinationId)); - - byte[] sha256sumDigest = sha256sumStream.getMessageDigest().digest(); - String sha256sum = SequencerHelper.BASE64_ENCODER.encodeToString(sha256sumDigest); - - - return UploadDetails.builder() - .sha256Sum(sha256sum) - .uploadDestinationId(uploadDestinationId) - .build(); - } catch (ClientProtocolException e) { - // Indicates an error in the HTTP protocol - throw new RuntimeException("Unable to upload the file.", e); - } catch (EncryptionException e) { - // Unable to perform cipher operations - throw new RuntimeException("Unable to perform cipher operations.", e); - } catch (IOException e) { - // Unable to upload the file or operate on the stream - throw new RuntimeException( - String.format("Unable to upload the file using URL %s (signing info redacted) " - + "or manipulate the InputStream.", truncatedUrl), - e - ); - } catch (NoSuchAlgorithmException e) { - // Unable to get a MAC on the encrypted doc - throw new IllegalStateException("Unable to create MessageDigest. Required algorithm not present.", e); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - @Override - public UploadDetails createDestinationAndUpload(String feedType, String contentType, File fileName) - throws FileNotFoundException, ApiException { - long documentLength = fileName.length(); - - return createDestinationAndUpload(feedType, contentType, documentLength, new FileInputStream(fileName)); - } - - @Override - public UploadDetails createDestinationAndUpload(String feedType, String contentType, long documentLength, - InputStream stream) throws ApiException { - - UploadDestination destination = createDestination(feedType, contentType, documentLength); - - return uploadToDestination(destination, contentType, documentLength, stream); - } - - public UploadDestination createDestination(String feedType, String contentType, long documentLength) - throws ApiException { - Preconditions.checkArgument(documentLength < (long) Integer.MAX_VALUE, - "Document length cannot exceed " + Integer.MAX_VALUE + " bytes." - ); - CreateUploadDestinationResponse response = uploadsApi.createUploadDestinationForFeed(feedType, contentType, - (int) documentLength - ); - return response.getPayload(); - } -/* - TODO - - public void submitFeed(UploadDetails uploadDetails) { - - CompleteDocumentUploadInput completeUploadInput = new CompleteDocumentUploadInput(); - completeUploadInput.setDocumentId(uploadDestinationId); - completeUploadInput.setObfuscatedCustomerId(uploadDestination.getObfuscatedCustomerId()); - completeUploadInput.setCompleteUploadToken(getUploadUrlOutput.getCompleteUploadToken()); - completeUploadInput.setSha256sum(sha256sum); - completeUploadInput.setContentMd5(contentMd5); - CompleteDocumentUploadSyncJobBuilder.build(completeUploadInput, metricsStrategy, client).runOnce(); - - } -*/ - - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/util/DemuxOutputStream.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/util/DemuxOutputStream.java deleted file mode 100644 index 6fe8a68..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/util/DemuxOutputStream.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.amazon.spapi.sequencing.client.util; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.io.OutputStream; - -/** - * {@code OutputStream} wrapper class that forwards data written to this stream to an associated stream and allows - * switching of the associated stream on the fly. Associated stream can be {@code null} in which case all operations - * are, essentially, NOOP. - */ -public class DemuxOutputStream extends OutputStream { - - private OutputStream out; - - /** - * Creates a new {@code DemuxOutputStream} wrapper around given output stream. Accepts {@code null}. - * @param stream - * the output stream (can be {@code null}) - */ - public DemuxOutputStream(@Nullable OutputStream stream) { - out = stream; - } - - /** - * Binds new output stream. Originally bound output stream will be flushed and returned. Accepts {@code null}. - * @param stream - * the new input stream to bind (can be {@code null}) - * @return the previously bound input stream (can be {@code null}) - * @throws IOException - * if there was an error flushing the currently bound stream - */ - @Nullable - public OutputStream bind(@Nullable OutputStream stream) throws IOException { - OutputStream tmp = out; - out = stream; - if (tmp != null) { - tmp.flush(); - } - return tmp; - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - if (out != null) { - out.close(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void flush() throws IOException { - if (out != null) { - out.flush(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void write(byte[] data, int offset, int len) throws IOException { - if (out != null) { - out.write(data, offset, len); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void write(byte[] data) throws IOException { - if (out != null) { - out.write(data); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void write(int data) throws IOException { - if (out != null) { - out.write(data); - } - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/util/TempInputStream.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/util/TempInputStream.java deleted file mode 100644 index 59310d8..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/client/util/TempInputStream.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.amazon.spapi.sequencing.client.util; - -import lombok.extern.apachecommons.CommonsLog; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import java.io.*; - -/** - * An InputStream from a temporary file that deletes the file after it was completely read or closed. - */ -@CommonsLog -public class TempInputStream extends InputStream { - - private final FileInputStream fileStream; - private final File tempFile; - - /** - * Creates a new {@code TempInputStream} for a given file. - * @param file - * the file - * @throws FileNotFoundException - * if file doesn't exist - */ - public TempInputStream(File file) throws FileNotFoundException { - fileStream = new FileInputStream(file); - tempFile = file; - } - - /** - * File cleanup. - */ - private void cleanup() { - IOUtils.closeQuietly(fileStream); - - try { - FileUtils.forceDelete(tempFile); - } catch (FileNotFoundException e) { - // Ignore not found exceptions - return; - } catch (IOException e) { - log.warn("IOException when deleting TempInputStream file", e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - try { - fileStream.close(); - } finally { - cleanup(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public int read() throws IOException { - try { - int read = fileStream.read(); - if (read == -1) { - cleanup(); - } - return read; - } catch (IOException e) { - cleanup(); - throw e; - } - } - - /** - * {@inheritDoc} - */ - @Override - public int read(byte[] bytes, int off, int len) throws IOException { - try { - int read = fileStream.read(bytes, off, len); - if (read == -1) { - cleanup(); - } - return read; - } catch (IOException e) { - cleanup(); - throw e; - } - } - - /** - * {@inheritDoc} - */ - @Override - public int read(byte[] bytes) throws IOException { - try { - int read = fileStream.read(bytes); - if (read == -1) { - cleanup(); - } - return read; - } catch (IOException e) { - cleanup(); - throw e; - } - } - - /** - * {@inheritDoc} - */ - @Override - public int available() throws IOException { - return fileStream.available(); - } - - /** - * {@inheritDoc} - */ - @Override - public long skip(long n) throws IOException { - return fileStream.skip(n); - } - - /** - * {@inheritDoc} - */ - @Override - protected void finalize() throws IOException { - close(); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/AsymmetricCryptoProvider.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/AsymmetricCryptoProvider.java deleted file mode 100644 index 9e48b2e..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/AsymmetricCryptoProvider.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -import com.google.common.base.Preconditions; - -import javax.annotation.Nonnull; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.InvalidKeyException; -import java.security.SignatureException; - - -/** - * An Asymmetric verison of {@link CryptoProvider} that uses RSA. - * Encryption is performed using RSA with PKCS1Padding. - * Signing is performed using SHA256withRSA. - */ -public class AsymmetricCryptoProvider implements CryptoProvider { - private static final String REQUIRED_KEY_ALGORITHM = "RSA"; - private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; - private static final String ENCRYPTION_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public byte[] encrypt(@Nonnull byte[] plainText, @Nonnull EncryptionMaterials materials) - throws EncryptionException { - Preconditions.checkArgument(plainText != null, "plainText must be non-null."); - - Cipher cipher = getInitializedCipher(Cipher.ENCRYPT_MODE, materials); - return applyCipher(cipher, plainText); - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public byte[] decrypt(@Nonnull byte[] cipherText, @Nonnull EncryptionMaterials materials) - throws EncryptionException { - Preconditions.checkArgument(cipherText != null, "cipherText must be non-null."); - - Cipher cipher = getInitializedCipher(Cipher.DECRYPT_MODE, materials); - return applyCipher(cipher, cipherText); - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public byte[] sign(@Nonnull byte[] bytesToSign, @Nonnull EncryptionMaterials materials) - throws SignatureGenerationException { - Preconditions.checkArgument(bytesToSign != null, "bytesToSign must be non-null."); - - PrivateKey key = validatePrivateKey(materials); - - byte[] signatureBytes; - try { - Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); - signature.initSign(key, new SecureRandom()); - signature.update(bytesToSign); - signatureBytes = signature.sign(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(String.format("NoSuchAlgorithmException for %s", SIGNATURE_ALGORITHM), e); - } catch (InvalidKeyException e) { - throw new SignatureGenerationException(e); - } catch (SignatureException e) { - throw new SignatureGenerationException(e); - } - - return signatureBytes; - } - - /** - * {@inheritDoc} - */ - @Override - public void validateSignature(@Nonnull byte[] signatureBytes, @Nonnull byte[] bytesToSign, - @Nonnull EncryptionMaterials materials) throws SignatureGenerationException, - SignatureValidationException { - Preconditions.checkArgument(signatureBytes != null, "signatureBytes must be non-null."); - Preconditions.checkArgument(bytesToSign != null, "bytesToSign must be non-null."); - - PublicKey key = validatePublicKey(materials); - - try { - Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); - signature.initVerify(key); - signature.update(bytesToSign); - if (!signature.verify(signatureBytes)) { - throw new SignatureValidationException("Signature mismatch. Possible tampering attempt."); - } - } catch (InvalidKeyException e) { - throw new SignatureGenerationException(e); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(String.format("NoSuchAlgorithmException for %s", SIGNATURE_ALGORITHM), e); - } catch (SignatureException e) { - throw new SignatureGenerationException(e); - } - } - - /** - * Helper function to apply a cipher to some data and return the results. Performs exception mapping. - * @param cipher the Cipher to apply. - * @param text the text to apply the cipher to - * @return the text generated by applying the cipher to the input text - * @throws EncryptionException if there is a problem applying the cipher. - */ - @Nonnull - private byte[] applyCipher(@Nonnull Cipher cipher, @Nonnull byte[] text) throws EncryptionException { - try { - return cipher.doFinal(text); - } catch (IllegalBlockSizeException e) { - throw new EncryptionException(e); - } catch (BadPaddingException e) { - throw new EncryptionException(e); - } - } - - /** - * {@inheritDoc} - */ - @Nonnull - public Cipher getInitializedCipher(int mode, @Nonnull EncryptionMaterials materials) throws EncryptionException { - - Key key; - if (Cipher.DECRYPT_MODE == mode) { - key = validatePrivateKey(materials); - } else { - key = validatePublicKey(materials); - } - - Cipher cipher; - try { - cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM); - cipher.init(mode, key, new SecureRandom()); - } catch (InvalidKeyException e) { - throw new EncryptionException(e); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(String.format("NoSuchAlgorithmException for %s", SIGNATURE_ALGORITHM), e); - } catch (NoSuchPaddingException e) { - throw new IllegalStateException(String.format("NoSuchPaddingException for %s", SIGNATURE_ALGORITHM), e); - } - - return cipher; - } - - /** - * Helper function to ensure EncryptionMaterials are valid and the Key matches a specific class. - * @param materials the Materials to validate - * @param clazz the expected class of the key. - * @param message The error message if the key is not of the expected class - * @return the key. - */ - @Nonnull - private Key validateKey(@Nonnull EncryptionMaterials materials, @Nonnull Class clazz, - @Nonnull String message) { - Preconditions.checkArgument(materials != null, "Materials must be non-null."); - Preconditions.checkArgument(REQUIRED_KEY_ALGORITHM.equals(materials.getKey().getAlgorithm()), - "AsymmetricCryptoProvider requires an RSA key."); - Preconditions.checkArgument(clazz.isAssignableFrom(materials.getKey().getClass()), message); - - return materials.getKey(); - } - - /** - * Helper function that validates that the EncryptionMaterials contain a valid PrivateKey. - * @param materials the materials to validate - * @return the PrivateKey - */ - @Nonnull - private PrivateKey validatePrivateKey(@Nonnull EncryptionMaterials materials) { - return (PrivateKey) validateKey(materials, PrivateKey.class, "Key must be of type PrivateKey."); - } - - /** - * Helper function that validates that the EncryptionMaterials contain a valid PublicKey. - * @param materials the materials to validate - * @return the PublicKey - */ - @Nonnull - private PublicKey validatePublicKey(@Nonnull EncryptionMaterials materials) { - return (PublicKey) validateKey(materials, PublicKey.class, "Key must be of type PublicKey."); - } -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/CryptoException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/CryptoException.java deleted file mode 100644 index 02dfed4..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/CryptoException.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - - -/** - * Base class for any Cryptographic exception that occurs. - */ -public class CryptoException extends Exception { - - /** - * Constructor taking both a message and cause. - * @param message the message - * @param cause the cause - */ - public CryptoException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message the message - */ - public CryptoException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause the cause - */ - public CryptoException(Throwable cause) { - super(cause); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/CryptoProvider.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/CryptoProvider.java deleted file mode 100644 index 9d0a5a1..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/CryptoProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -import javax.annotation.Nonnull; -import javax.crypto.Cipher; - -/** - * This class of objects performs encryption, decryption, signing (or hmacing), and signature/hmac validation. - */ -public interface CryptoProvider { - - /** - * Encrypt plain text into cipher text. - * @param plainText the plain text to encrypt. - * @param materials the EncryptionMaterials to use to perform the encryption - * @return the cipherText - * @throws EncryptionException if any problems occur during encryption. - */ - @Nonnull - byte[] encrypt(@Nonnull byte[] plainText, @Nonnull EncryptionMaterials materials) throws EncryptionException; - - /** - * Decrypts the cipher text into plain text. - * @param cipherText the cipher text to decrypt - * @param materials the EncryptionMaterials to use to perform decryption - * @return the plain text - * @throws EncryptionException if any problems occur during decryption. - */ - @Nonnull - byte[] decrypt(@Nonnull byte[] cipherText, @Nonnull EncryptionMaterials materials) throws EncryptionException; - - /** - * Create a signature or hmac based on the bytes provided. - * @param bytesToSign the bytes to use when calculating the signature or hmac - * @param materials the materials to use when generating the signature or hmac - * @return the signature/hmac bytes - * @throws SignatureGenerationException if any problems occur during signing/hmacing - */ - @Nonnull - byte[] sign(@Nonnull byte[] bytesToSign, @Nonnull EncryptionMaterials materials) - throws SignatureGenerationException; - - /** - * Validate that a signature/hmac matches the data that was signed. - * @param signature the signature/hmac to validate - * @param bytesToSign the data that was originally signed/hmac'd - * @param materials the materials to use when validating the signature or hmac - * @throws SignatureGenerationException if we are unable to generate the signature/hmac to validate against. - * @throws SignatureValidationException if the validation fails (i.e, the signature or hmac do not match). - * This indicates data corruption or a possible tampering attack. - */ - @Nonnull - void validateSignature(@Nonnull byte[] signature, @Nonnull byte[] bytesToSign, - @Nonnull EncryptionMaterials materials) throws SignatureGenerationException, - SignatureValidationException; - - /** - * Correctly Initialize a Cipher given the mode. The mode is specified by {@link Cipher}'s Mode constants. - * @param mode The mode of the cipher (i.e., encrypt or decrypt). See {@link Cipher}'s Mode constants. - * @param materials The EncryptionMaterials to initialize the cipher with - * @return an initialized Cipher object - * @throws EncryptionException if the Cipher could not be initialized. - */ - @Nonnull - Cipher getInitializedCipher(int mode, @Nonnull EncryptionMaterials materials) throws EncryptionException; - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/DefaultKeyConverter.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/DefaultKeyConverter.java deleted file mode 100644 index 893a783..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/DefaultKeyConverter.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -import com.google.common.base.Preconditions; - -import javax.annotation.Nonnull; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.security.*; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; - -/** - * Default implementation of the {@link KeyConverter} interface. - */ -public class DefaultKeyConverter implements KeyConverter { - private static final String SECRET_KEY_ALGORITHM = "AES"; - private static final String KEYPAIR_ALGORITHM = "RSA"; - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public SecretKey convertToSymmetricKey(@Nonnull byte[] material) { - Preconditions.checkArgument(material != null, "material must be non-null."); - return new SecretKeySpec(material, SECRET_KEY_ALGORITHM); - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public PrivateKey convertToPrivateKey(@Nonnull byte[] material) throws com.amazon.spapi.sequencing.crypto.InvalidKeyException { - Preconditions.checkArgument(material != null, "material must be non-null."); - - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(material); - try { - KeyFactory factory = KeyFactory.getInstance(KEYPAIR_ALGORITHM); - return factory.generatePrivate(keySpec); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("RSA Algorithm is invalid. This indicates a badly configured Provider."); - } catch (InvalidKeySpecException e) { - throw new com.amazon.spapi.sequencing.crypto.InvalidKeyException("Invalid key.", e); - } - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public PublicKey convertToPublicKey(@Nonnull byte[] material) throws com.amazon.spapi.sequencing.crypto.InvalidKeyException { - Preconditions.checkArgument(material != null, "material must be non-null."); - - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(material); - try { - KeyFactory factory = KeyFactory.getInstance(KEYPAIR_ALGORITHM); - return factory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("RSA Algorithm is invalid. This indicates a badly configured Provider."); - } catch (InvalidKeySpecException e) { - throw new com.amazon.spapi.sequencing.crypto.InvalidKeyException("Invalid key.", e); - } - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public KeyPair convertToKeyPair(@Nonnull byte[] publicMaterial, @Nonnull byte[] privateMaterial) - throws InvalidKeyException { - return new KeyPair(convertToPublicKey(publicMaterial), convertToPrivateKey(privateMaterial)); - } -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/EncryptionException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/EncryptionException.java deleted file mode 100644 index 14481ca..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/EncryptionException.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -/** - * An exception that indicates an error occured during encryption or decryption of some data. - */ -public class EncryptionException extends CryptoException { - - /** - * Constructor taking both a message and cause. - * @param message the message - * @param cause the cause - */ - public EncryptionException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message the message - */ - public EncryptionException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause the cause - */ - public EncryptionException(Throwable cause) { - super(cause); - } -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/EncryptionMaterials.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/EncryptionMaterials.java deleted file mode 100644 index 057feb9..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/EncryptionMaterials.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -import com.google.common.base.Preconditions; -import lombok.EqualsAndHashCode; -import lombok.Getter; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.security.Key; -import java.util.Arrays; - -/** - * Key and initialization vector for use when encrypting things. - */ -@EqualsAndHashCode -public class EncryptionMaterials { - - @Nonnull - @Getter - private final Key key; - @Nullable - private final byte[] initializationVector; - - /** - * All Args Constructor. - * @param key non null Key for this EncryptionMaterials - * @param initializationVector Nullable initializationVector (used for CBC mode encryption). - */ - public EncryptionMaterials(@Nonnull Key key, @Nullable byte[] initializationVector) { - Preconditions.checkArgument(key != null, "Key must be non-null."); - - this.key = key; - this.initializationVector = copyInitializationVector(initializationVector); - } - - /** - * Get a copy of the initialization vector bytes. - * @return A copy of the initialization vector bytes. - */ - @Nullable - public byte[] getInitializationVector() { - return copyInitializationVector(initializationVector); - } - - /** - * A null-safe helper that copies the initialization vector bytes into a new array if the IV is non-null. - * @param iv the byte array to copy - * @return a copy of iv if iv is non null, otherwise null. - */ - @Nullable - private byte[] copyInitializationVector(byte[] iv) { - byte[] newInitializationVector = null; - if (iv != null) { - newInitializationVector = Arrays.copyOf(iv, iv.length); - } - - return newInitializationVector; - } -} - diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/InvalidKeyException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/InvalidKeyException.java deleted file mode 100644 index 17c7610..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/InvalidKeyException.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -/** - * InvalidKeyException indicates that a key was invalid. - * - */ -public class InvalidKeyException extends CryptoException { - - /** - * Constructor taking both a message and cause. - * @param message the message - * @param cause the cause - */ - public InvalidKeyException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message the message - */ - public InvalidKeyException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause the cause - */ - public InvalidKeyException(Throwable cause) { - super(cause); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/KeyConverter.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/KeyConverter.java deleted file mode 100644 index d6df5fc..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/KeyConverter.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -import javax.annotation.Nonnull; -import javax.crypto.SecretKey; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * A class of objects that converts the raw bytes of an encoded key into a Key. - */ -public interface KeyConverter { - - /** - * Convert a byte[] into a SecretKey. - * @param material the byte[] to covert - * @return the SecretKey - */ - @Nonnull - SecretKey convertToSymmetricKey(@Nonnull byte[] material); - - /** - * Convert a PKCS8 encoded byte[] into a PrivateKey. - * @param material the byte[] to covert - * @return the PrivateKey - * @throws InvalidKeyException if the byte[] is not a properly encoded key - */ - @Nonnull - PrivateKey convertToPrivateKey(@Nonnull byte[] material) throws InvalidKeyException; - - /** - * Convert a X509 encoded byte[] into a PublicKey. - * @param material the byte[] to covert - * @return the PublicKey - * @throws InvalidKeyException if the byte[] is not a properly encoded key - */ - @Nonnull - PublicKey convertToPublicKey(@Nonnull byte[] material) throws InvalidKeyException; - - /** - * Convert a X509 encoded byte[] and a PKCS8 encoded byte[] into a KeyPair. - * @param publicMaterial the X509 encoded byte[] to covert into the public key - * @param privateMaterial the PKCS8 encoded byte[] to covert into the private key - * @return the SecretKey - * @throws InvalidKeyException if either the public or private material is invalid - */ - @Nonnull - KeyPair convertToKeyPair(@Nonnull byte[] publicMaterial, @Nonnull byte[] privateMaterial) - throws InvalidKeyException; -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureException.java deleted file mode 100644 index c121a20..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureException.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -/** - * Base class for exceptions related to Signature/HMAC problems. - */ -public class SignatureException extends CryptoException { - - /** - * Constructor taking both a message and cause. - * @param message the message - * @param cause the cause - */ - public SignatureException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message the message - */ - public SignatureException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause the cause - */ - public SignatureException(Throwable cause) { - super(cause); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureGenerationException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureGenerationException.java deleted file mode 100644 index 652f385..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureGenerationException.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -/** - * Indicates that an error occured when generating a Signature or HMAC. - */ -public class SignatureGenerationException extends SignatureException { - - /** - * Constructor taking both a message and cause. - * @param message the message - * @param cause the cause - */ - public SignatureGenerationException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message the message - */ - public SignatureGenerationException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause the cause - */ - public SignatureGenerationException(Throwable cause) { - super(cause); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureValidationException.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureValidationException.java deleted file mode 100644 index 4474ab7..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SignatureValidationException.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -/** - * Indicates that the signature/hmac did not verify and that there was a mismatch. - * This indicates either data corruption or a possible tamper attack. - */ -public class SignatureValidationException extends SignatureException { - - /** - * Constructor taking both a message and cause. - * @param message the message - * @param cause the cause - */ - public SignatureValidationException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructor taking only a message. - * @param message the message - */ - public SignatureValidationException(String message) { - super(message); - } - - /** - * Constructor taking only a cause. - * @param cause the cause - */ - public SignatureValidationException(Throwable cause) { - super(cause); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SymmetricCryptoProvider.java b/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SymmetricCryptoProvider.java deleted file mode 100644 index bec05af..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/main/java/com/amazon/spapi/sequencing/crypto/SymmetricCryptoProvider.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.amazon.spapi.sequencing.crypto; - -import com.google.common.base.Preconditions; -import lombok.extern.apachecommons.CommonsLog; - -import javax.annotation.Nonnull; -import javax.crypto.Mac; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import java.security.InvalidAlgorithmParameterException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.InvalidKeyException; -import java.util.Arrays; -/** - * A Symmetric verison of {@link CryptoProvider} that uses AES and HmacSha256. - * Encryption is performed using AES/CBC/PKCS5Padding - * HMAC is performed using HmacSha256. - */ -@CommonsLog -public class SymmetricCryptoProvider implements CryptoProvider { - private static final String REQUIRED_KEY_ALGORITHM = "AES"; - private static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding"; - private static final String MAC_ALGORITHM = "HmacSha256"; - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public byte[] encrypt(@Nonnull byte[] plainText, @Nonnull EncryptionMaterials materials) - throws EncryptionException { - Preconditions.checkArgument(plainText != null, "plainText must be non-null."); - - Cipher cipher = getInitializedCipher(Cipher.ENCRYPT_MODE, materials); - - return applyCipher(cipher, plainText); - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public byte[] decrypt(@Nonnull byte[] cipherText, @Nonnull EncryptionMaterials materials) - throws EncryptionException { - Preconditions.checkArgument(cipherText != null, "cipherText must be non-null."); - - Cipher cipher = getInitializedCipher(Cipher.DECRYPT_MODE, materials); - - return applyCipher(cipher, cipherText); - } - - /** - * {@inheritDoc} - */ - @Nonnull - @Override - public byte[] sign(@Nonnull byte[] bytesToSign, @Nonnull EncryptionMaterials materials) - throws SignatureGenerationException { - Preconditions.checkArgument(bytesToSign != null, "bytesToSign must be non-null.."); - validateMaterials(materials, false); - - Mac mac; - try { - mac = Mac.getInstance(MAC_ALGORITHM); - mac.init(materials.getKey()); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(String.format("NoSuchAlgorithmException for %s", MAC_ALGORITHM), e); - } catch (InvalidKeyException e) { - throw new SignatureGenerationException(e); - } - - return mac.doFinal(bytesToSign); - } - - /** - * {@inheritDoc} - */ - @Override - public void validateSignature(@Nonnull byte[] signature, @Nonnull byte[] bytesToSign, - @Nonnull EncryptionMaterials materials) throws SignatureValidationException, SignatureGenerationException { - Preconditions.checkArgument(signature != null, "signature must be non-null."); - Preconditions.checkArgument(bytesToSign != null, "bytesToSign must be non-null."); - validateMaterials(materials, false); - - byte[] expectedSignature = sign(bytesToSign, materials); - - if (!MessageDigest.isEqual(signature, expectedSignature)) { - if (log.isTraceEnabled()) { - log.trace(String.format("Signature mismatch detected. Expected=%s, Actual=%s", - Arrays.toString(expectedSignature), Arrays.toString(signature))); - } - throw new SignatureValidationException("Signature mismatch. Possible tampering attempt."); - } - } - - /** - * Helper function to apply a cipher to some data and return the results. Performs exception mapping. - * @param cipher the Cipher to apply. - * @param text the text to apply the cipher to - * @return the text generated by applying the cipher to the input text - * @throws EncryptionException if there is a problem applying the cipher. - */ - @Nonnull - private byte[] applyCipher(@Nonnull Cipher cipher, @Nonnull byte[] text) throws EncryptionException { - try { - return cipher.doFinal(text); - } catch (IllegalBlockSizeException e) { - throw new EncryptionException(e); - } catch (BadPaddingException e) { - throw new EncryptionException(e); - } - } - - /** - * {@inheritDoc} - */ - @Nonnull - public Cipher getInitializedCipher(int mode, @Nonnull EncryptionMaterials materials) throws EncryptionException { - validateMaterials(materials, true); - - Cipher cipher; - try { - cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM); - cipher.init(mode, materials.getKey(), - new IvParameterSpec(materials.getInitializationVector()), new SecureRandom()); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(String.format("NoSuchAlgorithmException for %s", ENCRYPTION_ALGORITHM), e); - } catch (NoSuchPaddingException e) { - throw new IllegalStateException(String.format("NoSuchPaddingException for %s", ENCRYPTION_ALGORITHM), e); - } catch (InvalidKeyException e) { - throw new EncryptionException(e); - } catch (InvalidAlgorithmParameterException e) { - throw new EncryptionException(e); - } - - return cipher; - } - - /** - * Helper function to ensure EncryptionMaterials are valid. - * @param materials the Materials to validate - * @param ivRequired whether or not this function should treat the InitializationVector as required. - * The IV is required for encryption/decryption but not for HMAC operations. - */ - private void validateMaterials(@Nonnull EncryptionMaterials materials, boolean ivRequired) { - Preconditions.checkArgument(materials != null, "Materials must be non-null."); - Preconditions.checkArgument(REQUIRED_KEY_ALGORITHM.equals(materials.getKey().getAlgorithm()), - "SymmetricCryptoProvider requires an AES Key."); - Preconditions.checkArgument(!ivRequired || materials.getInitializationVector() != null, "IV must be non-null."); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/main/resources/certs/InternalAndExternalTrustStore.jks b/clients/sellingpartner-api-frp-helper-java/src/main/resources/certs/InternalAndExternalTrustStore.jks deleted file mode 100644 index f2d9548..0000000 Binary files a/clients/sellingpartner-api-frp-helper-java/src/main/resources/certs/InternalAndExternalTrustStore.jks and /dev/null differ diff --git a/clients/sellingpartner-api-frp-helper-java/src/test/java/com/amazon/spapi/CreateDestinationAndUploadTest.java b/clients/sellingpartner-api-frp-helper-java/src/test/java/com/amazon/spapi/CreateDestinationAndUploadTest.java deleted file mode 100644 index b4ebc4a..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/test/java/com/amazon/spapi/CreateDestinationAndUploadTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.amazon.spapi; - -import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentials; -import com.amazon.SellingPartnerAPIAA.LWAAuthorizationCredentials; -import com.amazon.spapi.sequencing.client.UploadDetails; -import com.amazon.spapi.sequencing.client.UploadsSequencer; -import com.amazon.spapi.sequencing.client.impl.UploadsSequencerImpl; -import io.swagger.client.ApiException; -import io.swagger.client.api.UploadsApi; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.net.URL; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -/** - * API tests for UploadsApi - */ -public class CreateDestinationAndUploadTest { - - private final String feedType = "_POST_PRODUCT_DATA_"; - - private final UploadsSequencer sequencer; - - { - sequencer = testSequencer(); - - // Fill out the API sequencer with your credentials here. - } - - static { - System.setProperty("javax.net.ssl.trustStore", - getResourceFile("certs/InternalAndExternalTrustStore.jks").getPath() - ); - System.setProperty("javax.net.ssl.trustStorePassword", "amazon"); - } - - private UploadsSequencer createSequencer(String accessKeyId, String secretKey, String clientId, String clientSecret, - String refreshToken) { - AWSAuthenticationCredentials - awsAuthenticationCredentials = AWSAuthenticationCredentials.builder() - .accessKeyId(accessKeyId) - .secretKey(secretKey) - .region("us-west-2") - .build(); - - - LWAAuthorizationCredentials lwaAuthorizationCredentials = - LWAAuthorizationCredentials.builder() - .clientId(clientId) - .clientSecret(clientSecret) - .refreshToken( - refreshToken) - .endpoint("https://api.integ.amazon.com/auth/O2/token") - .build(); - UploadsApi api = new UploadsApi.Builder().awsAuthenticationCredentials(awsAuthenticationCredentials) - .lwaAuthorizationCredentials(lwaAuthorizationCredentials) - .endpoint("https://marketplaceapi-beta.amazonservices.com") - .build(); - - return UploadsSequencerImpl.builder() - .uploadsApi(api) - .build(); - } - - private static File getResourceFile(String fileName) { - final URL fileUrl = UploadsApi.class.getClassLoader().getResource(fileName); - assertNotNull(fileName + " cannot be reached", fileUrl); - final File file = new File(fileUrl.getFile()); - assertTrue(file.exists()); - return file; - } - - @Test - @Ignore - public void createDestinationThenUpload() throws FileNotFoundException, ApiException { - //file must be in "test/resources" - final File flatFileInventoryLoader = getResourceFile("Feed_101__POST_PRODUCT_DATA_.xml"); - final UploadDetails details = sequencer.createDestinationAndUpload(feedType, - "text/xml", - flatFileInventoryLoader - ); - System.out.println("SHA256Sum = " + details.getSha256Sum() + " // UploadDestinationId = " + details.getUploadDestinationId()); - assertNotNull(details); - assertNotNull(details.getUploadDestinationId()); - } - - @Test - @Ignore - public void makeItPass() { - assertTrue(true); - } - - private UploadsSequencer testSequencer() { - return createSequencer("", - "", - "", - "", - "" - ); - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/test/java/com/amazon/spapi/ReportDownloadTest.java b/clients/sellingpartner-api-frp-helper-java/src/test/java/com/amazon/spapi/ReportDownloadTest.java deleted file mode 100644 index 0711a3a..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/test/java/com/amazon/spapi/ReportDownloadTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.amazon.spapi; - -import com.amazon.spapi.sequencing.client.DownloadsSequencer; -import com.amazon.spapi.sequencing.client.impl.DownloadsSequencerImpl; -import io.swagger.client.model.EncryptionDetails; -import io.swagger.client.model.UploadDestination; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.net.URL; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class ReportDownloadTest { - - private final DownloadsSequencer sequencer; - - private UploadDestination destinationToDownload; - - { - sequencer = DownloadsSequencerImpl.builder().build(); - // Fill out the API sequencer with your credentials here. - } - - - static { - System.setProperty("javax.net.ssl.trustStore", - getResourceFile("certs/InternalAndExternalTrustStore.jks").getPath() - ); - System.setProperty("javax.net.ssl.trustStorePassword", "amazon"); - } - - - @Before - public void before() { - // TODO: fill this out based on payload - EncryptionDetails encryptionDetails = new EncryptionDetails().standard(EncryptionDetails.StandardEnum.AES) - .initializationVector("") - .key(""); - destinationToDownload = new UploadDestination().encryptionDetails(encryptionDetails) - .url(""); - } - - @Test - // TODO: remove @Ignore when ready to test - @Ignore - public void downloadAndOutputDecryptedReport() throws IOException { - File fileToOutputTo = new File("DecryptedReport.xml"); - sequencer.downloadDecryptThenWriteFile(destinationToDownload, false, fileToOutputTo); - } - - @Test - public void makeItPass() { - assertTrue(true); - } - - - private static File getResourceFile(String fileName) { - final URL fileUrl = DownloadsSequencerImpl.class.getClassLoader().getResource(fileName); - assertNotNull(fileName + " cannot be reached", fileUrl); - final File file = new File(fileUrl.getFile()); - assertTrue(file.exists()); - return file; - } - -} diff --git a/clients/sellingpartner-api-frp-helper-java/src/test/resources/Feed_101__POST_PRODUCT_DATA_.xml b/clients/sellingpartner-api-frp-helper-java/src/test/resources/Feed_101__POST_PRODUCT_DATA_.xml deleted file mode 100644 index 588fa5f..0000000 --- a/clients/sellingpartner-api-frp-helper-java/src/test/resources/Feed_101__POST_PRODUCT_DATA_.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file