Skip to content

Commit

Permalink
Allow anonymous access via bucket and object ACLs
Browse files Browse the repository at this point in the history
Fixes #44.
  • Loading branch information
gaul committed Jul 13, 2015
1 parent 757ed5a commit be3f38b
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 10 deletions.
79 changes: 79 additions & 0 deletions src/main/java/org/gaul/s3proxy/S3ProxyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.SortedSetMultimap;
Expand All @@ -72,9 +73,11 @@

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.KeyNotFoundException;
import org.jclouds.blobstore.LocalBlobRequestSigner;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobAccess;
import org.jclouds.blobstore.domain.BlobBuilder;
Expand All @@ -90,6 +93,7 @@
import org.jclouds.blobstore.options.ListContainerOptions;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.domain.Location;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.io.ContentMetadata;
Expand Down Expand Up @@ -158,6 +162,8 @@ final class S3ProxyHandler extends AbstractHandler {
private final XMLOutputFactory xmlOutputFactory =
XMLOutputFactory.newInstance();
private BlobStoreLocator blobStoreLocator;
// TODO: hack to allow per-request anonymous access
private final BlobStore defaultBlobStore;

S3ProxyHandler(final BlobStore blobStore, final String identity,
final String credential, Optional<String> virtualHost) {
Expand Down Expand Up @@ -186,6 +192,7 @@ public Map.Entry<String, BlobStore> locateBlobStore(
};
}
this.virtualHost = requireNonNull(virtualHost);
this.defaultBlobStore = blobStore;
xmlOutputFactory.setProperty("javax.xml.stream.isRepairingNamespaces",
Boolean.FALSE);
}
Expand Down Expand Up @@ -251,6 +258,15 @@ private void doHandle(HttpServletRequest request,
}
}

// anonymous request
if (method.equals("GET") &&
request.getHeader(HttpHeaders.AUTHORIZATION) == null &&
request.getParameter("AWSAccessKeyId") == null &&
defaultBlobStore != null) {
doHandleAnonymous(request, response, uri, defaultBlobStore);
return;
}

if (!anonymousIdentity && !hasDateHeader && !hasXAmzDateHeader &&
request.getParameter("Expires") == null) {
throw new S3Exception(S3ErrorCode.ACCESS_DENIED,
Expand Down Expand Up @@ -449,6 +465,69 @@ private void doHandle(HttpServletRequest request,
throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
}

private void doHandleAnonymous(HttpServletRequest request,
HttpServletResponse response, String uri, BlobStore blobStore)
throws IOException, S3Exception {
String method = request.getMethod();
String[] path = uri.split("/", 3);
switch (method) {
case "GET":
if (path.length <= 1) {
throw new S3Exception(S3ErrorCode.INVALID_REQUEST);
} else if (path.length == 2) {
handleContainerList(response, blobStore);
return;
}

BlobRequestSigner signer = blobStore
.getContext()
.getSigner();
if (signer instanceof LocalBlobRequestSigner) {
// Local blobstores do not have an HTTP server --
// must handle these calls directly.
Blob blob = blobStore.getBlob(path[1], path[2]);
if (blob == null) {
// TODO: NO_SUCH_BUCKET
throw new S3Exception(S3ErrorCode.NO_SUCH_KEY);
}
// TODO: getBlob should return BlobAccess
BlobAccess access = blobStore.getBlobAccess(path[1],
path[2]);
if (access != BlobAccess.PUBLIC_READ) {
throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
}
try (InputStream payload = blob.getPayload().openStream();
OutputStream os = response.getOutputStream()) {
ByteStreams.copy(payload, os);
}
} else {
HttpRequest anonymousRequest = signer
.signGetBlob(path[1], path[2])
.toBuilder()
.headers(ImmutableMultimap.<String, String>of())
// TODO: replace parameters?
.build();
logger.debug("issuing anonymous request: {}", anonymousRequest);
HttpResponse anonymousResponse = blobStore
.getContext()
.utils()
.http()
.invoke(anonymousRequest);
try (InputStream payload =
anonymousResponse.getPayload().openStream();
OutputStream os = response.getOutputStream()) {
ByteStreams.copy(payload, os);
}
}
return;
default:
break;
}
logger.error("Unknown method {} with URI {}",
method, request.getRequestURI());
throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
}

private void handleGetContainerAcl(HttpServletResponse response,
BlobStore blobStore, String containerName) throws IOException {
ContainerAccess access = blobStore.getContainerAccess(containerName);
Expand Down
26 changes: 16 additions & 10 deletions src/test/java/org/gaul/s3proxy/S3ProxyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobAccess;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.MultipartPart;
import org.jclouds.blobstore.domain.MultipartUpload;
Expand All @@ -54,7 +55,6 @@
import org.jclouds.io.ContentMetadataBuilder;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.io.payloads.ByteSourcePayload;
import org.jclouds.rest.HttpClient;
import org.jclouds.s3.S3Client;

Expand Down Expand Up @@ -110,17 +110,23 @@ public void tearDown() throws Exception {
}
}

// TODO: why does this hang for 30 seconds?
// TODO: this test requires anonymous access
@Ignore
@Test
public void testHttpClient() throws Exception {
HttpClient httpClient = context.utils().http();
// TODO: how to interpret this?
URI uri = URI.create(s3Endpoint + "/" + containerName + "/blob");
Payload payload = new ByteSourcePayload(BYTE_SOURCE);
payload.getContentMetadata().setContentLength(BYTE_SOURCE.size());
httpClient.put(uri, payload);
String blobName = "blob-name";
Blob blob = blobStore.blobBuilder(blobName)
.payload(BYTE_SOURCE)
.contentLength(BYTE_SOURCE.size())
.build();
blobStore.putBlob(containerName, blob);
// TODO: jclouds PutOptions should include BlobAccess
blobStore.setBlobAccess(containerName, blobName,
BlobAccess.PUBLIC_READ);

HttpClient httpClient = s3Context.utils().http();
URI uri = new URI(s3Endpoint.getScheme(), s3Endpoint.getUserInfo(),
s3Endpoint.getHost(), s3Proxy.getPort(),
"/" + containerName + "/" + blobName,
/*query=*/ null, /*fragment=*/ null);
try (InputStream actual = httpClient.get(uri);
InputStream expected = BYTE_SOURCE.openStream()) {
assertThat(actual).hasContentEqualTo(expected);
Expand Down

0 comments on commit be3f38b

Please sign in to comment.