diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/S3Connection.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/S3Connection.java index 5c4247f3ad..b8afc3fcaa 100644 --- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/S3Connection.java +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/S3Connection.java @@ -24,6 +24,7 @@ package org.jclouds.aws.s3; import org.jclouds.aws.s3.commands.options.*; +import org.jclouds.aws.s3.domain.AccessControlList; import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Object; @@ -211,4 +212,18 @@ public interface S3Connection { * @see org.jclouds.aws.s3.commands.ListOwnedBuckets */ Future> listOwnedBuckets(); + + /** + * @return access permissions of the bucket + * + * @see org.jclouds.aws.s3.commands.GetAccessControlList + */ + Future getBucketACL(String bucket); + + /** + * @return access permissions of the object + * + * @see org.jclouds.aws.s3.commands.GetAccessControlList + */ + Future getObjectACL(String bucket, String objectKey); } diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/GetAccessControlList.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/GetAccessControlList.java new file mode 100644 index 0000000000..d127d5dd2d --- /dev/null +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/GetAccessControlList.java @@ -0,0 +1,99 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.commands; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.jclouds.aws.AWSResponseException; +import org.jclouds.aws.s3.domain.AccessControlList; +import org.jclouds.http.HttpResponseException; +import org.jclouds.http.commands.callables.xml.ParseSax; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.name.Named; + +/** + * A GET request operation directed at an object or bucket URI with the "acl" parameter retrieves + * the Access Control List (ACL) settings for that S3 item. + *

+ * To list a bucket or object's ACL, you must have READ_ACP access to the item. + * + * @see + * @author James Murty + */ +public class GetAccessControlList extends S3FutureCommand { + + @Inject + public GetAccessControlList(@Named("jclouds.http.address") String amazonHost, + ParseSax accessControlListParser, + @Assisted("bucketName") String bucket) + { + super("GET", "/?acl", accessControlListParser, amazonHost, bucket); + } + + @Inject + public GetAccessControlList(@Named("jclouds.http.address") String amazonHost, + ParseSax accessControlListParser, + @Assisted("bucketName") String bucket, + @Assisted("objectKey") String objectKey) + { + super("GET", "/" + objectKey + "?acl", accessControlListParser, amazonHost, bucket); + } + + @Override + public AccessControlList get() throws InterruptedException, ExecutionException { + try { + return super.get(); + } catch (ExecutionException e) { + return attemptNotFound(e); + } + } + + @VisibleForTesting + AccessControlList attemptNotFound(ExecutionException e) throws ExecutionException { + if (e.getCause() != null && e.getCause() instanceof HttpResponseException) { + AWSResponseException responseException = (AWSResponseException) e.getCause(); + if ("NoSuchBucket".equals(responseException.getError().getCode()) + || "NoSuchObject".equals(responseException.getError().getCode())) + { + return AccessControlList.NOT_FOUND; + } + } + throw e; + } + + @Override + public AccessControlList get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException, + TimeoutException { + try { + return super.get(l, timeUnit); + } catch (ExecutionException e) { + return attemptNotFound(e); + } + } +} \ No newline at end of file diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/S3CommandFactory.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/S3CommandFactory.java index b6ff1170b1..941fb0e421 100644 --- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/S3CommandFactory.java +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/commands/S3CommandFactory.java @@ -140,4 +140,14 @@ public class S3CommandFactory { sourceObject, destinationBucket, destinationObject, options); } + public GetAccessControlList createGetBucketACL(String bucket) { + return new GetAccessControlList( + amazonHost, parserFactory.createAccessControlListParser(), bucket); + } + + public GetAccessControlList createGetObjectACL(String bucket, String objectKey) { + return new GetAccessControlList( + amazonHost, parserFactory.createAccessControlListParser(), bucket, objectKey); + } + } \ No newline at end of file diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/domain/AccessControlList.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/domain/AccessControlList.java new file mode 100644 index 0000000000..ef146dce48 --- /dev/null +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/domain/AccessControlList.java @@ -0,0 +1,390 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.domain; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; + +/** + * An Access Control List (ACL) describes the access control settings for a bucket or object in S3. + * + * ACL settings comprise a set of {@link Grant}s, each of which specifies a {@link Permission} + * that has been granted to a specific {@link Grantee}. If an entity tries to access or modify an + * item in S3, the operation will be denied unless the item has ACL settings that explicitly + * permit that entity to perform that action. + * + * + * @author James Murty + * @see + */ +public class AccessControlList { + public static final AccessControlList NOT_FOUND = new AccessControlList(); + + private CanonicalUser owner; + private final SortedSet grants = new TreeSet(); + + public AccessControlList() { + } + + public void setOwner(CanonicalUser owner) { + this.owner = owner; + } + + public CanonicalUser getOwner() { + return owner; + } + + /** + * @return an unmodifiable set of grants represented by this ACL. + */ + public Set getGrants() { + return Collections.unmodifiableSet(grants); + } + + /** + * @return an unmodifiable set of grantees who have been assigned permissions in this ACL. + */ + public Set getGrantees() { + SortedSet grantees = new TreeSet(); + for (Grant grant : getGrants()) { + grantees.add(grant.getGrantee()); + } + return Collections.unmodifiableSet(grantees); + } + + /** + * Add a permission for the given grantee. + * + * @param grantee + * @param permission + * @return + */ + public AccessControlList addPermission(Grantee grantee, Permission permission) { + Grant grant = new Grant(grantee, permission); + grants.add(grant); + return this; + } + + /** + * Add a permission for the given group grantee. + * + * @param groupGranteeURI + * @param permission + * @return + */ + public AccessControlList addPermission(GroupGranteeURI groupGranteeURI, Permission permission) { + return addPermission(new GroupGrantee(groupGranteeURI), permission); + } + + /** + * Revoke a permission for the given grantee, if this specific permission was granted. + * + * Note that you must be very explicit about the permissions you revoke, you cannot revoke + * partial permissions and expect this class to determine the implied remaining permissions. + * For example, if you revoke the {@link Permission#READ} permission from a grantee with + * {@link Permission#FULL_CONTROL} access, the revocation will do nothing + * and the grantee will retain full access. To change the access settings for this grantee, + * you must first remove the {@link Permission#FULL_CONTROL} permission the add back the + * {@link Permission#READ} permission. + * + * @param grantee + * @param permission + * @return + */ + public AccessControlList revokePermission(Grantee grantee, Permission permission) { + Collection grantsForGrantee = findGrantsForGrantee(grantee.getIdentifier()); + for (Grant grant : grantsForGrantee) { + if (grant.getPermission().equals(permission)) { + grants.remove(grant); + } + } + return this; + } + + /** + * Revoke a permission for the given group grantee, if this specific permission was granted. + * + * Note that you must be very explicit about the permissions you revoke, you cannot revoke + * partial permissions and expect this class to determine the implied remaining permissions. + * For example, if you revoke the {@link Permission#READ} permission from a grantee with + * {@link Permission#FULL_CONTROL} access, the revocation will do nothing + * and the grantee will retain full access. To change the access settings for this grantee, + * you must first remove the {@link Permission#FULL_CONTROL} permission the add back the + * {@link Permission#READ} permission. + * + * @param groupGranteeURI + * @param permission + * @return + */ + public AccessControlList revokePermission(GroupGranteeURI groupGranteeURI, Permission permission) + { + return revokePermission(new GroupGrantee(groupGranteeURI), permission); + } + + /** + * Revoke all the permissions granted to the given grantee. + * + * @param grantee + * @return + */ + public AccessControlList revokeAllPermissions(Grantee grantee) { + Collection grantsForGrantee = findGrantsForGrantee(grantee.getIdentifier()); + grants.removeAll(grantsForGrantee); + return this; + } + + /** + * @param granteeId + * @return + * the permissions assigned to a grantee, as identified by the given ID. + */ + public Collection getPermissions(String granteeId) { + Collection grantsForGrantee = findGrantsForGrantee(granteeId); + return Collections2.transform(grantsForGrantee, new Function() { + public Permission apply(Grant g) { + return g.getPermission(); + } + }); + } + + /** + * @param grantee + * @return + * the permissions assigned to a grantee. + */ + public Collection getPermissions(Grantee grantee) { + return getPermissions(grantee.getIdentifier()); + } + + /** + * @param granteeURI + * @return + * the permissions assigned to a group grantee. + */ + public Collection getPermissions(GroupGranteeURI granteeURI) { + return getPermissions(granteeURI.getIdentifier()); + } + + /** + * @param granteeId + * @param permission + * @return + * true if the grantee has the given permission. + */ + public boolean hasPermission(String granteeId, Permission permission) { + return getPermissions(granteeId).contains(permission); + } + + /** + * @param grantee + * @param permission + * @return + * true if the grantee has the given permission. + */ + public boolean hasPermission(Grantee grantee, Permission permission) { + return hasPermission(grantee.getIdentifier(), permission); + } + + + /** + * @param granteeURI + * @param permission + * @return + * true if the grantee has the given permission. + */ + public boolean hasPermission(GroupGranteeURI granteeURI, Permission permission) { + return getPermissions(granteeURI.getIdentifier()).contains(permission); + } + + /** + * Find all the grants for a given grantee, identified by an ID which allows all Grantee + * types to be searched. + * + * @param granteeId + * identifier of a canonical user, email address user, or group. + * @return + */ + protected Collection findGrantsForGrantee(final String granteeId) { + return Collections2.filter(grants, new Predicate() { + public boolean apply(Grant g) { + return g.getGrantee().getIdentifier().equals(granteeId); + } + }); + } + + /** + * Converts a canned access control policy into the equivalent access control list. + * + * @param cannedAP + * @param ownerId + * @return + */ + public static AccessControlList fromCannedAccessPolicy( + CannedAccessPolicy cannedAP, String ownerId) + { + AccessControlList acl = new AccessControlList(); + acl.setOwner(new CanonicalUser(ownerId)); + + // Canned access policies always allow full control to the owner. + acl.addPermission(new CanonicalUserGrantee(ownerId), Permission.FULL_CONTROL); + + if (CannedAccessPolicy.PRIVATE == cannedAP) { + // No more work to do. + } else if (CannedAccessPolicy.AUTHENTICATED_READ == cannedAP) { + acl.addPermission(GroupGranteeURI.AUTHENTICATED_USERS, Permission.READ); + } else if (CannedAccessPolicy.PUBLIC_READ == cannedAP) { + acl.addPermission(GroupGranteeURI.ALL_USERS, Permission.READ); + } else if (CannedAccessPolicy.PUBLIC_READ_WRITE == cannedAP) { + acl.addPermission(GroupGranteeURI.ALL_USERS, Permission.READ); + acl.addPermission(GroupGranteeURI.ALL_USERS, Permission.WRITE); + } + return acl; + } + + /////////////////////////////////////////////////////////////////////////////// + // Class and Enum declarations to represent Grants, Grantees and Permissions // + /////////////////////////////////////////////////////////////////////////////// + + public static enum Permission { + READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL; + }; + + public static class Grant implements Comparable { + private final Grantee grantee; + private final Permission permission; + + public Grant(Grantee grantee, Permission permission) { + this.grantee = grantee; + this.permission = permission; + } + + public Grantee getGrantee() { + return grantee; + } + + public Permission getPermission() { + return permission; + } + + public int compareTo(org.jclouds.aws.s3.domain.AccessControlList.Grant o) { + if (this == o) { + return 0; + } else { + String myGranteeAndPermission = grantee.getIdentifier() + "\n" + permission; + String otherGranteeAndPermission = o.grantee.getIdentifier() + "\n" + o.permission; + return myGranteeAndPermission.compareTo(otherGranteeAndPermission); + } + } + } + + public abstract static class Grantee implements Comparable { + private final String identifier; + + protected Grantee(String identifier) { + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } + + public int compareTo(org.jclouds.aws.s3.domain.AccessControlList.Grantee o) { + return (this == o) ? 0 : getIdentifier().compareTo(o.getIdentifier()); + } + } + + public static class EmailAddressGrantee extends Grantee { + public EmailAddressGrantee(String emailAddress) { + super(emailAddress); + } + + public String getEmailAddress() { + return getIdentifier(); + } + } + + public static class CanonicalUserGrantee extends Grantee { + private final String displayName; + + public CanonicalUserGrantee(String id, String displayName) { + super(id); + this.displayName = displayName; + } + + public CanonicalUserGrantee(String id) { + this(id, null); + } + + public String getDisplayName() { + return displayName; + } + } + + public enum GroupGranteeURI { + ALL_USERS ("http://acs.amazonaws.com/groups/global/AllUsers"), + AUTHENTICATED_USERS ("http://acs.amazonaws.com/groups/global/AuthenticatedUsers"), + LOG_DELIVERY ("http://acs.amazonaws.com/groups/s3/LogDelivery"); + + private final String uri; + + GroupGranteeURI(String uri) { + this.uri = uri; + } + + public String getIdentifier() { + return this.uri; + } + + public static GroupGranteeURI fromURI(String uri) { + if (ALL_USERS.uri.equals(uri)) { + return ALL_USERS; + } else if (AUTHENTICATED_USERS.uri.equals(uri)) { + return AUTHENTICATED_USERS; + } else if (LOG_DELIVERY.uri.equals(uri)) { + return LOG_DELIVERY; + } else { + throw new IllegalArgumentException("No GroupGranteeURI constant matches " + uri); + } + } + } + + + public static class GroupGrantee extends Grantee { + + public GroupGrantee(GroupGranteeURI groupURI) { + super(groupURI.getIdentifier()); + } + } + + +} diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/filters/RequestAuthorizeSignature.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/filters/RequestAuthorizeSignature.java index e06de16c66..c81cf513cd 100644 --- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/filters/RequestAuthorizeSignature.java +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/filters/RequestAuthorizeSignature.java @@ -164,11 +164,34 @@ public class RequestAuthorizeSignature implements HttpRequestFilter { } private static void appendUriPath(HttpRequest request, StringBuilder toSign) { + // Remove parameters from URI, because most must not be included in the signed URI... + String paramsString = null; int queryIndex = request.getUri().indexOf('?'); - if (queryIndex >= 0) + if (queryIndex >= 0) { toSign.append(request.getUri().substring(0, queryIndex)); - else + paramsString = request.getUri().substring(queryIndex + 1); + } else { toSign.append(request.getUri()); + } + + // ...however, there are a few exceptions that must be included in the signed URI. + if (paramsString != null) { + StringBuilder paramsToSign = new StringBuilder("?"); + + String[] params = paramsString.split("&"); + for (String param : params) { + String[] paramNameAndValue = param.split("="); + + if ("acl".equals(paramNameAndValue[0])) { + paramsToSign.append("acl"); + } + // TODO: Other special cases not yet handled: torrent, logging, location, requestPayment + } + + if (paramsToSign.length() > 1) { + toSign.append(paramsToSign); + } + } } private static String valueOrEmpty(Collection collection) { diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/internal/LiveS3Connection.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/internal/LiveS3Connection.java index e512fb5727..a1a21a3b67 100644 --- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/internal/LiveS3Connection.java +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/internal/LiveS3Connection.java @@ -31,6 +31,7 @@ import org.jclouds.aws.s3.commands.BucketExists; import org.jclouds.aws.s3.commands.CopyObject; import org.jclouds.aws.s3.commands.DeleteBucket; import org.jclouds.aws.s3.commands.DeleteObject; +import org.jclouds.aws.s3.commands.GetAccessControlList; import org.jclouds.aws.s3.commands.ListOwnedBuckets; import org.jclouds.aws.s3.commands.GetObject; import org.jclouds.aws.s3.commands.HeadObject; @@ -43,6 +44,7 @@ import org.jclouds.aws.s3.commands.options.GetObjectOptions; import org.jclouds.aws.s3.commands.options.ListBucketOptions; import org.jclouds.aws.s3.commands.options.PutBucketOptions; import org.jclouds.aws.s3.commands.options.PutObjectOptions; +import org.jclouds.aws.s3.domain.AccessControlList; import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Bucket.Metadata; @@ -229,4 +231,26 @@ public class LiveS3Connection implements S3Connection { return listRequest; } + /** + * {@inheritDoc} + * + * @see GetAccessControlList + */ + public Future getBucketACL(String bucket) { + GetAccessControlList getBucketACLRequest = factory.createGetBucketACL(bucket); + client.submit(getBucketACLRequest); + return getBucketACLRequest; + } + + /** + * {@inheritDoc} + * + * @see GetAccessControlList + */ + public Future getObjectACL(String bucket, String objectKey) { + GetAccessControlList getObjectACLRequest = factory.createGetObjectACL(bucket, objectKey); + client.submit(getObjectACLRequest); + return getObjectACLRequest; + } + } diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/AccessControlListHandler.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/AccessControlListHandler.java new file mode 100644 index 0000000000..30961d0298 --- /dev/null +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/AccessControlListHandler.java @@ -0,0 +1,98 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.xml; + +import org.jclouds.aws.s3.domain.AccessControlList; +import org.jclouds.aws.s3.domain.CanonicalUser; +import org.jclouds.aws.s3.domain.AccessControlList.CanonicalUserGrantee; +import org.jclouds.aws.s3.domain.AccessControlList.EmailAddressGrantee; +import org.jclouds.aws.s3.domain.AccessControlList.Grantee; +import org.jclouds.aws.s3.domain.AccessControlList.GroupGrantee; +import org.jclouds.aws.s3.domain.AccessControlList.GroupGranteeURI; +import org.jclouds.aws.s3.domain.AccessControlList.Permission; +import org.jclouds.http.commands.callables.xml.ParseSax; +import org.xml.sax.Attributes; + +/** + * Parses the following XML document: + *

+ * AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/" + * + * @author James Murty + * @see + */ +public class AccessControlListHandler extends ParseSax.HandlerWithResult { + private AccessControlList acl = new AccessControlList(); + private StringBuilder currentText = new StringBuilder(); + + public AccessControlListHandler() { + } + + public AccessControlList getResult() { + return acl; + } + + private String currentId; + private String currentDisplayName; + private String currentGranteeType; + private String currentPermission; + private Grantee currentGrantee; + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if (qName.equals("Grantee")) { + currentGranteeType = attrs.getValue("xsi:type"); + } + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("Owner")) { + CanonicalUser owner = new CanonicalUser(currentId); + owner.setDisplayName(currentDisplayName); + acl.setOwner(owner); + } else if (qName.equals("Grantee")) { + if ("AmazonCustomerByEmail".equals(currentGranteeType)) { + currentGrantee = new EmailAddressGrantee(currentId); + } else if ("CanonicalUser".equals(currentGranteeType)) { + currentGrantee = new CanonicalUserGrantee(currentId, currentDisplayName); + } else if ("Group".equals(currentGranteeType)) { + currentGrantee = new GroupGrantee(GroupGranteeURI.fromURI(currentId)); + } + } else if (qName.equals("Grant")) { + acl.addPermission(currentGrantee, Permission.valueOf(currentPermission)); + } + + else if (qName.equals("ID") || qName.equals("EmailAddress") || qName.equals("URI")) { + currentId = currentText.toString(); + } else if (qName.equals("DisplayName")) { + currentDisplayName = currentText.toString(); + } else if (qName.equals("Permission")) { + currentPermission = currentText.toString(); + } + currentText = new StringBuilder(); + } + + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } +} diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/S3ParserFactory.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/S3ParserFactory.java index 47e75201f0..771b2ee587 100644 --- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/S3ParserFactory.java +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/S3ParserFactory.java @@ -26,6 +26,7 @@ package org.jclouds.aws.s3.xml; import java.util.List; import org.jclouds.aws.domain.AWSError; +import org.jclouds.aws.s3.domain.AccessControlList; import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.xml.ErrorHandler; @@ -101,4 +102,17 @@ public class S3ParserFactory { return parseErrorFactory.create(errorHandlerProvider.get()); } + @Inject + private GenericParseFactory parseAccessControlListFactory; + + @Inject + Provider accessControlListHandlerProvider; + + /** + * @return a parser used to handle {@link org.jclouds.aws.s3.commands.GetAccessControlList} responses + */ + public ParseSax createAccessControlListParser() { + return parseAccessControlListFactory.create(accessControlListHandlerProvider.get()); + } + } \ No newline at end of file diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/config/S3ParserModule.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/config/S3ParserModule.java index 2b6c6cdbe3..e2734cff23 100644 --- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/config/S3ParserModule.java +++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/xml/config/S3ParserModule.java @@ -26,8 +26,10 @@ package org.jclouds.aws.s3.xml.config; import java.util.List; import org.jclouds.aws.domain.AWSError; +import org.jclouds.aws.s3.domain.AccessControlList; import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Object; +import org.jclouds.aws.s3.xml.AccessControlListHandler; import org.jclouds.aws.s3.xml.CopyObjectHandler; import org.jclouds.aws.s3.xml.ListAllMyBucketsHandler; import org.jclouds.aws.s3.xml.ListBucketHandler; @@ -46,13 +48,25 @@ import com.google.inject.assistedinject.FactoryProvider; * @author Adrian Cole */ public class S3ParserModule extends AbstractModule { - private final TypeLiteral>> listBucketsTypeLiteral = new TypeLiteral>>() { + private final TypeLiteral>> listBucketsTypeLiteral = + new TypeLiteral>>() { }; - private final TypeLiteral> bucketTypeLiteral = new TypeLiteral>() { + private final TypeLiteral> bucketTypeLiteral = + new TypeLiteral>() { }; - private final TypeLiteral> objectMetadataTypeLiteral = new TypeLiteral>() { + private final TypeLiteral> objectMetadataTypeLiteral = + new TypeLiteral>() { }; - private final TypeLiteral> errorTypeLiteral = new TypeLiteral>() { + private final TypeLiteral> errorTypeLiteral = + new TypeLiteral>() { + }; + private final TypeLiteral> accessControlListTypeLiteral = + new TypeLiteral>() { }; @Override @@ -71,6 +85,8 @@ public class S3ParserModule extends AbstractModule { }).to(CopyObjectHandler.class); bind(new TypeLiteral>() { }).to(ErrorHandler.class); + bind(new TypeLiteral>() { + }).to(AccessControlListHandler.class); } private void bindCallablesThatReturnParseResults() { @@ -88,6 +104,9 @@ public class S3ParserModule extends AbstractModule { bind(errorTypeLiteral).toProvider( FactoryProvider.newFactory(errorTypeLiteral, new TypeLiteral>() { })); + bind(accessControlListTypeLiteral).toProvider( + FactoryProvider.newFactory(accessControlListTypeLiteral, + new TypeLiteral>() {})); } } \ No newline at end of file diff --git a/aws/s3/core/src/test/java/org/jclouds/aws/s3/StubS3Connection.java b/aws/s3/core/src/test/java/org/jclouds/aws/s3/StubS3Connection.java index d65a30d94a..a7565b9f2d 100644 --- a/aws/s3/core/src/test/java/org/jclouds/aws/s3/StubS3Connection.java +++ b/aws/s3/core/src/test/java/org/jclouds/aws/s3/StubS3Connection.java @@ -53,6 +53,7 @@ import org.jclouds.aws.s3.commands.options.GetObjectOptions; import org.jclouds.aws.s3.commands.options.ListBucketOptions; import org.jclouds.aws.s3.commands.options.PutBucketOptions; import org.jclouds.aws.s3.commands.options.PutObjectOptions; +import org.jclouds.aws.s3.domain.AccessControlList; import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Bucket.Metadata; @@ -81,6 +82,8 @@ public class StubS3Connection implements S3Connection { private static Map bucketToLocation = new ConcurrentHashMap(); private static Map keyToAcl = new ConcurrentHashMap(); + public static final String DEFAULT_OWNER_ID = "abc123"; + /** * @throws java.io.IOException */ @@ -439,7 +442,7 @@ public class StubS3Connection implements S3Connection { newMd.setMd5(md5); newMd.setContentType(object.getMetadata().getContentType()); if (options.getAcl() != null) - keyToAcl.put(bucketName + object, options.getAcl()); + keyToAcl.put(bucketName + "/" + object.getKey(), options.getAcl()); bucketToContents.get(bucketName).put(object.getKey(), new S3Object(newMd, data)); // Set HTTP headers to match metadata @@ -532,4 +535,32 @@ public class StubS3Connection implements S3Connection { }; } + public Future getBucketACL(final String bucket) { + return new FutureBase() { + public AccessControlList get() throws InterruptedException, ExecutionException { + CannedAccessPolicy cannedAP = keyToAcl.get(bucket); + if (cannedAP == null) { + // Default to private access policy + cannedAP = CannedAccessPolicy.PRIVATE; + } + + return AccessControlList.fromCannedAccessPolicy(cannedAP, DEFAULT_OWNER_ID); + } + }; + } + + public Future getObjectACL(final String bucket, final String objectKey) { + return new FutureBase() { + public AccessControlList get() throws InterruptedException, ExecutionException { + CannedAccessPolicy cannedAP = keyToAcl.get(bucket + "/" + objectKey); + if (cannedAP == null) { + // Default to private access policy + cannedAP = CannedAccessPolicy.PRIVATE; + } + + return AccessControlList.fromCannedAccessPolicy(cannedAP, DEFAULT_OWNER_ID); + } + }; + } + } diff --git a/aws/s3/core/src/test/java/org/jclouds/aws/s3/commands/GetAccessControlListIntegrationTest.java b/aws/s3/core/src/test/java/org/jclouds/aws/s3/commands/GetAccessControlListIntegrationTest.java new file mode 100644 index 0000000000..d0d89f2b5c --- /dev/null +++ b/aws/s3/core/src/test/java/org/jclouds/aws/s3/commands/GetAccessControlListIntegrationTest.java @@ -0,0 +1,124 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.commands; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.jclouds.aws.s3.S3IntegrationTest; +import org.jclouds.aws.s3.commands.options.PutObjectOptions; +import org.jclouds.aws.s3.domain.AccessControlList; +import org.jclouds.aws.s3.domain.S3Object; +import org.jclouds.aws.s3.domain.AccessControlList.GroupGranteeURI; +import org.jclouds.aws.s3.domain.AccessControlList.Permission; +import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + +/** + * Tests integrated functionality of all commands that retrieve Access Control Lists (ACLs). + * + * @author James Murty + */ +@Test(groups = {"integration", "live"}, testName = "s3.GetAccessControlListIntegrationTest") +public class GetAccessControlListIntegrationTest extends S3IntegrationTest { + + @Test + void testPrivateBucketACL() throws InterruptedException, ExecutionException, + TimeoutException, IOException + { + bucketName = bucketPrefix + ".testPrivateBucketACL".toLowerCase(); + + createBucketAndEnsureEmpty(bucketName); + + AccessControlList acl = client.getBucketACL(bucketName).get(10, TimeUnit.SECONDS); + + assertEquals(acl.getGrants().size(), 1); + assertTrue(acl.getOwner() != null); + String ownerId = acl.getOwner().getId(); + assertTrue(acl.hasPermission(ownerId, Permission.FULL_CONTROL)); + } + + @Test + void testObjectACL() throws InterruptedException, ExecutionException, + TimeoutException, IOException + { + bucketName = bucketPrefix + ".testObjectACL".toLowerCase(); + createBucketAndEnsureEmpty(bucketName); + + String privateObjectKey = "pr“vate-acl"; + String publicReadObjectKey = "pŸblic-read-acl"; + String publicReadWriteObjectKey = "pŸblic-read-write-acl"; + + // Private object + addObjectToBucket(bucketName, privateObjectKey); + AccessControlList acl = client.getObjectACL(bucketName, privateObjectKey) + .get(10, TimeUnit.SECONDS); + + assertEquals(acl.getGrants().size(), 1); + assertTrue(acl.getOwner() != null); + String ownerId = acl.getOwner().getId(); + assertTrue(acl.hasPermission(ownerId, Permission.FULL_CONTROL)); + + // Public Read object + client.putObject(bucketName, new S3Object(publicReadObjectKey, ""), + new PutObjectOptions().withAcl(CannedAccessPolicy.PUBLIC_READ)); + + acl = client.getObjectACL(bucketName, publicReadObjectKey) + .get(10, TimeUnit.SECONDS); + + assertEquals(acl.getGrants().size(), 2); + assertEquals(acl.getPermissions(GroupGranteeURI.ALL_USERS).size(), 1); + assertTrue(acl.getOwner() != null); + ownerId = acl.getOwner().getId(); + assertTrue(acl.hasPermission(ownerId, Permission.FULL_CONTROL)); + assertTrue(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.READ)); + + // Public Read-Write object + client.putObject(bucketName, new S3Object(publicReadWriteObjectKey, ""), + new PutObjectOptions().withAcl(CannedAccessPolicy.PUBLIC_READ_WRITE)); + + acl = client.getObjectACL(bucketName, publicReadWriteObjectKey) + .get(10, TimeUnit.SECONDS); + + assertEquals(acl.getGrants().size(), 3); + assertEquals(acl.getPermissions(GroupGranteeURI.ALL_USERS).size(), 2); + assertTrue(acl.getOwner() != null); + ownerId = acl.getOwner().getId(); + assertTrue(acl.hasPermission(ownerId, Permission.FULL_CONTROL)); + assertTrue(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.READ)); + assertTrue(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.WRITE)); + assertFalse(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.READ_ACP)); + assertFalse(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.WRITE_ACP)); + assertFalse(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.FULL_CONTROL)); + + emptyBucket(bucketName); + } + +} \ No newline at end of file diff --git a/aws/s3/core/src/test/java/org/jclouds/aws/s3/xml/AccessControlListHandlerTest.java b/aws/s3/core/src/test/java/org/jclouds/aws/s3/xml/AccessControlListHandlerTest.java new file mode 100644 index 0000000000..a35c52c1be --- /dev/null +++ b/aws/s3/core/src/test/java/org/jclouds/aws/s3/xml/AccessControlListHandlerTest.java @@ -0,0 +1,84 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.xml; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import org.apache.commons.io.IOUtils; +import org.jclouds.aws.s3.domain.AccessControlList; +import org.jclouds.aws.s3.domain.AccessControlList.GroupGranteeURI; +import org.jclouds.aws.s3.domain.AccessControlList.Permission; +import org.jclouds.http.HttpException; +import org.jclouds.http.commands.callables.xml.ParseSax; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@Test(groups = "unit", testName = "s3.AccessControlListHandlerTest") +public class AccessControlListHandlerTest extends BaseHandlerTest { + public static final String aclOwnerOnly = "1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677cjamesmurty1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677cjamesmurtyFULL_CONTROL"; + public static final String aclExtreme = "1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677cjamesmurtyhttp://acs.amazonaws.com/groups/global/AuthenticatedUsersWRITEhttp://acs.amazonaws.com/groups/global/AuthenticatedUsersREAD_ACP1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677cjamesmurtyWRITEhttp://acs.amazonaws.com/groups/global/AuthenticatedUsersWRITE_ACPhttp://acs.amazonaws.com/groups/global/AllUsersREADhttp://acs.amazonaws.com/groups/global/AuthenticatedUsersREADhttp://acs.amazonaws.com/groups/s3/LogDeliveryWRITE1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677cjamesmurtyREAD1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677cjamesmurtyFULL_CONTROL"; + + @BeforeMethod + ParseSax createParser() { + ParseSax parser = parserFactory.createAccessControlListParser(); + return parser; + } + + @Test + public void testAccessControlListOwnerOnly() throws HttpException { + String ownerId = "1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677c"; + AccessControlList acl = createParser().parse( + IOUtils.toInputStream(aclOwnerOnly)); + assertEquals(acl.getOwner().getId(), ownerId); + assertEquals(acl.getOwner().getDisplayName(), "jamesmurty"); + assertEquals(acl.getPermissions(ownerId).size(), 1); + assertTrue(acl.hasPermission(ownerId, Permission.FULL_CONTROL)); + assertEquals(acl.getGrants().size(), 1); + assertEquals(acl.getPermissions(GroupGranteeURI.ALL_USERS).size(), 0); + assertEquals(acl.getPermissions(GroupGranteeURI.AUTHENTICATED_USERS).size(), 0); + assertEquals(acl.getPermissions(GroupGranteeURI.LOG_DELIVERY).size(), 0); + } + + @Test + public void testAccessControlListExtreme() throws HttpException { + String ownerId = "1a405254c932b52e5b5caaa88186bc431a1bacb9ece631f835daddaf0c47677c"; + AccessControlList acl = createParser().parse( + IOUtils.toInputStream(aclExtreme)); + assertEquals(acl.getOwner().getId(), ownerId); + assertEquals(acl.getOwner().getDisplayName(), "jamesmurty"); + assertEquals(acl.getPermissions(ownerId).size(), 3); + assertTrue(acl.hasPermission(ownerId, Permission.FULL_CONTROL)); + assertTrue(acl.hasPermission(ownerId, Permission.READ)); + assertTrue(acl.hasPermission(ownerId, Permission.WRITE)); + assertEquals(acl.getGrants().size(), 9); + assertTrue(acl.hasPermission(GroupGranteeURI.ALL_USERS, Permission.READ)); + assertTrue(acl.hasPermission(GroupGranteeURI.AUTHENTICATED_USERS, Permission.READ)); + assertTrue(acl.hasPermission(GroupGranteeURI.AUTHENTICATED_USERS, Permission.WRITE)); + assertTrue(acl.hasPermission(GroupGranteeURI.AUTHENTICATED_USERS, Permission.READ_ACP)); + assertTrue(acl.hasPermission(GroupGranteeURI.AUTHENTICATED_USERS, Permission.WRITE_ACP)); + assertTrue(acl.hasPermission(GroupGranteeURI.LOG_DELIVERY, Permission.WRITE)); + } + +}