Skip to content

Commit

Permalink
Merge pull request #169 from scireum/develop
Browse files Browse the repository at this point in the history
Release 7.0.1
  • Loading branch information
jakobvogel committed Apr 23, 2021
2 parents 752cc2f + 411eece commit c5cc203
Show file tree
Hide file tree
Showing 19 changed files with 308 additions and 284 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<url>http://s3ninja.net</url>

<properties>
<sirius.kernel>dev-21.0.1</sirius.kernel>
<sirius.web>dev-33.0.1</sirius.web>
<sirius.kernel>ga-1.0.1</sirius.kernel>
<sirius.web>ga-1.1.0</sirius.web>
</properties>

<repositories>
Expand Down
45 changes: 24 additions & 21 deletions src/main/java/ninja/Aws4HashCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,29 @@ public class Aws4HashCalculator {
/**
* Determines if the given request contains an AWS4 auth token.
*
* @param ctx the request to check
* @param webContext the request to check
* @return <tt>true</tt> if the request contains an AWS4 auth token, <tt>false</tt> otherwise.
*/
public boolean supports(final WebContext ctx) {
return AWS_AUTH4_PATTERN.matcher(ctx.getHeaderValue("Authorization").asString("")).matches()
|| X_AMZ_CREDENTIAL_PATTERN.matcher(ctx.get("X-Amz-Credential").asString("")).matches();
public boolean supports(final WebContext webContext) {
return AWS_AUTH4_PATTERN.matcher(webContext.getHeaderValue("Authorization").asString("")).matches()
|| X_AMZ_CREDENTIAL_PATTERN.matcher(webContext.get("X-Amz-Credential").asString("")).matches();
}

/**
* Computes the authentication hash as specified by the AWS SDK for verification purposes.
*
* @param ctx the current request to fetch parameters from
* @param webContext the current request to fetch parameters from
* @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(WebContext) effective URI}
* of the request
* @return the computes hash value
* @throws Exception when hashing fails
*/
public String computeHash(WebContext ctx, String pathPrefix) throws Exception {
Matcher matcher = AWS_AUTH4_PATTERN.matcher(ctx.getHeaderValue("Authorization").asString(""));
public String computeHash(WebContext webContext, String pathPrefix) throws Exception {
Matcher matcher = AWS_AUTH4_PATTERN.matcher(webContext.getHeaderValue("Authorization").asString(""));

if (!matcher.matches()) {
// If the header doesn't match, let's try an URL parameter as we might be processing a presigned URL
matcher = X_AMZ_CREDENTIAL_PATTERN.matcher(ctx.get("X-Amz-Credential").asString(""));
matcher = X_AMZ_CREDENTIAL_PATTERN.matcher(webContext.get("X-Amz-Credential").asString(""));
if (!matcher.matches()) {
throw new IllegalArgumentException("Unknown AWS4 auth pattern");
}
Expand All @@ -83,24 +83,27 @@ public String computeHash(WebContext ctx, String pathPrefix) throws Exception {

// For header based requests, the signed headers are in the "Credentials" header, for presigned URLs
// an extra parameter is given...
String signedHeaders = matcher.groupCount() == 7 ? matcher.group(6) : ctx.get("X-Amz-SignedHeaders").asString();
String signedHeaders =
matcher.groupCount() == 7 ? matcher.group(6) : webContext.get("X-Amz-SignedHeaders").asString();

byte[] dateKey = hmacSHA256(("AWS4" + storage.getAwsSecretKey()).getBytes(StandardCharsets.UTF_8), date);
byte[] dateRegionKey = hmacSHA256(dateKey, region);
byte[] dateRegionServiceKey = hmacSHA256(dateRegionKey, service);
byte[] signingKey = hmacSHA256(dateRegionServiceKey, serviceType);

byte[] signedData = hmacSHA256(signingKey, buildStringToSign(ctx, signedHeaders, region, service, serviceType));
byte[] signedData =
hmacSHA256(signingKey, buildStringToSign(webContext, signedHeaders, region, service, serviceType));
return BaseEncoding.base16().lowerCase().encode(signedData);
}

private String buildStringToSign(final WebContext ctx,
private String buildStringToSign(final WebContext webContext,
String signedHeaders,
String region,
String service,
String serviceType) {
final StringBuilder canonicalRequest = buildCanonicalRequest(ctx, signedHeaders);
final String amazonDateHeader = ctx.getHeaderValue("x-amz-date").asString(ctx.get("X-Amz-Date").asString());
final StringBuilder canonicalRequest = buildCanonicalRequest(webContext, signedHeaders);
final String amazonDateHeader =
webContext.getHeaderValue("x-amz-date").asString(webContext.get("X-Amz-Date").asString());
return "AWS4-HMAC-SHA256\n"
+ amazonDateHeader
+ "\n"
Expand All @@ -115,30 +118,30 @@ private String buildStringToSign(final WebContext ctx,
+ hashedCanonicalRequest(canonicalRequest);
}

private StringBuilder buildCanonicalRequest(final WebContext ctx, final String signedHeaders) {
StringBuilder canonicalRequest = new StringBuilder(ctx.getRequest().method().name());
private StringBuilder buildCanonicalRequest(final WebContext webContext, final String signedHeaders) {
StringBuilder canonicalRequest = new StringBuilder(webContext.getRequest().method().name());
canonicalRequest.append("\n");
canonicalRequest.append(ctx.getRawRequestedURI());
canonicalRequest.append(webContext.getRawRequestedURI());
canonicalRequest.append("\n");

appendCanonicalQueryString(ctx, canonicalRequest);
appendCanonicalQueryString(webContext, canonicalRequest);

for (String name : signedHeaders.split(";")) {
canonicalRequest.append(name.trim());
canonicalRequest.append(":");
canonicalRequest.append(Strings.join(ctx.getRequest().headers().getAll(name), ",").trim());
canonicalRequest.append(Strings.join(webContext.getRequest().headers().getAll(name), ",").trim());
canonicalRequest.append("\n");
}
canonicalRequest.append("\n");
canonicalRequest.append(signedHeaders);
canonicalRequest.append("\n");
canonicalRequest.append(ctx.getHeaderValue("x-amz-content-sha256").asString("UNSIGNED-PAYLOAD"));
canonicalRequest.append(webContext.getHeaderValue("x-amz-content-sha256").asString("UNSIGNED-PAYLOAD"));

return canonicalRequest;
}

private void appendCanonicalQueryString(WebContext ctx, StringBuilder canonicalRequest) {
QueryStringDecoder qsd = new QueryStringDecoder(ctx.getRequest().uri(), StandardCharsets.UTF_8);
private void appendCanonicalQueryString(WebContext webContext, StringBuilder canonicalRequest) {
QueryStringDecoder qsd = new QueryStringDecoder(webContext.getRequest().uri(), StandardCharsets.UTF_8);

List<Tuple<String, List<String>>> queryString = Tuple.fromMap(qsd.parameters());
queryString.sort(Comparator.comparing(Tuple::getFirst));
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/ninja/AwsHashCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,23 @@ public class AwsHashCalculator {
/**
* Computes the authentication hash as specified by the AWS SDK for verification purposes.
*
* @param ctx the current request to fetch parameters from
* @param webContext the current request to fetch parameters from
* @param pathPrefix the path prefix to append to the current uri
* @return the computes hash value
*/
public String computeHash(WebContext ctx, String pathPrefix) {
public String computeHash(WebContext webContext, String pathPrefix) {
try {
return doComputeHash(ctx, pathPrefix);
return doComputeHash(webContext, pathPrefix);
} catch (Exception e) {
throw Exceptions.handle(UserContext.LOG, e);
}
}

private String doComputeHash(final WebContext ctx, final String pathPrefix) throws Exception {
if (aws4HashCalculator.supports(ctx)) {
return aws4HashCalculator.computeHash(ctx, pathPrefix);
private String doComputeHash(final WebContext webContext, final String pathPrefix) throws Exception {
if (aws4HashCalculator.supports(webContext)) {
return aws4HashCalculator.computeHash(webContext, pathPrefix);
} else {
return legacyHashCalculator.computeHash(ctx, pathPrefix);
return legacyHashCalculator.computeHash(webContext, pathPrefix);
}
}
}
22 changes: 11 additions & 11 deletions src/main/java/ninja/AwsLegacyHashCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,27 +62,27 @@ public class AwsLegacyHashCalculator {
/**
* Computes the authentication hash as specified by the AWS SDK for verification purposes.
*
* @param ctx the current request to fetch parameters from
* @param webContext the current request to fetch parameters from
* @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(WebContext) effective URI}
* of the request
* @return the computes hash value
* @throws Exception when hashing fails
*/
public String computeHash(WebContext ctx, String pathPrefix) throws Exception {
StringBuilder stringToSign = new StringBuilder(ctx.getRequest().method().name());
public String computeHash(WebContext webContext, String pathPrefix) throws Exception {
StringBuilder stringToSign = new StringBuilder(webContext.getRequest().method().name());
stringToSign.append("\n");
stringToSign.append(ctx.getHeaderValue("Content-MD5").asString(""));
stringToSign.append(webContext.getHeaderValue("Content-MD5").asString(""));
stringToSign.append("\n");
stringToSign.append(ctx.getHeaderValue("Content-Type").asString(""));
stringToSign.append(webContext.getHeaderValue("Content-Type").asString(""));
stringToSign.append("\n");

String date = ctx.get("Expires").asString(ctx.getHeaderValue("Date").asString(""));
if (ctx.getHeaderValue("x-amz-date").isNull()) {
String date = webContext.get("Expires").asString(webContext.getHeaderValue("Date").asString(""));
if (webContext.getHeaderValue("x-amz-date").isNull()) {
stringToSign.append(date);
}
stringToSign.append("\n");

HttpHeaders requestHeaders = ctx.getRequest().headers();
HttpHeaders requestHeaders = webContext.getRequest().headers();
List<String> headers = requestHeaders.names()
.stream()
.filter(this::relevantAmazonHeader)
Expand All @@ -95,14 +95,14 @@ public String computeHash(WebContext ctx, String pathPrefix) throws Exception {
stringToSign.append("\n");
}

stringToSign.append(pathPrefix).append('/').append(S3Dispatcher.getEffectiveURI(ctx));
stringToSign.append(pathPrefix).append('/').append(S3Dispatcher.getEffectiveURI(webContext));

char separator = '?';
for (String parameterName : ctx.getParameterNames().stream().sorted().collect(Collectors.toList())) {
for (String parameterName : webContext.getParameterNames().stream().sorted().collect(Collectors.toList())) {
// Skip parameters that aren't part of the canonical signed string
if (SIGNED_PARAMETERS.contains(parameterName)) {
stringToSign.append(separator).append(parameterName);
String parameterValue = ctx.get(parameterName).asString();
String parameterValue = webContext.get(parameterName).asString();
if (Strings.isFilled(parameterValue)) {
stringToSign.append("=").append(parameterValue);
}
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/ninja/Bucket.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public class Bucket {

private final File publicMarker;

private static final Cache<String, Boolean> publicAccessCache = CacheManager.createLocalCache("public-bucket-access");
private static final Cache<String, Boolean> publicAccessCache =
CacheManager.createLocalCache("public-bucket-access");

/**
* Creates a new bucket based on the given directory.
Expand Down Expand Up @@ -195,7 +196,7 @@ public void outputObjects(XMLStructuredOutput output, int limit, @Nullable Strin
/**
* Very simplified stand-in for {@link Files#walkFileTree(Path, FileVisitor)} where we control the traversal order.
*
* @param path the start path.
* @param path the start path.
* @param visitor the visitor processing the files.
* @throws IOException forwarded from nested I/O operations.
*/
Expand All @@ -205,9 +206,7 @@ private void walkFileTreeOurWay(Path path, FileVisitor<? super Path> visitor) th
}

try (Stream<Path> children = Files.list(path)) {
children.filter(p -> filterObjects(p.toFile()))
.sorted(Bucket::compareUtf8Binary)
.forEach(p -> {
children.filter(p -> filterObjects(p.toFile())).sorted(Bucket::compareUtf8Binary).forEach(p -> {
try {
BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
visitor.visitFile(p, attrs);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ninja/BucketMigrator.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static void migratePublicMarkerVersion1To2(Bucket bucket) {
* The legacy file name is considered as-is and URL-encoded for general UTF-8 support. The properties file is
* prefixed with <tt>$</tt>, avoiding name clashes with other object files (where <tt>$</tt> would be encoded).
*
* @param bucket the bucket to migrate
* @param bucket the bucket to migrate
* @param legacyObject the legacy object to migrate
*/
private static void migrateObjectVersion1To2(Bucket bucket, File legacyObject) {
Expand Down
Loading

0 comments on commit c5cc203

Please sign in to comment.