Repository: jclouds Updated Branches: refs/heads/master c20fcb8cd -> 8bddbb496
http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java index e8009e4..75dd648 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java +++ b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java @@ -16,243 +16,11 @@ */ package org.jclouds.s3.filters; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Iterables.get; -import static com.google.common.io.BaseEncoding.base64; -import static com.google.common.io.ByteStreams.readBytes; -import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_TAG; -import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; -import static org.jclouds.crypto.Macs.asByteProcessor; -import static org.jclouds.http.utils.Queries.queryParser; -import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_SERVICE_PATH; -import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS; -import static org.jclouds.util.Strings2.toInputStream; - -import java.util.Collection; -import java.util.Locale; -import java.util.Map.Entry; -import java.util.Set; - -import javax.annotation.Resource; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; - -import org.jclouds.Constants; -import org.jclouds.aws.domain.SessionCredentials; -import org.jclouds.crypto.Crypto; -import org.jclouds.date.TimeStamp; -import org.jclouds.domain.Credentials; -import org.jclouds.http.HttpException; -import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequestFilter; -import org.jclouds.http.HttpUtils; -import org.jclouds.http.internal.SignatureWire; -import org.jclouds.logging.Logger; -import org.jclouds.rest.RequestSigner; -import org.jclouds.s3.util.S3Utils; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; -import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.Ordering; -import com.google.common.collect.SortedSetMultimap; -import com.google.common.collect.TreeMultimap; -import com.google.common.io.ByteProcessor; -import com.google.common.net.HttpHeaders; /** * Signs the S3 request. */ -@Singleton -public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSigner { - - private static final Collection<String> FIRST_HEADERS_TO_SIGN = ImmutableList.of(HttpHeaders.DATE); - - private static final Set<String> SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy", - "requestPayment", "versioning", "versions", "versionId", "notification", "uploadId", "uploads", - "partNumber", "website", "response-content-type", "response-content-language", "response-expires", - "response-cache-control", "response-content-disposition", "response-content-encoding", "delete"); - - private final SignatureWire signatureWire; - private final Supplier<Credentials> creds; - private final Provider<String> timeStampProvider; - private final Crypto crypto; - private final HttpUtils utils; - - @Resource - @Named(Constants.LOGGER_SIGNATURE) - Logger signatureLog = Logger.NULL; - - private final String authTag; - private final String headerTag; - private final String servicePath; - private final boolean isVhostStyle; - - @Inject - public RequestAuthorizeSignature(SignatureWire signatureWire, @Named(PROPERTY_AUTH_TAG) String authTag, - @Named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS) boolean isVhostStyle, - @Named(PROPERTY_S3_SERVICE_PATH) String servicePath, @Named(PROPERTY_HEADER_TAG) String headerTag, - @org.jclouds.location.Provider Supplier<Credentials> creds, - @TimeStamp Provider<String> timeStampProvider, Crypto crypto, HttpUtils utils) { - this.isVhostStyle = isVhostStyle; - this.servicePath = servicePath; - this.headerTag = headerTag; - this.authTag = authTag; - this.signatureWire = signatureWire; - this.creds = creds; - this.timeStampProvider = timeStampProvider; - this.crypto = crypto; - this.utils = utils; - } - - public HttpRequest filter(HttpRequest request) throws HttpException { - request = replaceDateHeader(request); - Credentials current = creds.get(); - if (current instanceof SessionCredentials) { - request = replaceSecurityTokenHeader(request, SessionCredentials.class.cast(current)); - } - String signature = calculateSignature(createStringToSign(request)); - request = replaceAuthorizationHeader(request, signature); - utils.logRequest(signatureLog, request, "<<"); - return request; - } - - HttpRequest replaceSecurityTokenHeader(HttpRequest request, SessionCredentials current) { - return request.toBuilder().replaceHeader("x-amz-security-token", current.getSessionToken()).build(); - } - - protected HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) { - request = request.toBuilder() - .replaceHeader(HttpHeaders.AUTHORIZATION, authTag + " " + creds.get().identity + ":" + signature).build(); - return request; - } - - HttpRequest replaceDateHeader(HttpRequest request) { - request = request.toBuilder().replaceHeader(HttpHeaders.DATE, timeStampProvider.get()).build(); - return request; - } - - public String createStringToSign(HttpRequest request) { - utils.logRequest(signatureLog, request, ">>"); - SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create(); - StringBuilder buffer = new StringBuilder(); - // re-sign the request - appendMethod(request, buffer); - appendPayloadMetadata(request, buffer); - appendHttpHeaders(request, canonicalizedHeaders); - - // Remove default date timestamp if "x-amz-date" is set. - if (canonicalizedHeaders.containsKey("x-" + headerTag + "-date")) { - canonicalizedHeaders.removeAll("date"); - } - - appendAmzHeaders(canonicalizedHeaders, buffer); - appendBucketName(request, buffer); - appendUriPath(request, buffer); - if (signatureWire.enabled()) - signatureWire.output(buffer.toString()); - return buffer.toString(); - } - - String calculateSignature(String toSign) throws HttpException { - String signature = sign(toSign); - if (signatureWire.enabled()) - signatureWire.input(toInputStream(signature)); - return signature; - } - - public String sign(String toSign) { - try { - ByteProcessor<byte[]> hmacSHA1 = asByteProcessor(crypto.hmacSHA1(creds.get().credential.getBytes(UTF_8))); - return base64().encode(readBytes(toInputStream(toSign), hmacSHA1)); - } catch (Exception e) { - throw new HttpException("error signing request", e); - } - } - - void appendMethod(HttpRequest request, StringBuilder toSign) { - toSign.append(request.getMethod()).append("\n"); - } - - @VisibleForTesting - void appendAmzHeaders(SortedSetMultimap<String, String> canonicalizedHeaders, StringBuilder toSign) { - for (Entry<String, String> header : canonicalizedHeaders.entries()) { - String key = header.getKey(); - if (key.startsWith("x-" + headerTag + "-")) { - toSign.append(String.format("%s:%s\n", key.toLowerCase(), header.getValue())); - } - } - } - - void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) { - // note that we fall back to headers, and some requests such as ?uploads do not have a - // payload, yet specify payload related parameters - buffer.append( - request.getPayload() == null ? Strings.nullToEmpty(request.getFirstHeaderOrNull("Content-MD5")) : - HttpUtils.nullToEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata() - .getContentMD5())).append("\n"); - buffer.append( - Strings.nullToEmpty(request.getPayload() == null ? request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE) - : request.getPayload().getContentMetadata().getContentType())).append("\n"); - for (String header : FIRST_HEADERS_TO_SIGN) - buffer.append(HttpUtils.nullToEmpty(request.getHeaders().get(header))).append("\n"); - } - - @VisibleForTesting - void appendHttpHeaders(HttpRequest request, SortedSetMultimap<String, String> canonicalizedHeaders) { - Multimap<String, String> headers = request.getHeaders(); - for (Entry<String, String> header : headers.entries()) { - if (header.getKey() == null) - continue; - String key = header.getKey().toString().toLowerCase(Locale.getDefault()); - // Ignore any headers that are not particularly interesting. - if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase("Content-MD5") - || key.equalsIgnoreCase(HttpHeaders.DATE) || key.startsWith("x-" + headerTag + "-")) { - canonicalizedHeaders.put(key, header.getValue()); - } - } - } - - @VisibleForTesting - void appendBucketName(HttpRequest req, StringBuilder toSign) { - String bucketName = S3Utils.getBucketName(req); - - // If we have a payload/bucket/container that is not all lowercase, vhost-style URLs are not an option and must be - // automatically converted to their path-based equivalent. This should only be possible for AWS-S3 since it is - // the only S3 implementation configured to allow uppercase payload/bucket/container names. - // - // http://code.google.com/p/jclouds/issues/detail?id=992 - if (isVhostStyle && bucketName != null && bucketName.equals(bucketName.toLowerCase())) - toSign.append(servicePath).append(bucketName); - } - - @VisibleForTesting - void appendUriPath(HttpRequest request, StringBuilder toSign) { - - toSign.append(request.getEndpoint().getRawPath()); - - // ...however, there are a few exceptions that must be included in the - // signed URI. - if (request.getEndpoint().getQuery() != null) { - Multimap<String, String> params = queryParser().apply(request.getEndpoint().getQuery()); - char separator = '?'; - for (String paramName : Ordering.natural().sortedCopy(params.keySet())) { - // Skip any parameters that aren't part of the canonical signed string - if (!SIGNED_PARAMETERS.contains(paramName)) - continue; - toSign.append(separator).append(paramName); - String paramValue = get(params.get(paramName), 0); - if (paramValue != null) { - toSign.append("=").append(paramValue); - } - separator = '&'; - } - } - } +public interface RequestAuthorizeSignature extends HttpRequestFilter { } http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2.java b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2.java new file mode 100644 index 0000000..dab003c --- /dev/null +++ b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.s3.filters; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.collect.Iterables.get; +import static com.google.common.io.BaseEncoding.base64; +import static com.google.common.io.ByteStreams.readBytes; +import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_TAG; +import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; +import static org.jclouds.crypto.Macs.asByteProcessor; +import static org.jclouds.http.utils.Queries.queryParser; +import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_SERVICE_PATH; +import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS; +import static org.jclouds.util.Strings2.toInputStream; + +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.aws.domain.SessionCredentials; +import org.jclouds.crypto.Crypto; +import org.jclouds.date.TimeStamp; +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpUtils; +import org.jclouds.http.internal.SignatureWire; +import org.jclouds.logging.Logger; +import org.jclouds.rest.RequestSigner; +import org.jclouds.s3.util.S3Utils; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.common.io.ByteProcessor; +import com.google.common.net.HttpHeaders; + +/** + * AWS Sign V2 + */ +@Singleton +public class RequestAuthorizeSignatureV2 implements RequestAuthorizeSignature, RequestSigner { + private static final Collection<String> FIRST_HEADERS_TO_SIGN = ImmutableList.of(HttpHeaders.DATE); + + private static final Set<String> SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", + "policy", "requestPayment", "versioning", "versions", "versionId", "notification", "uploadId", "uploads", + "partNumber", "website", "response-content-type", "response-content-language", "response-expires", + "response-cache-control", "response-content-disposition", "response-content-encoding", "delete"); + + private final SignatureWire signatureWire; + private final Supplier<Credentials> creds; + private final Provider<String> timeStampProvider; + private final Crypto crypto; + private final HttpUtils utils; + + @Resource + @Named(Constants.LOGGER_SIGNATURE) + Logger signatureLog = Logger.NULL; + + private final String authTag; + private final String headerTag; + private final String servicePath; + private final boolean isVhostStyle; + + @Inject + public RequestAuthorizeSignatureV2(SignatureWire signatureWire, @Named(PROPERTY_AUTH_TAG) String authTag, + @Named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS) boolean isVhostStyle, + @Named(PROPERTY_S3_SERVICE_PATH) String servicePath, @Named(PROPERTY_HEADER_TAG) String headerTag, + @org.jclouds.location.Provider Supplier<Credentials> creds, + @TimeStamp Provider<String> timeStampProvider, Crypto crypto, HttpUtils utils) { + this.isVhostStyle = isVhostStyle; + this.servicePath = servicePath; + this.headerTag = headerTag; + this.authTag = authTag; + this.signatureWire = signatureWire; + this.creds = creds; + this.timeStampProvider = timeStampProvider; + this.crypto = crypto; + this.utils = utils; + } + + public HttpRequest filter(HttpRequest request) throws HttpException { + request = replaceDateHeader(request); + Credentials current = creds.get(); + if (current instanceof SessionCredentials) { + request = replaceSecurityTokenHeader(request, SessionCredentials.class.cast(current)); + } + String signature = calculateSignature(createStringToSign(request)); + request = replaceAuthorizationHeader(request, signature); + utils.logRequest(signatureLog, request, "<<"); + return request; + } + + HttpRequest replaceSecurityTokenHeader(HttpRequest request, SessionCredentials current) { + return request.toBuilder().replaceHeader("x-amz-security-token", current.getSessionToken()).build(); + } + + protected HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) { + request = request.toBuilder() + .replaceHeader(HttpHeaders.AUTHORIZATION, + authTag + " " + creds.get().identity + ":" + signature).build(); + return request; + } + + HttpRequest replaceDateHeader(HttpRequest request) { + request = request.toBuilder().replaceHeader(HttpHeaders.DATE, timeStampProvider.get()).build(); + return request; + } + + public String createStringToSign(HttpRequest request) { + utils.logRequest(signatureLog, request, ">>"); + SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create(); + StringBuilder buffer = new StringBuilder(); + // re-sign the request + appendMethod(request, buffer); + appendPayloadMetadata(request, buffer); + appendHttpHeaders(request, canonicalizedHeaders); + + // Remove default date timestamp if "x-amz-date" is set. + if (canonicalizedHeaders.containsKey("x-" + headerTag + "-date")) { + canonicalizedHeaders.removeAll("date"); + } + + appendAmzHeaders(canonicalizedHeaders, buffer); + appendBucketName(request, buffer); + appendUriPath(request, buffer); + if (signatureWire.enabled()) { + signatureWire.output(buffer.toString()); + } + return buffer.toString(); + } + + String calculateSignature(String toSign) throws HttpException { + String signature = sign(toSign); + if (signatureWire.enabled()) { + signatureWire.input(toInputStream(signature)); + } + return signature; + } + + public String sign(String toSign) { + try { + ByteProcessor<byte[]> hmacSHA1 = asByteProcessor( + crypto.hmacSHA1(creds.get().credential.getBytes(UTF_8))); + return base64().encode(readBytes(toInputStream(toSign), hmacSHA1)); + } catch (Exception e) { + throw new HttpException("error signing request", e); + } + } + + void appendMethod(HttpRequest request, StringBuilder toSign) { + toSign.append(request.getMethod()).append("\n"); + } + + @VisibleForTesting + void appendAmzHeaders(SortedSetMultimap<String, String> canonicalizedHeaders, StringBuilder toSign) { + for (Map.Entry<String, String> header : canonicalizedHeaders.entries()) { + String key = header.getKey(); + if (key.startsWith("x-" + headerTag + "-")) { + toSign.append(String.format("%s:%s\n", key.toLowerCase(), header.getValue())); + } + } + } + + void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) { + // note that we fall back to headers, and some requests such as ?uploads do not have a + // payload, yet specify payload related parameters + buffer.append( + request.getPayload() == null ? Strings.nullToEmpty(request.getFirstHeaderOrNull("Content-MD5")) : + HttpUtils.nullToEmpty( + request.getPayload() == null ? null : request.getPayload().getContentMetadata() + .getContentMD5())).append("\n"); + buffer.append( + Strings.nullToEmpty( + request.getPayload() == null ? request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE) + : request.getPayload().getContentMetadata().getContentType())).append("\n"); + for (String header : FIRST_HEADERS_TO_SIGN) + buffer.append(HttpUtils.nullToEmpty(request.getHeaders().get(header))).append("\n"); + } + + @VisibleForTesting + void appendHttpHeaders(HttpRequest request, SortedSetMultimap<String, String> canonicalizedHeaders) { + Multimap<String, String> headers = request.getHeaders(); + for (Map.Entry<String, String> header : headers.entries()) { + if (header.getKey() == null) { + continue; + } + String key = header.getKey().toString().toLowerCase(Locale.getDefault()); + // Ignore any headers that are not particularly interesting. + if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase("Content-MD5") + || key.equalsIgnoreCase(HttpHeaders.DATE) || key.startsWith("x-" + headerTag + "-")) { + canonicalizedHeaders.put(key, header.getValue()); + } + } + } + + @VisibleForTesting + void appendBucketName(HttpRequest req, StringBuilder toSign) { + String bucketName = S3Utils.getBucketName(req); + + // If we have a payload/bucket/container that is not all lowercase, vhost-style URLs are not an option and must be + // automatically converted to their path-based equivalent. This should only be possible for AWS-S3 since it is + // the only S3 implementation configured to allow uppercase payload/bucket/container names. + // + // http://code.google.com/p/jclouds/issues/detail?id=992 + if (isVhostStyle && bucketName != null && bucketName.equals(bucketName.toLowerCase())) { + toSign.append(servicePath).append(bucketName); + } + } + + @VisibleForTesting + void appendUriPath(HttpRequest request, StringBuilder toSign) { + + toSign.append(request.getEndpoint().getRawPath()); + + // ...however, there are a few exceptions that must be included in the + // signed URI. + if (request.getEndpoint().getQuery() != null) { + Multimap<String, String> params = queryParser().apply(request.getEndpoint().getQuery()); + char separator = '?'; + for (String paramName : Ordering.natural().sortedCopy(params.keySet())) { + // Skip any parameters that aren't part of the canonical signed string + if (!SIGNED_PARAMETERS.contains(paramName)) { + continue; + } + toSign.append(separator).append(paramName); + String paramValue = get(params.get(paramName), 0); + if (paramValue != null) { + toSign.append("=").append(paramValue); + } + separator = '&'; + } + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4.java b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4.java new file mode 100644 index 0000000..4e4edbd --- /dev/null +++ b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.s3.filters; + +import com.google.common.reflect.TypeToken; +import com.google.inject.Singleton; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.jclouds.s3.S3Client; + +import javax.inject.Inject; + +@Singleton +public class RequestAuthorizeSignatureV4 implements RequestAuthorizeSignature { + + private static final String PUT_OBJECT_METHOD = "putObject"; + private static final TypeToken<S3Client> S3_CLIENT_TYPE = new TypeToken<S3Client>() { + }; + + private final Aws4SignerForAuthorizationHeader signerForAuthorizationHeader; + private final Aws4SignerForChunkedUpload signerForChunkedUpload; + private final Aws4SignerForQueryString signerForQueryString; + + @Inject + public RequestAuthorizeSignatureV4(Aws4SignerForAuthorizationHeader signerForAuthorizationHeader, + Aws4SignerForChunkedUpload signerForChunkedUpload, + Aws4SignerForQueryString signerForQueryString) { + this.signerForAuthorizationHeader = signerForAuthorizationHeader; + this.signerForChunkedUpload = signerForChunkedUpload; + this.signerForQueryString = signerForQueryString; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + // request use chunked upload + if (useChunkedUpload(request)) { + return signForChunkedUpload(request); + } + return signForAuthorizationHeader(request); + } + + /** + * returns true, if use AWS S3 chunked upload. + */ + protected boolean useChunkedUpload(HttpRequest request) { + // only S3Client putObject method, payload not null, content-length > 0 and cannot repeatable + if (!GeneratedHttpRequest.class.isAssignableFrom(request.getClass())) { + return false; + } + GeneratedHttpRequest req = GeneratedHttpRequest.class.cast(request); + + // s3 client type and method name is putObject + if (S3_CLIENT_TYPE.equals(req.getInvocation().getInvokable().getOwnerType()) && + !PUT_OBJECT_METHOD.equals(req.getInvocation().getInvokable().getName())) { + return false; + } + + Payload payload = req.getPayload(); + + // check payload null or payload.contentMetadata null + if (payload == null || payload.getContentMetadata() == null) { + return false; + } + + Long contentLength = payload.getContentMetadata().getContentLength(); + + if (contentLength == null) { + return false; + } + + return contentLength > 0l && !payload.isRepeatable(); + } + + protected HttpRequest signForAuthorizationHeader(HttpRequest request) { + return signerForAuthorizationHeader.sign(request); + } + + protected HttpRequest signForChunkedUpload(HttpRequest request) { + return signerForChunkedUpload.sign(request); + } + + // Authenticating Requests by Using Query Parameters (AWS Signature Version 4) + + /** + * Using query parameters to authenticate requests is useful when you want to express a request entirely in a URL. + * This method is also referred as presigning a URL. Presigned URLs enable you to grant temporary access to your + * Amazon S3 resources. The end user can then enter the presigned URL in his or her browser to access the specific + * Amazon S3 resource. You can also use presigned URLs to embed clickable links in HTML. + * <p/> + * For example, you might store videos in an Amazon S3 bucket and make them available on your website by using presigned URLs. + * Identifies the version of AWS Signature and the algorithm that you used to calculate the signature. + */ + public HttpRequest signForTemporaryAccess(HttpRequest request, long timeInSeconds) { + return signerForQueryString.sign(request, timeInSeconds); + } + + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/main/java/org/jclouds/s3/reference/S3Constants.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/main/java/org/jclouds/s3/reference/S3Constants.java b/apis/s3/src/main/java/org/jclouds/s3/reference/S3Constants.java index a376ae3..7569573 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/reference/S3Constants.java +++ b/apis/s3/src/main/java/org/jclouds/s3/reference/S3Constants.java @@ -31,6 +31,7 @@ public final class S3Constants { public static final String DELIMITER = "delimiter"; public static final String PROPERTY_S3_SERVICE_PATH = "jclouds.s3.service-path"; public static final String PROPERTY_S3_VIRTUAL_HOST_BUCKETS = "jclouds.s3.virtual-host-buckets"; + public static final String PROPERTY_JCLOUDS_S3_CHUNKED_SIZE = "jclouds.s3.chunked.size"; private S3Constants() { throw new AssertionError("intentionally unimplemented"); http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/test/java/org/jclouds/s3/filters/AwsHostNameUtilsTest.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/filters/AwsHostNameUtilsTest.java b/apis/s3/src/test/java/org/jclouds/s3/filters/AwsHostNameUtilsTest.java new file mode 100644 index 0000000..6e160cd --- /dev/null +++ b/apis/s3/src/test/java/org/jclouds/s3/filters/AwsHostNameUtilsTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.s3.filters; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.net.URI; + +/** + * Tests parser region and service + */ +public class AwsHostNameUtilsTest { + @Test + public void testParseRegion() { + Assert.assertEquals( + AwsHostNameUtils.parseRegionName("test.s3.cn-north-1.amazonaws.com.cn", "s3"), + "cn-north-1" + ); + + + } + + @Test + // default region + public void testParseDefaultRegion() { + Assert.assertEquals( + AwsHostNameUtils.parseRegionName("s3.amazonaws.com", "s3"), + "us-east-1" + ); + } + + @Test + // test s3 service + public void testParseService() { + Assert.assertEquals( + AwsHostNameUtils.parseServiceName(URI.create("https://s3.amazonaws.com")), + "s3" + ); + + + Assert.assertEquals( + AwsHostNameUtils.parseServiceName(URI.create("https://test-bucket.s3.cn-north-1.amazonaws.com.cn")), + "s3" + ); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java deleted file mode 100644 index 794701d..0000000 --- a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file 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. - */ -package org.jclouds.s3.filters; - -import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; -import static org.jclouds.reflect.Reflection2.method; -import static org.testng.Assert.assertEquals; - -import java.net.URI; -import java.util.Properties; - -import org.jclouds.blobstore.binders.BindBlobToMultipartFormTest; -import org.jclouds.http.HttpRequest; -import org.jclouds.rest.internal.GeneratedHttpRequest; -import org.jclouds.s3.S3Client; -import org.jclouds.s3.domain.AccessControlList; -import org.jclouds.s3.domain.CannedAccessPolicy; -import org.jclouds.s3.domain.S3Object; -import org.jclouds.s3.internal.BaseS3ClientTest; -import org.jclouds.s3.options.PutObjectOptions; -import org.jclouds.s3.reference.S3Headers; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.SortedSetMultimap; -import com.google.common.collect.TreeMultimap; -import com.google.common.net.HttpHeaders; -/** - * Tests behavior of {@code RequestAuthorizeSignature} - */ -// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire -@Test(groups = "unit", testName = "RequestAuthorizeSignatureTest") -public class RequestAuthorizeSignatureTest extends BaseS3ClientTest<S3Client> { - String bucketName = "bucket"; - - @DataProvider(parallel = true) - public Object[][] dataProvider() throws NoSuchMethodException { - return new Object[][] { { listOwnedBuckets() }, { putObject() }, { putBucketAcl() } - - }; - } - - /** - * NOTE this test is dependent on how frequently the timestamp updates. At the time of writing, - * this was once per second. If this timestamp update interval is increased, it could make this - * test appear to hang for a long time. - */ - @Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000) - void testIdempotent(HttpRequest request) { - request = filter.filter(request); - String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION); - String date = request.getFirstHeaderOrNull(HttpHeaders.DATE); - int iterations = 1; - while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) { - date = request.getFirstHeaderOrNull(HttpHeaders.DATE); - request = filter.filter(request); - if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) - assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format( - "sig: %s != %s on attempt %s", signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION), - iterations); - else - iterations++; - - } - System.out.printf("%s: %d iterations before the timestamp updated %n", Thread.currentThread().getName(), - iterations); - } - - @Test - void testAppendBucketNameHostHeader() throws SecurityException, NoSuchMethodException { - GeneratedHttpRequest request = processor.createRequest( - method(S3Client.class, "getBucketLocation", String.class), - ImmutableList.<Object> of("bucket")); - StringBuilder builder = new StringBuilder(); - filter.appendBucketName(request, builder); - assertEquals(builder.toString(), ""); - } - - @Test - void testAclQueryString() throws SecurityException, NoSuchMethodException { - HttpRequest request = putBucketAcl(); - StringBuilder builder = new StringBuilder(); - filter.appendUriPath(request, builder); - assertEquals(builder.toString(), "/" + bucketName + "?acl"); - } - - private GeneratedHttpRequest putBucketAcl() throws NoSuchMethodException { - return processor.createRequest( - method(S3Client.class, "putBucketACL", String.class, AccessControlList.class), - ImmutableList.<Object> of("bucket", - AccessControlList.fromCannedAccessPolicy(CannedAccessPolicy.PRIVATE, "1234"))); - } - - // "?acl", "?location", "?logging", "?uploads", or "?torrent" - - @Test - void testAppendBucketNameHostHeaderService() throws SecurityException, NoSuchMethodException { - HttpRequest request = listOwnedBuckets(); - StringBuilder builder = new StringBuilder(); - filter.appendBucketName(request, builder); - assertEquals(builder.toString(), ""); - } - - private GeneratedHttpRequest listOwnedBuckets() throws NoSuchMethodException { - return processor.createRequest(method(S3Client.class, "listOwnedBuckets"), - ImmutableList.of()); - } - - @Test - void testHeadersGoLowercase() throws SecurityException, NoSuchMethodException { - HttpRequest request = putObject(); - SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create(); - filter.appendHttpHeaders(request, canonicalizedHeaders); - StringBuilder builder = new StringBuilder(); - filter.appendAmzHeaders(canonicalizedHeaders, builder); - assertEquals(builder.toString(), S3Headers.USER_METADATA_PREFIX + "adrian:foo\n"); - } - - private HttpRequest putObject() throws NoSuchMethodException { - S3Object object = blobToS3Object.apply(BindBlobToMultipartFormTest.TEST_BLOB); - object.getMetadata().getUserMetadata().put("Adrian", "foo"); - return processor.createRequest(method(S3Client.class, "putObject", String.class, - S3Object.class, PutObjectOptions[].class), ImmutableList.<Object> of("bucket", object)); - } - - @Test - void testAppendBucketNameInURIPath() throws SecurityException, NoSuchMethodException { - GeneratedHttpRequest request = processor.createRequest( - method(S3Client.class, "getBucketLocation", String.class), - ImmutableList.<Object> of(bucketName)); - URI uri = request.getEndpoint(); - assertEquals(uri.getHost(), "localhost"); - assertEquals(uri.getPath(), "/" + bucketName); - } - - @Override - protected Properties setupProperties() { - Properties overrides = super.setupProperties(); - overrides.setProperty(PROPERTY_SESSION_INTERVAL, 1 + ""); - return overrides; - } -} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2Test.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2Test.java b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2Test.java new file mode 100644 index 0000000..1733a1b --- /dev/null +++ b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV2Test.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.s3.filters; + +import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; +import static org.jclouds.reflect.Reflection2.method; +import static org.testng.Assert.assertEquals; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.blobstore.binders.BindBlobToMultipartFormTest; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.jclouds.s3.S3Client; +import org.jclouds.s3.domain.AccessControlList; +import org.jclouds.s3.domain.CannedAccessPolicy; +import org.jclouds.s3.domain.S3Object; +import org.jclouds.s3.internal.BaseS3ClientTest; +import org.jclouds.s3.options.PutObjectOptions; +import org.jclouds.s3.reference.S3Headers; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.common.net.HttpHeaders; + +/** + * Tests behavior of {@code RequestAuthorizeSignatureV2} + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "RequestAuthorizeSignatureV2Test") +public class RequestAuthorizeSignatureV2Test extends BaseS3ClientTest<S3Client> { + String bucketName = "bucket"; + + @DataProvider(parallel = true) + public Object[][] dataProvider() throws NoSuchMethodException { + return new Object[][]{{listOwnedBuckets()}, {putObject()}, {putBucketAcl()} + + }; + } + + /** + * NOTE this test is dependent on how frequently the timestamp updates. At the time of writing, + * this was once per second. If this timestamp update interval is increased, it could make this + * test appear to hang for a long time. + */ + @Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000) + void testIdempotent(HttpRequest request) { + request = filter.filter(request); + String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION); + String date = request.getFirstHeaderOrNull(HttpHeaders.DATE); + int iterations = 1; + while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) { + date = request.getFirstHeaderOrNull(HttpHeaders.DATE); + request = filter.filter(request); + if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) + assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format( + "sig: %s != %s on attempt %s", signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION), + iterations); + else + iterations++; + + } + System.out.printf("%s: %d iterations before the timestamp updated %n", Thread.currentThread().getName(), + iterations); + } + + @Test + void testAppendBucketNameHostHeader() throws SecurityException, NoSuchMethodException { + GeneratedHttpRequest request = processor.createRequest( + method(S3Client.class, "getBucketLocation", String.class), + ImmutableList.<Object>of("bucket")); + StringBuilder builder = new StringBuilder(); + ((RequestAuthorizeSignatureV2) filter).appendBucketName(request, builder); + assertEquals(builder.toString(), ""); + } + + @Test + void testAclQueryString() throws SecurityException, NoSuchMethodException { + HttpRequest request = putBucketAcl(); + StringBuilder builder = new StringBuilder(); + ((RequestAuthorizeSignatureV2) filter).appendUriPath(request, builder); + assertEquals(builder.toString(), "/" + bucketName + "?acl"); + } + + private GeneratedHttpRequest putBucketAcl() throws NoSuchMethodException { + return processor.createRequest( + method(S3Client.class, "putBucketACL", String.class, AccessControlList.class), + ImmutableList.<Object>of("bucket", + AccessControlList.fromCannedAccessPolicy(CannedAccessPolicy.PRIVATE, "1234"))); + } + + // "?acl", "?location", "?logging", "?uploads", or "?torrent" + + @Test + void testAppendBucketNameHostHeaderService() throws SecurityException, NoSuchMethodException { + HttpRequest request = listOwnedBuckets(); + StringBuilder builder = new StringBuilder(); + ((RequestAuthorizeSignatureV2) filter).appendBucketName(request, builder); + assertEquals(builder.toString(), ""); + } + + private GeneratedHttpRequest listOwnedBuckets() throws NoSuchMethodException { + return processor.createRequest(method(S3Client.class, "listOwnedBuckets"), + ImmutableList.of()); + } + + @Test + void testHeadersGoLowercase() throws SecurityException, NoSuchMethodException { + HttpRequest request = putObject(); + SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create(); + ((RequestAuthorizeSignatureV2) filter).appendHttpHeaders(request, canonicalizedHeaders); + StringBuilder builder = new StringBuilder(); + ((RequestAuthorizeSignatureV2) filter).appendAmzHeaders(canonicalizedHeaders, builder); + assertEquals(builder.toString(), S3Headers.USER_METADATA_PREFIX + "adrian:foo\n"); + } + + private HttpRequest putObject() throws NoSuchMethodException { + S3Object object = blobToS3Object.apply(BindBlobToMultipartFormTest.TEST_BLOB); + object.getMetadata().getUserMetadata().put("Adrian", "foo"); + return processor.createRequest(method(S3Client.class, "putObject", String.class, + S3Object.class, PutObjectOptions[].class), ImmutableList.<Object>of("bucket", object)); + } + + @Test + void testAppendBucketNameInURIPath() throws SecurityException, NoSuchMethodException { + GeneratedHttpRequest request = processor.createRequest( + method(S3Client.class, "getBucketLocation", String.class), + ImmutableList.<Object>of(bucketName)); + URI uri = request.getEndpoint(); + assertEquals(uri.getHost(), "localhost"); + assertEquals(uri.getPath(), "/" + bucketName); + } + + @Override + protected Properties setupProperties() { + Properties overrides = super.setupProperties(); + overrides.setProperty(PROPERTY_SESSION_INTERVAL, 1 + ""); + return overrides; + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4ChunkedUploadTest.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4ChunkedUploadTest.java b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4ChunkedUploadTest.java new file mode 100644 index 0000000..39a6fe1 --- /dev/null +++ b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4ChunkedUploadTest.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.s3.filters; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import com.google.common.net.HttpHeaders; +import com.google.inject.Injector; +import com.google.inject.Module; +import org.jclouds.Constants; +import org.jclouds.ContextBuilder; +import org.jclouds.date.DateService; +import org.jclouds.date.TimeStamp; +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.logging.config.NullLoggingModule; +import org.jclouds.reflect.Invocation; +import org.jclouds.rest.ConfiguresHttpApi; +import org.jclouds.rest.internal.BaseRestApiTest; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.jclouds.s3.S3ApiMetadata; +import org.jclouds.s3.S3Client; +import org.jclouds.s3.config.S3HttpApiModule; +import org.jclouds.s3.domain.S3Object; +import org.jclouds.s3.options.PutObjectOptions; +import org.jclouds.util.Closeables2; +import org.testng.annotations.Test; + +import javax.inject.Named; +import javax.xml.ws.http.HTTPException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Date; + +import static com.google.common.io.BaseEncoding.base16; +import static org.jclouds.reflect.Reflection2.method; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +/** + * Tests behavior of {@code RequestAuthorizeSignature} + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "RequestAuthorizeSignatureV4ChunkedUploadTest") +public class RequestAuthorizeSignatureV4ChunkedUploadTest { + private static final String CONTENT_SEED = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tortor metus, sagittis eget augue ut,\n" + + "feugiat vehicula risus. Integer tortor mauris, vehicula nec mollis et, consectetur eget tortor. In ut\n" + + "elit sagittis, ultrices est ut, iaculis turpis. In hac habitasse platea dictumst. Donec laoreet tellus\n" + + "at auctor tempus. Praesent nec diam sed urna sollicitudin vehicula eget id est. Vivamus sed laoreet\n" + + "lectus. Aliquam convallis condimentum risus, vitae porta justo venenatis vitae. Phasellus vitae nunc\n" + + "varius, volutpat quam nec, mollis urna. Donec tempus, nisi vitae gravida facilisis, sapien sem malesuada\n" + + "purus, id semper libero ipsum condimentum nulla. Suspendisse vel mi leo. Morbi pellentesque placerat congue.\n" + + "Nunc sollicitudin nunc diam, nec hendrerit dui commodo sed. Duis dapibus commodo elit, id commodo erat\n" + + "congue id. Aliquam erat volutpat.\n"; + + private static final String CHUKED_UPLOAD_PAYLOAD_SHA256 = "2b6da230b03189254b2ceafe689c5298cfdd288869e80b2b9369da8f8f0a3d99"; + + private static final String PUT_OBJECT_AUTHORIZATION = "AWS4-HMAC-SHA256 " + + "Credential=AKIAPAEBI3QI4EXAMPLE/20150203/cn-north-1/s3/aws4_request, " + + "SignedHeaders=content-encoding;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-storage-class, " + + "Signature=3db48b3d786d599e8e785ba66030e8a9249c678a52f2432bf6fd44c97cb3145f"; + + + private static final String IDENTITY = "AKIAPAEBI3QI4EXAMPLE"; + private static final String CREDENTIAL = "oHkkcPcOjJnoAXpjT8GXdNeBjo6Ru7QeFExAmPlE"; + private static final String TIMESTAMP = "Thu, 03 Feb 2015 07:11:11 GMT"; + + private static final String BUCKET_NAME = "test-bucket"; + private static final String OBJECT_NAME = "ExampleChunkedObject.txt"; + + @ConfiguresHttpApi + private static final class TestS3HttpApiModule extends S3HttpApiModule<S3Client> { + @Override + protected String provideTimeStamp(@TimeStamp Supplier<String> cache) { + return TIMESTAMP; + } + + @Override + protected Supplier<Date> provideTimeStampCacheDate( + @Named(Constants.PROPERTY_SESSION_INTERVAL) long seconds, + @TimeStamp final Supplier<String> timestamp, + final DateService dateService) { + return Suppliers.ofInstance(dateService.rfc822DateParse(TIMESTAMP)); + } + } + + public static Injector injector(Credentials creds) { + return ContextBuilder.newBuilder(new S3ApiMetadata()) + .credentialsSupplier(Suppliers.<Credentials>ofInstance(creds)) + .modules(ImmutableList.<Module>of(new BaseRestApiTest.MockModule(), new NullLoggingModule(), + new TestS3HttpApiModule())) + .buildInjector(); + } + + public static RequestAuthorizeSignatureV4 filter(Credentials creds) { + return injector(creds).getInstance(RequestAuthorizeSignatureV4.class); + } + + Credentials temporaryCredentials = new Credentials.Builder() + .identity(IDENTITY) + .credential(CREDENTIAL) + .build(); + + + @Test + void testPutObjectWithChunkedUpload() { + Invocation invocation = Invocation.create( + method(S3Client.class, "putObject", String.class, S3Object.class, PutObjectOptions[].class), + ImmutableList.<Object>of(BUCKET_NAME)); + byte[] content = make65KPayload().getBytes(Charset.forName("UTF-8")); + HttpRequest putObject = GeneratedHttpRequest.builder().invocation(invocation) + .method("PUT") + .endpoint("https://" + BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn/" + OBJECT_NAME) + .addHeader(HttpHeaders.HOST, BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn") + .addHeader("x-amz-storage-class", "REDUCED_REDUNDANCY") + .build(); + Payload payload = Payloads.newInputStreamPayload(new ByteArrayInputStream(content)); + payload.getContentMetadata().setContentLength((long) content.length); + payload.getContentMetadata().setContentType("text/plain"); + putObject.setPayload(payload); + HttpRequest filtered = filter(temporaryCredentials).filter(putObject); + assertEquals(filtered.getFirstHeaderOrNull("Authorization"), PUT_OBJECT_AUTHORIZATION); + assertEquals(filtered.getPayload().getClass(), ChunkedUploadPayload.class); + + InputStream is = null; + try { + is = filtered.getPayload().openStream(); + assertEquals(base16().lowerCase().encode(hash(is)), CHUKED_UPLOAD_PAYLOAD_SHA256); + } catch (IOException e) { + fail("open stream error", e); + } finally { + Closeables2.closeQuietly(is); + } + } + + /** + * Want sample to upload 3 chunks for our selected chunk size of 64K; one + * full size chunk, one partial chunk and then the 0-byte terminator chunk. + * This routine just takes 1K of seed text and turns it into a 65K-or-so + * string for sample use. + */ + private static String make65KPayload() { + StringBuilder oneKSeed = new StringBuilder(); + while (oneKSeed.length() < 1024) { + oneKSeed.append(CONTENT_SEED); + } + + // now scale up to meet/exceed our requirement + StringBuilder output = new StringBuilder(); + for (int i = 0; i < 66; i++) { + output.append(oneKSeed); + } + return output.toString(); + } + + /** + * hash input with sha256 + * + * @param input + * @return hash result + * @throws HTTPException + */ + private static byte[] hash(InputStream input) { + try { + Hasher hasher = Hashing.sha256().newHasher(); + byte[] buffer = new byte[4096]; + int r; + while ((r = input.read(buffer)) != -1) { + hasher.putBytes(buffer, 0, r); + } + return hasher.hash().asBytes(); + } catch (Exception e) { + throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e); + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4Test.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4Test.java b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4Test.java new file mode 100644 index 0000000..9494a86 --- /dev/null +++ b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureV4Test.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.s3.filters; + +import static org.jclouds.reflect.Reflection2.method; +import static org.testng.Assert.assertEquals; + +import java.util.Date; + +import javax.inject.Named; + +import org.jclouds.Constants; +import org.jclouds.ContextBuilder; +import org.jclouds.date.DateService; +import org.jclouds.date.TimeStamp; +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.options.GetOptions; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.logging.config.NullLoggingModule; +import org.jclouds.reflect.Invocation; +import org.jclouds.rest.ConfiguresHttpApi; +import org.jclouds.rest.internal.BaseRestApiTest; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.jclouds.s3.S3ApiMetadata; +import org.jclouds.s3.S3Client; +import org.jclouds.s3.config.S3HttpApiModule; +import org.jclouds.s3.domain.S3Object; +import org.jclouds.s3.options.PutObjectOptions; +import org.testng.annotations.Test; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; +import com.google.common.net.HttpHeaders; +import com.google.inject.Injector; +import com.google.inject.Module; + +/** + * Tests behavior of {@code RequestAuthorizeSignature} + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire +@Test(groups = "unit", testName = "RequestAuthorizeSignatureV4Test") +public class RequestAuthorizeSignatureV4Test { + private static final String IDENTITY = "AKIAPAEBI3QI4EXAMPLE"; + private static final String CREDENTIAL = "oHkkcPcOjJnoAXpjT8GXdNeBjo6Ru7QeFExAmPlE"; + private static final String TIMESTAMP = "Thu, 03 Feb 2015 07:11:11 GMT"; + + private static final String GET_BUCKET_LOCATION_SIGNATURE_RESULT = "AWS4-HMAC-SHA256 " + + "Credential=AKIAPAEBI3QI4EXAMPLE/20150203/cn-north-1/s3/aws4_request, " + + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " + + "Signature=5634847b3ad6a857887ab0ccff2fcaf3d35ef3dc549a3c27ebc0f584a80494c3"; + + private static final String GET_OBJECT_RESULT = "AWS4-HMAC-SHA256 " + + "Credential=AKIAPAEBI3QI4EXAMPLE/20150203/cn-north-1/s3/aws4_request, " + + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " + + "Signature=fbd1d0f04a72907fb20ecd771644afd62cb689f91d26e9471b7a234531ec4718"; + + private static final String GET_OBJECT_ACL_RESULT = "AWS4-HMAC-SHA256 " + + "Credential=AKIAPAEBI3QI4EXAMPLE/20150203/cn-north-1/s3/aws4_request, " + + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " + + "Signature=52d7f31d249032b59781fe69c8124ff4bf209be3f374b28657a60d906c752381"; + + private static final String PUT_OBJECT_CONTENT = "text sign"; + + private static final String PUT_OBJECT_RESULT = "AWS4-HMAC-SHA256 " + + "Credential=AKIAPAEBI3QI4EXAMPLE/20150203/cn-north-1/s3/aws4_request, " + + "SignedHeaders=content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class, " + + "Signature=090f1bb1db984221ae1a20c5d12a82820a0d74b4be85f20daa1431604f41df08"; + + private static final String BUCKET_NAME = "test-bucket"; + private static final String OBJECT_NAME = "ExampleObject.txt"; + + @ConfiguresHttpApi + private static final class TestS3HttpApiModule extends S3HttpApiModule<S3Client> { + @Override + protected String provideTimeStamp(@TimeStamp Supplier<String> cache) { + return TIMESTAMP; + } + + @Override + protected Supplier<Date> provideTimeStampCacheDate( + @Named(Constants.PROPERTY_SESSION_INTERVAL) long seconds, + @TimeStamp final Supplier<String> timestamp, + final DateService dateService) { + return Suppliers.ofInstance(dateService.rfc822DateParse(TIMESTAMP)); + } + } + + public static Injector injector(Credentials creds) { + return ContextBuilder.newBuilder(new S3ApiMetadata()) + .credentialsSupplier(Suppliers.<Credentials>ofInstance(creds)) + .modules(ImmutableList.<Module>of(new BaseRestApiTest.MockModule(), new NullLoggingModule(), + new TestS3HttpApiModule())) + .buildInjector(); + } + + public static RequestAuthorizeSignatureV4 filter(Credentials creds) { + return injector(creds).getInstance(RequestAuthorizeSignatureV4.class); + } + + Credentials temporaryCredentials = new Credentials.Builder() + .identity(IDENTITY) + .credential(CREDENTIAL) + .build(); + + + @Test + void testGetBucketLocationSignature() { + Invocation invocation = Invocation.create(method(S3Client.class, "getBucketLocation", String.class), + ImmutableList.<Object>of(BUCKET_NAME)); + + HttpRequest getBucketLocation = GeneratedHttpRequest.builder().method("GET") + .invocation(invocation) + .endpoint("https://" + BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn/") + .addHeader(HttpHeaders.HOST, BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn") + .addQueryParam("location", "") + .build(); + HttpRequest filtered = filter(temporaryCredentials).filter(getBucketLocation); + assertEquals(filtered.getFirstHeaderOrNull("Authorization"), GET_BUCKET_LOCATION_SIGNATURE_RESULT); + } + + @Test + void testGetObjectSignature() { + Invocation invocation = Invocation.create(method(S3Client.class, "getObject", String.class, + String.class, GetOptions[].class), + ImmutableList.<Object>of(BUCKET_NAME, OBJECT_NAME, new GetOptions[0])); + + HttpRequest getObject = GeneratedHttpRequest.builder().method("GET") + .invocation(invocation) + .endpoint("https://" + BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn/" + OBJECT_NAME) + .addHeader(HttpHeaders.HOST, BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn") + .build(); + + HttpRequest filtered = filter(temporaryCredentials).filter(getObject); + assertEquals(filtered.getFirstHeaderOrNull("Authorization"), GET_OBJECT_RESULT); + + } + + @Test + void testGetObjectACLSignature() { + + Invocation invocation = Invocation.create(method(S3Client.class, "getObjectACL", String.class, String.class), + ImmutableList.<Object>of(BUCKET_NAME)); + + HttpRequest getObjectACL = GeneratedHttpRequest.builder().method("GET") + .invocation(invocation) + .endpoint("https://" + BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn/" + OBJECT_NAME) + .addHeader(HttpHeaders.HOST, BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn") + .addQueryParam("acl", "") + .build(); + + HttpRequest filtered = filter(temporaryCredentials).filter(getObjectACL); + assertEquals(filtered.getFirstHeaderOrNull("Authorization"), GET_OBJECT_ACL_RESULT); + } + + @Test + void testPutObjectSignature() { + Invocation invocation = Invocation.create(method(S3Client.class, "putObject", String.class, S3Object.class, + PutObjectOptions[].class), + ImmutableList.<Object>of(BUCKET_NAME)); + + Payload payload = Payloads.newStringPayload(PUT_OBJECT_CONTENT); + payload.getContentMetadata().setContentType("text/plain"); + + HttpRequest putObject = GeneratedHttpRequest.builder().method("PUT") + .invocation(invocation) + .endpoint("https://" + BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn/" + OBJECT_NAME) + .addHeader(HttpHeaders.HOST, BUCKET_NAME + ".s3.cn-north-1.amazonaws.com.cn") + .addHeader("x-amz-storage-class", "REDUCED_REDUNDANCY") + .payload(payload) + .build(); + + HttpRequest filtered = filter(temporaryCredentials).filter(putObject); + assertEquals(filtered.getFirstHeaderOrNull("Authorization"), PUT_OBJECT_RESULT); + + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/apis/s3/src/test/java/org/jclouds/s3/internal/BaseS3ClientTest.java ---------------------------------------------------------------------- diff --git a/apis/s3/src/test/java/org/jclouds/s3/internal/BaseS3ClientTest.java b/apis/s3/src/test/java/org/jclouds/s3/internal/BaseS3ClientTest.java index 63d4481..ee48a0f 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/internal/BaseS3ClientTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/internal/BaseS3ClientTest.java @@ -26,6 +26,7 @@ import org.jclouds.s3.S3ApiMetadata; import org.jclouds.s3.S3Client; import org.jclouds.s3.blobstore.functions.BlobToObject; import org.jclouds.s3.filters.RequestAuthorizeSignature; +import org.jclouds.s3.filters.RequestAuthorizeSignatureV2; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -47,7 +48,7 @@ public abstract class BaseS3ClientTest<T extends S3Client> extends BaseRestAnnot protected void setupFactory() throws IOException { super.setupFactory(); blobToS3Object = injector.getInstance(BlobToObject.class); - filter = injector.getInstance(RequestAuthorizeSignature.class); + filter = injector.getInstance(RequestAuthorizeSignatureV2.class); } public BaseS3ClientTest() { http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java ---------------------------------------------------------------------- diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java index 39649af..aa967c9 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java @@ -33,7 +33,7 @@ import org.jclouds.reflect.Invocation; import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.s3.blobstore.S3BlobRequestSigner; import org.jclouds.s3.blobstore.functions.BlobToObject; -import org.jclouds.s3.filters.RequestAuthorizeSignature; +import org.jclouds.s3.filters.RequestAuthorizeSignatureV2; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; @@ -44,7 +44,7 @@ import com.google.inject.Provider; public class AWSS3BlobRequestSigner extends S3BlobRequestSigner<AWSS3Client> { public static final String TEMPORARY_SIGNATURE_PARAM = "Signature"; - private final RequestAuthorizeSignature authSigner; + private final RequestAuthorizeSignatureV2 authSigner; private final String identity; private final DateService dateService; private final Provider<String> timeStampProvider; @@ -53,7 +53,7 @@ public class AWSS3BlobRequestSigner extends S3BlobRequestSigner<AWSS3Client> { public AWSS3BlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject, BlobToHttpGetOptions blob2HttpGetOptions, Class<AWSS3Client> interfaceClass, @org.jclouds.location.Provider Supplier<Credentials> credentials, - RequestAuthorizeSignature authSigner, @TimeStamp Provider<String> timeStampProvider, + RequestAuthorizeSignatureV2 authSigner, @TimeStamp Provider<String> timeStampProvider, DateService dateService) throws SecurityException, NoSuchMethodException { super(processor, blobToObject, blob2HttpGetOptions, interfaceClass); this.authSigner = authSigner; http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSignerV4.java ---------------------------------------------------------------------- diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSignerV4.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSignerV4.java new file mode 100644 index 0000000..f470999 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSignerV4.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.aws.s3.blobstore; + +import static org.jclouds.blobstore.util.BlobStoreUtils.cleanRequest; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.aws.s3.AWSS3Client; +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.functions.BlobToHttpGetOptions; +import org.jclouds.http.HttpRequest; +import org.jclouds.reflect.Invocation; +import org.jclouds.rest.internal.RestAnnotationProcessor; +import org.jclouds.s3.blobstore.S3BlobRequestSigner; +import org.jclouds.s3.blobstore.functions.BlobToObject; +import org.jclouds.s3.filters.RequestAuthorizeSignatureV4; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; + +public class AWSS3BlobRequestSignerV4 extends S3BlobRequestSigner<AWSS3Client> { + + private final RequestAuthorizeSignatureV4 authSigner; + + @Inject + public AWSS3BlobRequestSignerV4(RestAnnotationProcessor processor, BlobToObject blobToObject, + BlobToHttpGetOptions blob2HttpGetOptions, Class<AWSS3Client> interfaceClass, + RequestAuthorizeSignatureV4 authSigner) throws SecurityException, NoSuchMethodException { + super(processor, blobToObject, blob2HttpGetOptions, interfaceClass); + this.authSigner = authSigner; + } + + @Override + public HttpRequest signGetBlob(String container, String name, long timeInSeconds) { + checkNotNull(container, "container"); + checkNotNull(name, "name"); + HttpRequest request = processor.apply(Invocation.create(getMethod, ImmutableList.<Object>of(container, name))); + request = authSigner.signForTemporaryAccess(request, timeInSeconds); + return cleanRequest(request); + } + + @Override + public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) { + checkNotNull(container, "container"); + checkNotNull(blob, "blob"); + HttpRequest request = processor.apply(Invocation.create(createMethod, + ImmutableList.<Object>of(container, blobToObject.apply(blob)))); + request = authSigner.signForTemporaryAccess(request, timeInSeconds); + return cleanRequest(request); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignature.java ---------------------------------------------------------------------- diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignature.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignature.java index 86c1bb9..f494ba1 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignature.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignature.java @@ -34,13 +34,13 @@ import org.jclouds.domain.Credentials; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpUtils; import org.jclouds.http.internal.SignatureWire; -import org.jclouds.s3.filters.RequestAuthorizeSignature; +import org.jclouds.s3.filters.RequestAuthorizeSignatureV2; import com.google.common.base.Supplier; /** Signs the AWS S3 request, supporting temporary signatures. */ @Singleton -public class AWSRequestAuthorizeSignature extends RequestAuthorizeSignature { +public class AWSRequestAuthorizeSignature extends RequestAuthorizeSignatureV2 { @Inject public AWSRequestAuthorizeSignature(SignatureWire signatureWire, @Named(PROPERTY_AUTH_TAG) String authTag, http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignatureV4.java ---------------------------------------------------------------------- diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignatureV4.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignatureV4.java new file mode 100644 index 0000000..6cb3bf1 --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/filters/AWSRequestAuthorizeSignatureV4.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.aws.s3.filters; + +import static org.jclouds.http.utils.Queries.queryParser; +import static org.jclouds.s3.filters.AwsSignatureV4Constants.AMZ_SIGNATURE_PARAM; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.HttpRequest; +import org.jclouds.s3.filters.Aws4SignerForAuthorizationHeader; +import org.jclouds.s3.filters.Aws4SignerForChunkedUpload; +import org.jclouds.s3.filters.Aws4SignerForQueryString; +import org.jclouds.s3.filters.RequestAuthorizeSignatureV4; + +/** + * Signs the AWS S3 request, supporting temporary signatures. + */ +@Singleton +public class AWSRequestAuthorizeSignatureV4 extends RequestAuthorizeSignatureV4 { + + @Inject + public AWSRequestAuthorizeSignatureV4(Aws4SignerForAuthorizationHeader signerForAuthorizationHeader, + Aws4SignerForChunkedUpload signerForChunkedUpload, + Aws4SignerForQueryString signerForQueryString) { + super(signerForAuthorizationHeader, signerForChunkedUpload, signerForQueryString); + } + + @Override + protected HttpRequest signForAuthorizationHeader(HttpRequest request) { + /* + * Only add the Authorization header if the query string doesn't already contain + * the 'X-Amz-Signature' parameter, otherwise S3 will fail the request complaining about + * duplicate authentication methods. The 'Signature' parameter will be added for signed URLs + * with expiration. + */ + + if (queryParser().apply(request.getEndpoint().getQuery()).containsKey(AMZ_SIGNATURE_PARAM)) { + return request; + } + return super.signForAuthorizationHeader(request); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/8bddbb49/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerV4ExpectTest.java ---------------------------------------------------------------------- diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerV4ExpectTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerV4ExpectTest.java new file mode 100644 index 0000000..cdfccf1 --- /dev/null +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerV4ExpectTest.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file 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. + */ +package org.jclouds.aws.s3.blobstore; + +import static org.jclouds.Constants.PROPERTY_CREDENTIAL; +import static org.jclouds.Constants.PROPERTY_IDENTITY; +import static org.testng.Assert.assertEquals; + +import java.util.Date; +import java.util.Properties; + +import javax.inject.Named; + +import org.jclouds.Constants; +import org.jclouds.aws.s3.AWSS3ApiMetadata; +import org.jclouds.aws.s3.AWSS3ProviderMetadata; +import org.jclouds.aws.s3.blobstore.config.AWSS3BlobStoreContextModule; +import org.jclouds.aws.s3.config.AWSS3HttpApiModule; +import org.jclouds.aws.s3.filters.AWSRequestAuthorizeSignatureV4; +import org.jclouds.blobstore.BlobRequestSigner; +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.date.DateService; +import org.jclouds.date.TimeStamp; +import org.jclouds.http.HttpRequest; +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.rest.ConfiguresHttpApi; +import org.jclouds.s3.blobstore.S3BlobSignerExpectTest; +import org.jclouds.s3.filters.RequestAuthorizeSignature; +import org.testng.SkipException; +import org.testng.annotations.Test; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableSet; +import com.google.common.net.HttpHeaders; +import com.google.inject.Module; +import com.google.inject.Scopes; + +@Test(groups = "unit", testName = "AWSS3BlobSignerV4ExpectTest") +public class AWSS3BlobSignerV4ExpectTest extends S3BlobSignerExpectTest { + private static final String IDENTITY = "AKIAPAEBI3QI4EXAMPLE"; + private static final String CREDENTIAL = "oHkkcPcOjJnoAXpjT8GXdNeBjo6Ru7QeFExAmPlE"; + private static final String TIMESTAMP = "Thu, 03 Feb 2015 07:11:11 GMT"; + + private static final String BUCKET_NAME = "test-bucket"; + private static final String OBJECT_NAME = "ExampleObject.txt"; + private static final String HOST = BUCKET_NAME + ".s3.amazonaws.com"; + + public AWSS3BlobSignerV4ExpectTest() { + provider = null; + } + + @Override + protected HttpRequest getBlobWithTime() { + return HttpRequest.builder().method("GET") + .endpoint("https://" + HOST + "/" + OBJECT_NAME + + "?X-Amz-Algorithm=AWS4-HMAC-SHA256" + + "&X-Amz-Credential=AKIAPAEBI3QI4EXAMPLE/20150203/us-east-1/s3/aws4_request" + + "&X-Amz-Date=20150203T071111Z" + + "&X-Amz-Expires=86400" + + "&X-Amz-SignedHeaders=host" + + "&X-Amz-Signature=0bafb6a0d99c8b7c39abe5496e9897e8c442b09278f1a647267acb25e8d1c550") + .addHeader(HttpHeaders.HOST, HOST) + .build(); + } + + @Test + @Override + public void testSignGetBlobWithTime() { + BlobStore getBlobWithTime = requestsSendResponses(init()); + HttpRequest compare = getBlobWithTime(); + HttpRequest signedRequest = getBlobWithTime.getContext().getSigner().signGetBlob(BUCKET_NAME, OBJECT_NAME, + 86400l /* seconds */); + assertEquals(signedRequest, compare); + } + + protected HttpRequest _putBlobWithTime() { + return HttpRequest.builder().method("PUT") + .endpoint("https://" + HOST + "/" + OBJECT_NAME + + "?X-Amz-Algorithm=AWS4-HMAC-SHA256" + + "&X-Amz-Credential=AKIAPAEBI3QI4EXAMPLE/20150203/us-east-1/s3/aws4_request" + + "&X-Amz-Date=20150203T071111Z" + + "&X-Amz-Expires=86400" + + "&X-Amz-SignedHeaders=host" + + "&X-Amz-Signature=41484fb83e0c51b289907979ff96b2c743f6faf8dc70fca1c6fa78d8aeda132f") + .addHeader(HttpHeaders.EXPECT, "100-continue") + .addHeader(HttpHeaders.HOST, HOST) + .build(); + } + + @Test + @Override + public void testSignPutBlobWithTime() throws Exception { + BlobStore signPutBloblWithTime = requestsSendResponses(init()); + Blob blob = signPutBloblWithTime.blobBuilder(OBJECT_NAME).payload(text).contentType("text/plain").build(); + HttpRequest compare = _putBlobWithTime(); + compare.setPayload(blob.getPayload()); + HttpRequest signedRequest = signPutBloblWithTime.getContext().getSigner().signPutBlob(BUCKET_NAME, blob, + 86400l /* seconds */); + assertEquals(signedRequest, compare); + } + + @Override + protected HttpRequest putBlob() { + throw new SkipException("skip putBlob"); + } + + @Override + public void testSignPutBlob() { + throw new SkipException("skip testSignPutBlob"); + } + + @Override + public void testSignGetBlob() { + throw new SkipException("skip testSignGetBlob"); + } + + @Override + public void testSignGetBlobWithOptions() { + throw new SkipException("skip testSignGetBlobWithOptions"); + } + + @Override + public void testSignRemoveBlob() { + throw new SkipException("skip testSignRemoveBlob"); + } + + @Override + protected Module createModule() { + return new TestAWSS3SignerV4HttpApiModule(); + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + props.put(PROPERTY_IDENTITY, IDENTITY); + props.put(PROPERTY_CREDENTIAL, CREDENTIAL); + return props; + } + + @Override + protected ProviderMetadata createProviderMetadata() { + AWSS3ApiMetadata.Builder apiBuilder = new AWSS3ApiMetadata().toBuilder(); + apiBuilder.defaultModules(ImmutableSet.<Class<? extends Module>>of(TestAWSS3SignerV4HttpApiModule.class, + TestAWSS3BlobStoreContextModule.class)); + return new AWSS3ProviderMetadata().toBuilder().apiMetadata(apiBuilder.build()).build(); + } + + public static final class TestAWSS3BlobStoreContextModule extends AWSS3BlobStoreContextModule { + + @Override + protected void bindRequestSigner() { + // replace AWSS3BlobRequestSigner aws s3 with AWSS3BlobRequestSignerV4 + bind(BlobRequestSigner.class).to(AWSS3BlobRequestSignerV4.class); + } + + } + + @ConfiguresHttpApi + public static final class TestAWSS3SignerV4HttpApiModule extends AWSS3HttpApiModule { + @Override + protected void configure() { + super.configure(); + } + + @Override + protected void bindRequestSigner() { + bind(RequestAuthorizeSignature.class).to(AWSRequestAuthorizeSignatureV4.class).in(Scopes.SINGLETON); + } + + @Override + @TimeStamp + protected String provideTimeStamp(@TimeStamp Supplier<String> cache) { + return TIMESTAMP; + } + + @Override + @TimeStamp + protected Supplier<Date> provideTimeStampCacheDate( + @Named(Constants.PROPERTY_SESSION_INTERVAL) long seconds, + @TimeStamp final Supplier<String> timestamp, + final DateService dateService) { + return Suppliers.ofInstance(dateService.rfc822DateParse(TIMESTAMP)); + } + } +}
