Skip to content
Open

demo #369

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions src/main/java/com/shipmentEvents/EventHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package com.shipmentEvents.handlers;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.shipmentEvents.util.Constants;
import com.shopify.ShopifySdk;
import com.shopify.model.ShopifyShop;

import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;


public class EventHandler implements RequestHandler<ScheduledEvent, String> {

/**
* Shipment events for a carrier are uploaded to separate S3 buckets based on the source of events. E.g., events originating from
* the hand-held scanner are stored in a separate bucket than the ones from mobile App. The Lambda processes events from multiple
* sources and updates the latest status of the package in a summary S3 bucket every 15 minutes.
*
* The events are stored in following format:
* - Each status update is a file, where the name of the file is tracking number + random id.
* - Each file has status and time-stamp as the first 2 lines respectively.
* - The time at which the file is stored in S3 is not an indication of the time-stamp of the event.
* - Once the status is marked as DELIVERED, we can stop tracking the package.
*
* A Sample files looks as below:
* FILE-NAME-> '8787323232232332--55322798-dd29-4a04-97f4-93e18feed554'
* >status:IN TRANSIT
* >timestamp: 1573410202
* >Other fields like...tracking history and address
*/
public String handleRequest(ScheduledEvent scheduledEvent, Context context) {

final LambdaLogger logger = context.getLogger();
try {
processShipmentUpdates(logger);
return "SUCCESS";
} catch (final Exception ex) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

It appears that your code handles a broad swath of exceptions in the catch block, potentially trapping dissimilar issues or problems that should not be dealt with at this point in the program.

Learn more

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Problem: While re-throwing the caught exception with modifications , information about the caught exception is being lost, including information about the stack trace of the exception.

Fix: If the caught exception object does not contain sensitive information, consider passing it as the "rootCause" or inner exception parameter to the constructor of the new exception before throwing the new exception. (Note that not all exception constructors support inner exceptions. Use a wrapper exception that supports inner exceptions.)
Learn more

Suggested remediation:
Consider passing the exception object either as "rootCause" or as an inner exception parameter, to the constructor of the new exception before throwing new exception. This will help keep stack trace.

@@ -61,3 +61,3 @@
             logger.log(String.format(&#34;Failed to process shipment Updates in %s due to %s&#34;, scheduledEvent.getAccount(), ex.getMessage()));
-            throw new RuntimeException(&#34;Hiding the exception&#34;);
+            throw new RuntimeException(&#34;Hiding the exception&#34;, ex);
         }

logger.log(String.format("Failed to process shipment Updates in %s due to %s", scheduledEvent.getAccount(), ex.getMessage()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential log injection detected. Ensure all untrusted input is properly sanitized before logging. Use parameterized logging or validate input against a allow list to prevent log injection vulnerabilities. Consider using a dedicated logging library's built-in sanitization features when available. Learn more - https://cwe.mitre.org/data/definitions/117.html

throw new RuntimeException("Hiding the exception");
}
}

public String weakMessageEncryption(String message, String key) throws Exception {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Methods are throwing general exception, instead try and throw specific exception for proper and easy error handling.

Cipher cipher = Cipher.getInstance("RSA");
SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

String.getBytes

Using this method without an explicit charset/encoding causes the default character encoding of the JVM to be used for conversion from Java’s internal Unicode encoding to bytes. The system’s default encoding, which is used to determine the bytes for unsafe characters, might not always give correct results.

Solution:

Specify an encoding

(likely UTF-8, which is backward compatible with ASCII and has multi-language support)

byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential hardcoded credential detected. This code may contain sensitive data such as passwords or API keys embedded directly in the source. Hardcoded credentials can be extracted and misused, leading to unauthorized access to systems or data breaches. To remediate this, store secrets in environment variables or use a secrets management tool like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault. Avoid committing credentials to version control. For best practices, refer to - https://cwe.mitre.org/data/definitions/798.html

cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return new String(cipher.doFinal(message.getBytes()), StandardCharsets.UTF_8);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

String.getBytes

Using this method without an explicit charset/encoding causes the default character encoding of the JVM to be used for conversion from Java’s internal Unicode encoding to bytes. The system’s default encoding, which is used to determine the bytes for unsafe characters, might not always give correct results.

Solution:

Specify an encoding

(likely UTF-8, which is backward compatible with ASCII and has multi-language support)

byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

}

public ShopifyShop connectToShopify(String subdomain) {
final String token = "shpss_sdkfhkjh134134141341344133412312345678";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

It appears your code contains a hardcoded Shopify Shared Secret. Hardcoded secrets can allow attackers to bypass authentication methods and perform malicious actions. Possible remediation approaches are:

 1. Manage secret data using environment variables:
	 a. Identify the secret data and store that data in environment variables on your local and production server. The setting process may vary depending on your OS and environment.
	 b. Replace hard-coded secrets with references to environment variables, e.g., `password = os.environ.get(&#39;PASSWORD&#39;)`.
 2. Use AWS Secrets Manager to store, rotate, monitor, and control access to secrets. To retrieve secrets, you can replace hardcoded secrets in applications with a call to Secrets Manager APIs. To use Secrets Manager:
	 a. Visit [Secrets Manager](https://aws.amazon.com/secrets-manager/) on the AWS Management Console.
	 b. Choose the secret type on the console page and follow the instructions.
	 c. Use the code samples suggested in the console to retrieve the secret in your application.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential hardcoded credential detected. This code may contain sensitive data such as passwords or API keys embedded directly in the source. Hardcoded credentials can be extracted and misused, leading to unauthorized access to systems or data breaches. To remediate this, store secrets in environment variables or use a secrets management tool like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault. Avoid committing credentials to version control. For best practices, refer to - https://cwe.mitre.org/data/definitions/798.html

final ShopifySdk shopifySdk = ShopifySdk.newBuilder()
.withSubdomain(subdomain)
.withAccessToken(token).build();
return shopifySdk.getShop();
}

private void processShipmentUpdates(final LambdaLogger logger) throws InterruptedException {

final List<String> bucketsToProcess = Constants.BUCKETS_TO_PROCESS;
final Map<String, Pair<Long, String>> latestStatusForTrackingNumber = new HashMap<String, Pair<Long, String>>();
final Map<String, List<KeyVersion>> filesToDelete = new HashMap<String, List<DeleteObjectsRequest.KeyVersion>>();
for (final String bucketName : bucketsToProcess) {
final List<KeyVersion> filesProcessed = processEventsInBucket(bucketName, logger, latestStatusForTrackingNumber);
filesToDelete.put(bucketName, filesProcessed);
}
final AmazonS3 s3Client = EventHandler.getS3Client();

//Create a new file in the Constants.SUMMARY_BUCKET
logger.log("Map of statuses -> " + latestStatusForTrackingNumber);
String summaryUpdateName = Long.toString(System.currentTimeMillis());

EventHandler.getS3Client().putObject(Constants.SUMMARY_BUCKET, summaryUpdateName, latestStatusForTrackingNumber.toString());

long expirationTime = System.currentTimeMillis() + Duration.ofMinutes(1).toMillis();
while(System.currentTimeMillis() < expirationTime) {
if (s3Client.doesObjectExist(Constants.SUMMARY_BUCKET, summaryUpdateName)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

This code appears to be waiting for a resource before it runs. You could use the waiters feature to help improve efficiency. Consider using ObjectExists or ObjectNotExists. For more information, see https://aws.amazon.com/blogs/developer/waiters-in-the-aws-sdk-for-java/

break;
}
logger.log("waiting for file to be created " + summaryUpdateName);
Thread.sleep(1000);
}

// Before we delete the shipment updates make sure the summary update file exists
if (EventHandler.getS3Client().doesObjectExist(Constants.SUMMARY_BUCKET, summaryUpdateName)) {
deleteProcessedFiles(filesToDelete);
logger.log("All updates successfully processed");
} else {
throw new RuntimeException("Failed to write summary status, will be retried in 15 minutes");
}

}

private List<KeyVersion> processEventsInBucket(String bucketName, LambdaLogger logger, Map<String, Pair<Long, String>> latestStatusForTrackingNumber) {
final AmazonS3 s3Client = EventHandler.getS3Client();
logger.log("Processing Bucket: " + bucketName);

ObjectListing files = s3Client.listObjects(bucketName);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

This code might not produce accurate results if the operation returns paginated results instead of all results. Consider adding another call to check for additional results.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

This code uses an outdated API. ListObjectsV2 is the revised List Objects API, and we recommend you use this revised API for new application developments.

List<KeyVersion> filesProcessed = new ArrayList<DeleteObjectsRequest.KeyVersion>();

for (Iterator<?> iterator = files.getObjectSummaries().iterator(); iterator.hasNext(); ) {
S3ObjectSummary summary = (S3ObjectSummary) iterator.next();
logger.log("Reading Object: " + summary.getKey());

String trackingNumber = summary.getKey().split("--")[0];
Pair<Long, String> lastKnownStatus = latestStatusForTrackingNumber.get(trackingNumber);

// Check if this shipment has already been delivered, skip this file
if (lastKnownStatus != null && "DELIVERED".equals(lastKnownStatus.getRight())) {
continue;
}

String fileContents = s3Client.getObjectAsString(bucketName, summary.getKey());

if (!isValidFile(fileContents)) {
logger.log(String.format("Skipping invalid file %s", summary.getKey()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential log injection detected. Ensure all untrusted input is properly sanitized before logging. Use parameterized logging or validate input against a allow list to prevent log injection vulnerabilities. Consider using a dedicated logging library's built-in sanitization features when available. Learn more - https://cwe.mitre.org/data/definitions/117.html

continue;
}

if (!fileContents.contains("\n")) {

}
String[] lines = fileContents.split("\n");
String line1 = lines[0];
String line2 = lines[1];

String status = line1.split(":")[1];
Long timeStamp = Long.parseLong(line2.split(":")[1]);


if (null == lastKnownStatus || lastKnownStatus.getLeft() < timeStamp) {
lastKnownStatus = new MutablePair<Long, String>(timeStamp, status);
latestStatusForTrackingNumber.put(trackingNumber, lastKnownStatus);
}

//Add to list of processed files
filesProcessed.add(new KeyVersion(summary.getKey()));
logger.log("logging Contents of the file" + fileContents);
}
return filesProcessed;
}


private void deleteProcessedFiles(Map<String, List<KeyVersion>> filesToDelete) {
final AmazonS3 s3Client = EventHandler.getS3Client();
for (Entry<String, List<KeyVersion>> entry : filesToDelete.entrySet()) {
final DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(entry.getKey()).withKeys(entry.getValue()).withQuiet(false);
s3Client.deleteObjects(deleteRequest);
}
}

private boolean isValidFile(String fileContents) {
if (!fileContents.contains("\n")) {
return false;
}
String[] lines = fileContents.split("\n");
for (String l: lines) {
if (!l.contains(":")) {
return false;
}
}
return true;
}

public static AmazonS3 getS3Client() {
return AmazonS3ClientBuilder.standard().withRegion(Regions.DEFAULT_REGION).build();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

This code is written so that the client cannot be reused across invocations of the Lambda function.
To improve the performance of the Lambda function, consider using static initialization/constructor, global/static variables and singletons. It allows to keep alive and reuse HTTP connections that were established during a previous invocation.
Learn more about best practices for working with AWS Lambda functions.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Problem: The AWS region value is hardcoded in the code while calling AWS resources. The code will require refactoring, while expanding to other AWS regions.

Solution: The AWS region can be automatically determined by the client/client builder.The following link contains sample code snippets, on how to avoid AWS region hardcoding.

Learn more.

}


}


1 change: 1 addition & 0 deletions src/main/java/com/shipmentEvents/demo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@