diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java index f688a661119c..edcaec047476 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java @@ -386,7 +386,7 @@ public long getQuotaInNamespace() { * for the bucket. * @throws IOException */ - public boolean addAcls(OzoneAcl addAcl) throws IOException { + public boolean addAcl(OzoneAcl addAcl) throws IOException { return proxy.addAcl(ozoneObj, addAcl); } @@ -396,10 +396,21 @@ public boolean addAcls(OzoneAcl addAcl) throws IOException { * removed does not exist for the bucket. * @throws IOException */ - public boolean removeAcls(OzoneAcl removeAcl) throws IOException { + public boolean removeAcl(OzoneAcl removeAcl) throws IOException { return proxy.removeAcl(ozoneObj, removeAcl); } + /** + * Acls to be set for given Ozone object. This operations reset ACL for + * given object to list of ACLs provided in argument. + * @param acls List of acls. + * + * @throws IOException if there is error. + * */ + public boolean setAcl(List acls) throws IOException { + return proxy.setAcl(ozoneObj, acls); + } + /** * Sets/Changes the storage type of the bucket. * @param newStorageType Storage type to be set diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneVolume.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneVolume.java index 369500f81f02..3847b1214f64 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneVolume.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneVolume.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.NoSuchElementException; +import org.apache.commons.collections.ListUtils; import org.apache.hadoop.hdds.client.OzoneQuota; import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.scm.client.HddsClientUtils; @@ -35,6 +36,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import org.apache.hadoop.ozone.security.acl.OzoneObj; +import org.apache.hadoop.ozone.security.acl.OzoneObjInfo; import static org.apache.hadoop.ozone.OzoneConsts.QUOTA_RESET; @@ -89,6 +92,8 @@ public class OzoneVolume extends WithMetadata { private int listCacheSize; + private OzoneObj ozoneObj; + /** * Constructs OzoneVolume instance. * @param conf Configuration object. @@ -122,6 +127,10 @@ public OzoneVolume(ConfigurationSource conf, ClientProtocol proxy, modificationTime = Instant.ofEpochSecond( this.creationTime.getEpochSecond(), this.creationTime.getNano()); } + this.ozoneObj = OzoneObjInfo.Builder.newBuilder() + .setVolumeName(name) + .setResType(OzoneObj.ResourceType.VOLUME) + .setStoreType(OzoneObj.StoreType.OZONE).build(); } /** @@ -261,7 +270,53 @@ public Instant getModificationTime() { * @return aclMap */ public List getAcls() { - return acls; + return ListUtils.unmodifiableList(acls); + } + + /** + * Adds ACLs to the volume. + * @param addAcl ACL to be added + * @return true - if acl is successfully added, false if acl already exists + * for the bucket. + * @throws IOException + */ + public boolean addAcl(OzoneAcl addAcl) throws IOException { + boolean added = proxy.addAcl(ozoneObj, addAcl); + if (added) { + acls.add(addAcl); + } + return added; + } + + /** + * Remove acl for Ozone object. Return true if acl is removed successfully + * else false. + * @param acl Ozone acl to be removed. + * + * @throws IOException if there is error. + * */ + public boolean removeAcl(OzoneAcl acl) throws IOException { + boolean removed = proxy.removeAcl(ozoneObj, acl); + if (removed) { + acls.remove(acl); + } + return removed; + } + + /** + * Acls to be set for given Ozone object. This operations reset ACL for + * given object to list of ACLs provided in argument. + * @param aclList List of acls. + * + * @throws IOException if there is error. + * */ + public boolean setAcl(List aclList) throws IOException { + boolean reset = proxy.setAcl(ozoneObj, aclList); + if (reset) { + acls.clear(); + acls.addAll(aclList); + } + return reset; } /** diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OzoneAclUtil.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OzoneAclUtil.java index 0c15d79fcf6d..9901ae719913 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OzoneAclUtil.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OzoneAclUtil.java @@ -68,6 +68,26 @@ public static List getAclList(String userName, return listOfAcls; } + /** + * Helper function to get acl list for one user/group. + * + * @param identityName + * @param type + * @param aclList + * @return list of OzoneAcls + * */ + public static List filterAclList(String identityName, + IAccessAuthorizer.ACLIdentityType type, List aclList) { + + if (aclList == null || aclList.isEmpty()) { + return new ArrayList<>(); + } + + List retList = aclList.stream().filter(acl -> acl.getType() == type + && acl.getName().equals(identityName)).collect(Collectors.toList()); + return retList; + } + /** * Check if acl right requested for given RequestContext exist * in provided acl list. diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java index 6e4581e2c330..3523618d66a5 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java @@ -145,7 +145,6 @@ public static String getAclString(ACLType acl) { throw new IllegalArgumentException("ACL right is not recognized"); } } - } /** diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java index 277359cfbb3a..dfc7d797e290 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java @@ -649,7 +649,7 @@ public void testAddBucketAcl() acls.add(new OzoneAcl(USER, "test", ACLType.ALL, ACCESS)); OzoneBucket bucket = volume.getBucket(bucketName); for (OzoneAcl acl : acls) { - assertTrue(bucket.addAcls(acl)); + assertTrue(bucket.addAcl(acl)); } OzoneBucket newBucket = volume.getBucket(bucketName); Assert.assertEquals(bucketName, newBucket.getName()); @@ -672,7 +672,7 @@ public void testRemoveBucketAcl() volume.createBucket(bucketName, builder.build()); OzoneBucket bucket = volume.getBucket(bucketName); for (OzoneAcl acl : acls) { - assertTrue(bucket.removeAcls(acl)); + assertTrue(bucket.removeAcl(acl)); } OzoneBucket newBucket = volume.getBucket(bucketName); Assert.assertEquals(bucketName, newBucket.getName()); @@ -2262,10 +2262,10 @@ public void testMultipartUploadWithACL() throws Exception { OzoneAcl acl2 = new OzoneAcl(USER, "Friday", ACLType.ALL, DEFAULT); OzoneAcl acl3 = new OzoneAcl(USER, "Jan", ACLType.ALL, ACCESS); OzoneAcl acl4 = new OzoneAcl(USER, "Feb", ACLType.ALL, ACCESS); - bucket.addAcls(acl1); - bucket.addAcls(acl2); - bucket.addAcls(acl3); - bucket.addAcls(acl4); + bucket.addAcl(acl1); + bucket.addAcl(acl2); + bucket.addAcl(acl3); + bucket.addAcl(acl4); doMultipartUpload(bucket, keyName, (byte)98); OzoneObj keyObj = OzoneObjInfo.Builder.newBuilder() diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java index b8bed64cc830..72a95ceb6b77 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java @@ -17,6 +17,31 @@ */ package org.apache.hadoop.ozone.s3.endpoint; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.hdds.client.ReplicationType; +import org.apache.hadoop.ozone.OzoneAcl; +import org.apache.hadoop.ozone.client.OzoneBucket; +import org.apache.hadoop.ozone.client.OzoneKey; +import org.apache.hadoop.ozone.client.OzoneMultipartUploadList; +import org.apache.hadoop.ozone.client.OzoneVolume; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes; +import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil; +import org.apache.hadoop.ozone.s3.commontypes.KeyMetadata; +import org.apache.hadoop.ozone.s3.endpoint.MultiDeleteRequest.DeleteObject; +import org.apache.hadoop.ozone.s3.endpoint.MultiDeleteResponse.DeletedObject; +import org.apache.hadoop.ozone.s3.endpoint.MultiDeleteResponse.Error; +import org.apache.hadoop.ozone.s3.endpoint.S3BucketAcl.Grant; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.s3.util.ContinueToken; +import org.apache.hadoop.ozone.s3.util.S3StorageType; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -33,29 +58,16 @@ import javax.ws.rs.core.Response; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; import java.util.Iterator; +import java.util.List; +import java.util.Set; -import org.apache.hadoop.hdds.client.ReplicationType; -import org.apache.hadoop.ozone.client.OzoneBucket; -import org.apache.hadoop.ozone.client.OzoneKey; -import org.apache.hadoop.ozone.client.OzoneMultipartUploadList; -import org.apache.hadoop.ozone.om.exceptions.OMException; -import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes; -import org.apache.hadoop.ozone.s3.commontypes.KeyMetadata; -import org.apache.hadoop.ozone.s3.endpoint.MultiDeleteRequest.DeleteObject; -import org.apache.hadoop.ozone.s3.endpoint.MultiDeleteResponse.DeletedObject; -import org.apache.hadoop.ozone.s3.endpoint.MultiDeleteResponse.Error; -import org.apache.hadoop.ozone.s3.exception.OS3Exception; -import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; -import org.apache.hadoop.ozone.s3.util.ContinueToken; -import org.apache.hadoop.ozone.s3.util.S3StorageType; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.apache.commons.lang3.StringUtils; +import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS; +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED; import static org.apache.hadoop.ozone.s3.util.S3Consts.ENCODING_TYPE; -import org.apache.http.HttpStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Bucket level rest endpoints. @@ -75,7 +87,7 @@ public class BucketEndpoint extends EndpointBase { @GET @SuppressFBWarnings @SuppressWarnings("parameternumber") - public Response list( + public Response get( @PathParam("bucket") String bucketName, @QueryParam("delimiter") String delimiter, @QueryParam("encoding-type") String encodingType, @@ -86,8 +98,14 @@ public Response list( @QueryParam("continuation-token") String continueToken, @QueryParam("start-after") String startAfter, @QueryParam("uploads") String uploads, + @QueryParam("acl") String aclMarker, @Context HttpHeaders hh) throws OS3Exception, IOException { + if (aclMarker != null) { + S3BucketAcl result = getAcl(bucketName); + return Response.ok(result, MediaType.APPLICATION_XML_TYPE).build(); + } + if (browser != null) { InputStream browserPage = getClass() .getResourceAsStream("/browser.html"); @@ -210,9 +228,13 @@ public Response list( } @PUT - public Response put(@PathParam("bucket") String bucketName, @Context - HttpHeaders httpHeaders) throws IOException, OS3Exception { - + public Response put(@PathParam("bucket") String bucketName, + @QueryParam("acl") String aclMarker, + @Context HttpHeaders httpHeaders, + InputStream body) throws IOException, OS3Exception { + if (aclMarker != null) { + return putAcl(bucketName, httpHeaders, body); + } try { String location = createS3Bucket(bucketName); LOG.info("Location is {}", location); @@ -353,6 +375,219 @@ public MultiDeleteResponse multiDelete(@PathParam("bucket") String bucketName, return result; } + /** + * Implement acl get. + *

+ * see: https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketAcl.html + */ + public S3BucketAcl getAcl(String bucketName) + throws OS3Exception, IOException { + S3BucketAcl result = new S3BucketAcl(); + try { + OzoneBucket bucket = getBucket(bucketName); + OzoneVolume volume = getVolume(); + // TODO: use bucket owner instead of volume owner here once bucket owner + // TODO: is supported. + S3Owner owner = new S3Owner(volume.getOwner(), volume.getOwner()); + result.setOwner(owner); + + // TODO: remove this duplication avoid logic when ACCESS and DEFAULT scope + // TODO: are merged. + // Use set to remove ACLs with different scopes(ACCESS and DEFAULT) + Set grantSet = new HashSet<>(); + // Return ACL list + for (OzoneAcl acl : bucket.getAcls()) { + List grants = S3Acl.ozoneNativeAclToS3Acl(acl); + grantSet.addAll(grants); + } + ArrayList grantList = new ArrayList<>(); + grantList.addAll(grantSet); + result.setAclList( + new S3BucketAcl.AccessControlList(grantList)); + return result; + } catch (OMException ex) { + if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { + throw S3ErrorTable.newError(S3ErrorTable + .NO_SUCH_BUCKET, bucketName); + } else if (ex.getResult() == ResultCodes.PERMISSION_DENIED) { + throw S3ErrorTable.newError(S3ErrorTable + .ACCESS_DENIED, bucketName); + } else { + LOG.error("Failed to get acl of Bucket " + bucketName, ex); + throw S3ErrorTable.newError(S3ErrorTable.INTERNAL_ERROR, bucketName); + } + } + } + + /** + * Implement acl put. + *

+ * see: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html + */ + public Response putAcl(String bucketName, HttpHeaders httpHeaders, + InputStream body) throws IOException, OS3Exception { + String grantReads = httpHeaders.getHeaderString(S3Acl.GRANT_READ); + String grantWrites = httpHeaders.getHeaderString(S3Acl.GRANT_WRITE); + String grantReadACP = httpHeaders.getHeaderString(S3Acl.GRANT_READ_CAP); + String grantWriteACP = httpHeaders.getHeaderString(S3Acl.GRANT_WRITE_CAP); + String grantFull = httpHeaders.getHeaderString(S3Acl.GRANT_FULL_CONTROL); + + try { + OzoneBucket bucket = getBucket(bucketName); + OzoneVolume volume = getVolume(); + + List ozoneAclListOnBucket = new ArrayList<>(); + List ozoneAclListOnVolume = new ArrayList<>(); + + if (grantReads == null && grantWrites == null && grantReadACP == null + && grantWriteACP == null && grantFull == null) { + S3BucketAcl putBucketAclRequest = + new PutBucketAclRequestUnmarshaller().readFrom( + null, null, null, null, null, body); + // Handle grants in body + ozoneAclListOnBucket.addAll( + S3Acl.s3AclToOzoneNativeAclOnBucket(putBucketAclRequest)); + ozoneAclListOnVolume.addAll( + S3Acl.s3AclToOzoneNativeAclOnVolume(putBucketAclRequest)); + } else { + + // Handle grants in headers + if (grantReads != null) { + ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantReads, + S3Acl.ACLType.READ.getValue())); + ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantReads, + S3Acl.ACLType.READ.getValue())); + } + if (grantWrites != null) { + ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantWrites, + S3Acl.ACLType.WRITE.getValue())); + ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantWrites, + S3Acl.ACLType.WRITE.getValue())); + } + if (grantReadACP != null) { + ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantReadACP, + S3Acl.ACLType.READ_ACP.getValue())); + ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantReadACP, + S3Acl.ACLType.READ_ACP.getValue())); + } + if (grantWriteACP != null) { + ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantWriteACP, + S3Acl.ACLType.WRITE_ACP.getValue())); + ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantWriteACP, + S3Acl.ACLType.WRITE_ACP.getValue())); + } + if (grantFull != null) { + ozoneAclListOnBucket.addAll(getAndConvertAclOnBucket(grantFull, + S3Acl.ACLType.FULL_CONTROL.getValue())); + ozoneAclListOnVolume.addAll(getAndConvertAclOnVolume(grantFull, + S3Acl.ACLType.FULL_CONTROL.getValue())); + } + } + + // A put request will reset all previous ACLs on bucket + bucket.setAcl(ozoneAclListOnBucket); + // A put request will reset input user/group's permission on volume + List acls = bucket.getAcls(); + List aclsToRemoveOnVolume = new ArrayList<>(); + List currentAclsOnVolume = volume.getAcls(); + // Remove input user/group's permission from Volume first + if (currentAclsOnVolume.size() > 0) { + for (OzoneAcl acl : acls) { + if (acl.getAclScope() == ACCESS) { + aclsToRemoveOnVolume.addAll(OzoneAclUtil.filterAclList( + acl.getName(), acl.getType(), currentAclsOnVolume)); + } + } + for (OzoneAcl acl : aclsToRemoveOnVolume) { + volume.removeAcl(acl); + } + } + // Add new permission on Volume + for(OzoneAcl acl : ozoneAclListOnVolume) { + volume.addAcl(acl); + } + } catch (OMException exception) { + LOG.error("Error in set ACL Request for bucket: {}", bucketName, + exception); + if (exception.getResult() == ResultCodes.BUCKET_NOT_FOUND) { + throw S3ErrorTable.newError(S3ErrorTable.NO_SUCH_BUCKET, + bucketName); + } else if (exception.getResult() == ResultCodes.PERMISSION_DENIED) { + throw S3ErrorTable.newError(S3ErrorTable + .ACCESS_DENIED, bucketName); + } + throw exception; + } + return Response.status(HttpStatus.SC_OK).build(); + } + + /** + * Example: x-amz-grant-write: \ + * uri="http://acs.amazonaws.com/groups/s3/LogDelivery", id="111122223333", \ + * id="555566667777". + */ + private List getAndConvertAclOnBucket(String value, + String permission) throws OS3Exception { + List ozoneAclList = new ArrayList<>(); + if (StringUtils.isEmpty(value)) { + return ozoneAclList; + } + String[] subValues = value.split(","); + for (String acl: subValues) { + String[] part = acl.split("="); + if (part.length != 2) { + throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, acl); + } + S3Acl.ACLIdentityType type = + S3Acl.ACLIdentityType.getTypeFromHeaderType(part[0]); + if (type == null || !type.isSupported()) { + LOG.warn("S3 grantee {} is null or not supported", part[0]); + throw S3ErrorTable.newError(NOT_IMPLEMENTED, part[0]); + } + // Build ACL on Bucket + BitSet aclsOnBucket = + S3Acl.getOzoneAclOnBucketFromS3Permission(permission); + OzoneAcl defaultOzoneAcl = new OzoneAcl( + IAccessAuthorizer.ACLIdentityType.USER, part[1], aclsOnBucket, + OzoneAcl.AclScope.DEFAULT); + OzoneAcl accessOzoneAcl = new OzoneAcl( + IAccessAuthorizer.ACLIdentityType.USER, part[1], aclsOnBucket, + ACCESS); + ozoneAclList.add(defaultOzoneAcl); + ozoneAclList.add(accessOzoneAcl); + } + return ozoneAclList; + } + + private List getAndConvertAclOnVolume(String value, + String permission) throws OS3Exception { + List ozoneAclList = new ArrayList<>(); + if (StringUtils.isEmpty(value)) { + return ozoneAclList; + } + String[] subValues = value.split(","); + for (String acl: subValues) { + String[] part = acl.split("="); + if (part.length != 2) { + throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, acl); + } + S3Acl.ACLIdentityType type = + S3Acl.ACLIdentityType.getTypeFromHeaderType(part[0]); + if (type == null || !type.isSupported()) { + LOG.warn("S3 grantee {} is null or not supported", part[0]); + throw S3ErrorTable.newError(NOT_IMPLEMENTED, part[0]); + } + // Build ACL on Volume + BitSet aclsOnVolume = + S3Acl.getOzoneAclOnVolumeFromS3Permission(permission); + OzoneAcl accessOzoneAcl = new OzoneAcl( + IAccessAuthorizer.ACLIdentityType.USER, part[1], aclsOnVolume, + ACCESS); + ozoneAclList.add(accessOzoneAcl); + } + return ozoneAclList; + } + private void addKey(ListObjectResponse response, OzoneKey next) { KeyMetadata keyMetadata = new KeyMetadata(); keyMetadata.setKey(next.getName()); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsResult.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsResult.java index 20dc9cdaac1e..46297f732aaa 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsResult.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsResult.java @@ -38,9 +38,6 @@ "http://s3.amazonaws.com/doc/2006-03-01/") public class ListMultipartUploadsResult { - public static final Owner - NOT_SUPPORTED_OWNER = new Owner("NOT-SUPPORTED", "Not Supported"); - @XmlElement(name = "Bucket") private String bucket; @@ -148,10 +145,10 @@ public static class Upload { private String uploadId; @XmlElement(name = "Owner") - private Owner owner = NOT_SUPPORTED_OWNER; + private S3Owner owner = S3Owner.NOT_SUPPORTED_OWNER; @XmlElement(name = "Initiator") - private Owner initiator = NOT_SUPPORTED_OWNER; + private S3Owner initiator = S3Owner.NOT_SUPPORTED_OWNER; @XmlElement(name = "StorageClass") private String storageClass = "STANDARD"; @@ -193,21 +190,21 @@ public void setUploadId(String uploadId) { this.uploadId = uploadId; } - public Owner getOwner() { + public S3Owner getOwner() { return owner; } public void setOwner( - Owner owner) { + S3Owner owner) { this.owner = owner; } - public Owner getInitiator() { + public S3Owner getInitiator() { return initiator; } public void setInitiator( - Owner initiator) { + S3Owner initiator) { this.initiator = initiator; } @@ -227,42 +224,4 @@ public void setInitiated(Instant initiated) { this.initiated = initiated; } } - - /** - * Upload information. - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlRootElement(name = "Owner") - public static class Owner { - - @XmlElement(name = "ID") - private String id; - - @XmlElement(name = "DisplayName") - private String displayName; - - public Owner() { - } - - public Owner(String id, String displayName) { - this.id = id; - this.displayName = displayName; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/PutBucketAclRequestUnmarshaller.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/PutBucketAclRequestUnmarshaller.java new file mode 100644 index 000000000000..3ca2e47c469e --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/PutBucketAclRequestUnmarshaller.java @@ -0,0 +1,85 @@ +/* + * 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.apache.hadoop.ozone.s3.endpoint; + +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.UnmarshallerHandler; +import javax.xml.parsers.SAXParserFactory; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static org.apache.hadoop.ozone.s3.util.S3Consts.S3_XML_NAMESPACE; + +/** + * Custom unmarshaller to read PutBucketAclRequest wo namespace. + */ +@Provider +public class PutBucketAclRequestUnmarshaller + implements MessageBodyReader { + + private final JAXBContext context; + private final XMLReader xmlReader; + + public PutBucketAclRequestUnmarshaller() { + try { + context = JAXBContext.newInstance(S3BucketAcl.class); + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + xmlReader = saxParserFactory.newSAXParser().getXMLReader(); + } catch (Exception ex) { + throw new AssertionError("Can not instantiate " + + "PutBucketAclRequest parser", ex); + } + } + @Override + public boolean isReadable(Class aClass, Type type, + Annotation[] annotations, MediaType mediaType) { + return type.equals(S3BucketAcl.class); + } + + @Override + public S3BucketAcl readFrom( + Class aClass, Type type, + Annotation[] annotations, MediaType mediaType, + MultivaluedMap multivaluedMap, + InputStream inputStream) throws IOException, WebApplicationException { + try { + UnmarshallerHandler unmarshallerHandler = + context.createUnmarshaller().getUnmarshallerHandler(); + XmlNamespaceFilter filter = + new XmlNamespaceFilter(S3_XML_NAMESPACE); + filter.setContentHandler(unmarshallerHandler); + filter.setParent(xmlReader); + filter.parse(new InputSource(inputStream)); + return (S3BucketAcl)(unmarshallerHandler.getResult()); + } catch (Exception e) { + throw new WebApplicationException("Can't parse request body to XML.", e); + } + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Acl.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Acl.java new file mode 100644 index 000000000000..111f28ce9254 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Acl.java @@ -0,0 +1,342 @@ +/* + * 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.apache.hadoop.ozone.s3.endpoint; + +import org.apache.hadoop.ozone.OzoneAcl; +import org.apache.hadoop.ozone.s3.endpoint.S3BucketAcl.Grant; +import org.apache.hadoop.ozone.s3.endpoint.S3BucketAcl.Grantee; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_ARGUMENT; +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED; + +public final class S3Acl { + private static final Logger LOG = LoggerFactory.getLogger(S3Acl.class); + + // ACL put related headers + public static final String GRANT_READ = "x-amz-grant-read"; + public static final String GRANT_WRITE = "x-amz-grant-write"; + public static final String GRANT_READ_CAP = "x-amz-grant-read-acp"; + public static final String GRANT_WRITE_CAP = "x-amz-grant-write-acp"; + public static final String GRANT_FULL_CONTROL = "x-amz-grant-full-control"; + + // Not supported headers at current stage, may support it in future + public static final String CANNED_ACL_HEADER = "x-amz-acl"; + + /** + * https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html. + */ + enum ACLType { + // Allows grantee to list the objects in the bucket + READ("READ"), + // Allows grantee to create, overwrite, and delete any object in the bucket + WRITE("WRITE"), + // Allows grantee to write the bucket ACL + READ_ACP("READ_ACP"), + // Allows grantee to write the ACL for the applicable bucket + WRITE_ACP("WRITE_ACP"), + // Allows grantee above all permissions on the bucket + FULL_CONTROL("FULL_CONTROL"); + + public String getValue() { + return value; + } + /** + * String value for this Enum. + */ + private final String value; + + /** + * @param val String type for this enum. + */ + ACLType(String val) { + value = val; + } + + + public static ACLType getType(String typeStr) { + for(ACLType type: ACLType.values()) { + if (type.getValue().equals(typeStr)) { + return type; + } + } + return null; + } + } + + enum ACLIdentityType { + USER("CanonicalUser", true, "id"), + GROUP("Group", false, "url"), + USER_BY_EMAIL("AmazonCustomerByEmail", false, "emailAddress"); + + public String getGranteeType() { + return granteeType; + } + + public String getHeaderType() { + return granteeInHeader; + } + + /** + * Grantee type in body XML. + */ + private final String granteeType; + + /** + * Is this type supported or not. + */ + private final boolean supported; + + /** + * Grantee type in header. + */ + private final String granteeInHeader; + + /** + * Init OzoneACLtypes enum. + * + * @param val String type for this enum. + */ + ACLIdentityType(String val, boolean support, String headerType) { + granteeType = val; + supported = support; + granteeInHeader = headerType; + } + + boolean isSupported() { + return supported; + } + + public static ACLIdentityType getTypeFromGranteeType(String typeStr) { + for(ACLIdentityType type: ACLIdentityType.values()) { + if (type.getGranteeType().equals(typeStr)) { + return type; + } + } + return null; + } + + public static ACLIdentityType getTypeFromHeaderType(String typeStr) { + for(ACLIdentityType type: ACLIdentityType.values()) { + if (type.getHeaderType().equals(typeStr)) { + return type; + } + } + return null; + } + } + + private S3Acl() { + } + + public static boolean isGranteeTypeSupported(String typeStr) { + ACLIdentityType type = ACLIdentityType.getTypeFromGranteeType(typeStr); + return type == null ? false : type.isSupported(); + } + + public static boolean isHeaderTypeSupported(String typeStr) { + ACLIdentityType type = ACLIdentityType.getTypeFromHeaderType(typeStr); + return type == null ? false : type.isSupported(); + } + + public static List ozoneNativeAclToS3Acl(OzoneAcl ozoneAcl) { + // Since currently only "CanonicalUser" is supported, which maps to Ozone + // "USER" + List grantList = new ArrayList<>(); + if (ozoneAcl.getType() != IAccessAuthorizer.ACLIdentityType.USER) { + return grantList; + } + + Grantee grantee = new Grantee(); + grantee.setDisplayName(ozoneAcl.getName()); + grantee.setId(ozoneAcl.getName()); + + List acls = ozoneAcl.getAclList(); + if (acls.contains(IAccessAuthorizer.ACLType.ALL)) { + Grant grant = new Grant(); + grant.setGrantee(grantee); + grant.setPermission(ACLType.FULL_CONTROL.toString()); + grantList.add(grant); + } else if (acls.contains(IAccessAuthorizer.ACLType.WRITE_ACL)) { + Grant grant = new Grant(); + grant.setGrantee(grantee); + grant.setPermission(ACLType.WRITE_ACP.toString()); + grantList.add(grant); + } else if (acls.contains(IAccessAuthorizer.ACLType.READ_ACL)) { + Grant grant = new Grant(); + grant.setGrantee(grantee); + grant.setPermission(ACLType.READ_ACP.toString()); + grantList.add(grant); + } else if (acls.contains(IAccessAuthorizer.ACLType.WRITE) && + acls.contains(IAccessAuthorizer.ACLType.DELETE) && + acls.contains(IAccessAuthorizer.ACLType.CREATE)) { + Grant grant = new Grant(); + grant.setGrantee(grantee); + grant.setPermission(ACLType.WRITE.toString()); + grantList.add(grant); + } else if (acls.contains(IAccessAuthorizer.ACLType.READ) && + acls.contains(IAccessAuthorizer.ACLType.LIST)) { + Grant grant = new Grant(); + grant.setGrantee(grantee); + grant.setPermission(ACLType.READ.toString()); + grantList.add(grant); + } else { + LOG.error("Cannot find a good mapping for Ozone ACL {} to S3", + ozoneAcl.toString()); + } + return grantList; + } + + public static List s3AclToOzoneNativeAclOnBucket( + S3BucketAcl bucketAcl) throws OS3Exception { + List ozoneAclList = new ArrayList<>(); + List grantList = bucketAcl.getAclList().getGrantList(); + for (Grant grant : grantList) { + // Only "CanonicalUser" is supported, which maps to Ozone "USER" + ACLIdentityType identityType = ACLIdentityType.getTypeFromGranteeType( + grant.getGrantee().getXsiType()); + if (identityType != null && identityType.isSupported()) { + String permission = grant.getPermission(); + BitSet acls = getOzoneAclOnBucketFromS3Permission(permission); + OzoneAcl defaultOzoneAcl = new OzoneAcl( + IAccessAuthorizer.ACLIdentityType.USER, + grant.getGrantee().getId(), acls, + OzoneAcl.AclScope.DEFAULT); + OzoneAcl accessOzoneAcl = new OzoneAcl( + IAccessAuthorizer.ACLIdentityType.USER, + grant.getGrantee().getId(), acls, + OzoneAcl.AclScope.ACCESS); + ozoneAclList.add(defaultOzoneAcl); + ozoneAclList.add(accessOzoneAcl); + } else { + LOG.error("Grantee type {} is not supported", + grant.getGrantee().getXsiType()); + throw S3ErrorTable.newError(NOT_IMPLEMENTED, + grant.getGrantee().getXsiType()); + } + } + return ozoneAclList; + } + + public static BitSet getOzoneAclOnBucketFromS3Permission(String permission) + throws OS3Exception { + ACLType permissionType = ACLType.getType(permission); + if (permissionType == null) { + throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, permission); + } + BitSet acls = new BitSet(IAccessAuthorizer.ACLType.getNoOfAcls()); + switch (permissionType) { + case FULL_CONTROL: + acls.set(IAccessAuthorizer.ACLType.ALL.ordinal()); + break; + case WRITE_ACP: + acls.set(IAccessAuthorizer.ACLType.WRITE_ACL.ordinal()); + break; + case READ_ACP: + acls.set(IAccessAuthorizer.ACLType.READ_ACL.ordinal()); + break; + case WRITE: + acls.set(IAccessAuthorizer.ACLType.WRITE.ordinal()); + acls.set(IAccessAuthorizer.ACLType.DELETE.ordinal()); + acls.set(IAccessAuthorizer.ACLType.CREATE.ordinal()); + break; + case READ: + acls.set(IAccessAuthorizer.ACLType.READ.ordinal()); + acls.set(IAccessAuthorizer.ACLType.LIST.ordinal()); + break; + default: + LOG.error("Failed to recognize S3 permission {}", permission); + throw S3ErrorTable.newError(INVALID_ARGUMENT, permission); + } + return acls; + } + + public static List s3AclToOzoneNativeAclOnVolume( + S3BucketAcl bucketAcl) throws OS3Exception { + List ozoneAclList = new ArrayList<>(); + List grantList = bucketAcl.getAclList().getGrantList(); + for (Grant grant : grantList) { + // Only "CanonicalUser" is supported, which maps to Ozone "USER" + ACLIdentityType identityType = ACLIdentityType.getTypeFromGranteeType( + grant.getGrantee().getXsiType()); + if (identityType != null && identityType.isSupported()) { + String permission = grant.getPermission(); + BitSet acls = getOzoneAclOnVolumeFromS3Permission(permission); + OzoneAcl accessOzoneAcl = new OzoneAcl( + IAccessAuthorizer.ACLIdentityType.USER, + grant.getGrantee().getId(), acls, + OzoneAcl.AclScope.ACCESS); + ozoneAclList.add(accessOzoneAcl); + } else { + LOG.error("Grantee type {} is not supported", + grant.getGrantee().getXsiType()); + throw S3ErrorTable.newError(NOT_IMPLEMENTED, + grant.getGrantee().getXsiType()); + } + } + return ozoneAclList; + } + + // User privilege on volume follows the "lest privilege" principle. + public static BitSet getOzoneAclOnVolumeFromS3Permission(String permission) + throws OS3Exception { + BitSet acls = new BitSet(IAccessAuthorizer.ACLType.getNoOfAcls()); + ACLType permissionType = ACLType.getType(permission); + if (permissionType == null) { + throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, permission); + } + switch (permissionType) { + case FULL_CONTROL: + acls.set(IAccessAuthorizer.ACLType.READ.ordinal()); + acls.set(IAccessAuthorizer.ACLType.WRITE.ordinal()); + acls.set(IAccessAuthorizer.ACLType.READ_ACL.ordinal()); + acls.set(IAccessAuthorizer.ACLType.WRITE_ACL.ordinal()); + break; + case WRITE_ACP: + acls.set(IAccessAuthorizer.ACLType.READ.ordinal()); + acls.set(IAccessAuthorizer.ACLType.READ_ACL.ordinal()); + acls.set(IAccessAuthorizer.ACLType.WRITE_ACL.ordinal()); + break; + case READ_ACP: + acls.set(IAccessAuthorizer.ACLType.READ.ordinal()); + acls.set(IAccessAuthorizer.ACLType.READ_ACL.ordinal()); + break; + case WRITE: + acls.set(IAccessAuthorizer.ACLType.READ.ordinal()); + acls.set(IAccessAuthorizer.ACLType.WRITE.ordinal()); + break; + case READ: + acls.set(IAccessAuthorizer.ACLType.READ.ordinal()); + break; + default: + LOG.error("Failed to recognize S3 permission {}", permission); + throw S3ErrorTable.newError(INVALID_ARGUMENT, permission); + } + return acls; + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3BucketAcl.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3BucketAcl.java new file mode 100644 index 000000000000..7a699e482744 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3BucketAcl.java @@ -0,0 +1,235 @@ +/** + * 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.apache.hadoop.ozone.s3.endpoint; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Bucket ACL. + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "AccessControlPolicy", + namespace = "http://s3.amazonaws.com/doc/2006-03-01/") +public class S3BucketAcl { + + @XmlElement(name = "Owner") + private S3Owner owner; + + @XmlElement(name = "AccessControlList") + private AccessControlList aclList; + + public S3Owner getOwner() { + return owner; + } + + public void setOwner(S3Owner owner) { + this.owner = owner; + } + + public AccessControlList getAclList() { + return aclList; + } + + public void setAclList(AccessControlList aclList) { + this.aclList = aclList; + } + + @Override + public String toString() { + return "GetBucketAclResponse{" + + "owner=" + owner + + ", aclList=" + aclList + + '}'; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "AccessControlList") + public static class AccessControlList { + + @XmlElement(name = "Grant") + private List grantList = new ArrayList<>(); + + public void addGrant(Grant grant) { + grantList.add(grant); + } + + public List getGrantList() { + return grantList; + } + + public AccessControlList(List grants) { + this.grantList = grants; + } + + public AccessControlList() { + + } + + @Override + public String toString() { + return "AccessControlList{" + + "grantList=" + grantList + + '}'; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "Grant") + public static class Grant { + + @XmlElement(name = "Grantee") + private Grantee grantee; + + @XmlElement(name = "Permission") + private String permission; + + public String getPermission() { + return permission; + } + + public void setPermission(String permission) { + this.permission = permission; + } + + public Grantee getGrantee() { + return grantee; + } + + public void setGrantee(Grantee grantee) { + this.grantee = grantee; + } + + @Override + public String toString() { + return "Grant{" + + "grantee=" + grantee + + ", permission='" + permission + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Grant grant = (Grant) o; + return Objects.equals(grantee, grant.grantee) && + Objects.equals(permission, grant.permission); + } + + @Override + public int hashCode() { + return Objects.hash(grantee, permission); + } + } + + /** + * A grantee can be an AWS account or one of the predefined Amazon S3 groups. + * https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "Grantee") + public static class Grantee { + + @XmlElement(name = "DisplayName") + private String displayName; + + @XmlElement(name = "ID") + private String id; + + @XmlAttribute(name="xsi:type") + private String xsiType = "CanonicalUser"; + + @XmlAttribute(name="xmlns:xsi") + private String xsiNs = "http://www.w3.org/2001/XMLSchema-instance"; + + public String getXsiNs() { + return xsiNs; + } + + public String getXsiType() { + return xsiType; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String name) { + this.displayName = name; + } + + public void setXsiType(String type) { + this.xsiType = type; + } + + public void setXsiNs(String ns) { + this.xsiNs = ns; + } + + @Override + public String toString() { + return "Grantee{" + + "displayName='" + displayName + '\'' + + ", id='" + id + '\'' + + ", xsiType='" + xsiType + '\'' + + ", xsiNs='" + xsiNs + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Grantee grantee = (Grantee) o; + return Objects.equals(displayName, grantee.displayName) && + Objects.equals(id, grantee.id) && + Objects.equals(xsiType, grantee.xsiType) && + Objects.equals(xsiNs, grantee.xsiNs); + } + + @Override + public int hashCode() { + return Objects.hash(displayName, id, xsiType, xsiNs); + } + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java new file mode 100644 index 000000000000..48f154e53bef --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3Owner.java @@ -0,0 +1,72 @@ +/* + * 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.apache.hadoop.ozone.s3.endpoint; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "Owner") +public class S3Owner { + + public static final S3Owner + NOT_SUPPORTED_OWNER = new S3Owner("NOT-SUPPORTED", "Not Supported"); + + @XmlElement(name = "DisplayName") + private String displayName; + + @XmlElement(name = "ID") + private String id; + + public S3Owner() { + + } + + public S3Owner(String id, String displayName) { + this.id = id; + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String name) { + this.displayName = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "S3Owner{" + + "displayName='" + displayName + '\'' + + ", id='" + id + '\'' + + '}'; + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 7d9b55fb4165..86d9fc049cd8 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -25,6 +25,7 @@ import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_PRECON_FAILED; +import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; import static java.net.HttpURLConnection.HTTP_SERVER_ERROR; import static org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_NOT_SATISFIABLE; @@ -114,6 +115,10 @@ private S3ErrorTable() { public static final OS3Exception PRECOND_FAILED = new OS3Exception( "PreconditionFailed", "At least one of the pre-conditions you " + "specified did not hold", HTTP_PRECON_FAILED); + + public static final OS3Exception NOT_IMPLEMENTED = new OS3Exception( + "NotImplemented", "This part of feature is not implemented yet.", + HTTP_NOT_IMPLEMENTED); /** * Create a new instance of Error. diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java index 7861bd4aad5d..30cd412d343b 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java @@ -35,6 +35,7 @@ import org.apache.hadoop.hdds.client.ReplicationFactor; import org.apache.hadoop.hdds.client.ReplicationType; import org.apache.hadoop.hdds.protocol.StorageType; +import org.apache.hadoop.ozone.OzoneAcl; import org.apache.hadoop.ozone.client.io.OzoneInputStream; import org.apache.hadoop.ozone.client.io.OzoneOutputStream; import org.apache.hadoop.ozone.client.OzoneMultipartUploadPartListParts.PartInfo; @@ -57,6 +58,8 @@ public class OzoneBucketStub extends OzoneBucket { private Map> partList = new HashMap<>(); + private ArrayList aclList = new ArrayList<>(); + /** * Constructs OzoneBucket instance. * @@ -291,7 +294,27 @@ public OzoneMultipartUploadPartListParts listParts(String key, return ozoneMultipartUploadPartListParts; } + } + + @Override + public List getAcls() throws IOException { + return (List)aclList.clone(); + } + + @Override + public boolean removeAcl(OzoneAcl removeAcl) throws IOException { + return aclList.remove(removeAcl); + } + + @Override + public boolean addAcl(OzoneAcl addAcl) throws IOException { + return aclList.add(addAcl); + } + @Override + public boolean setAcl(List acls) throws IOException { + aclList.clear(); + return aclList.addAll(acls); } /** diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java index f5e06035fed6..55a7191ac38a 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java @@ -20,6 +20,7 @@ package org.apache.hadoop.ozone.client; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -38,6 +39,8 @@ public class OzoneVolumeStub extends OzoneVolume { private Map buckets = new HashMap<>(); + private ArrayList aclList = new ArrayList<>(); + public OzoneVolumeStub(String name, String admin, String owner, long quotaInBytes, long quotaInNamespace, long creationTime, List acls) { @@ -107,4 +110,25 @@ public void deleteBucket(String bucketName) throws IOException { throw new OMException("", OMException.ResultCodes.BUCKET_NOT_FOUND); } } + + @Override + public List getAcls() { + return (List)aclList.clone(); + } + + @Override + public boolean addAcl(OzoneAcl addAcl) throws IOException { + return aclList.add(addAcl); + } + + @Override + public boolean removeAcl(OzoneAcl acl) throws IOException { + return aclList.remove(acl); + } + + @Override + public boolean setAcl(List acls) throws IOException { + aclList.clear(); + return aclList.addAll(acls); + } } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java new file mode 100644 index 000000000000..703ed98c4095 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketAcl.java @@ -0,0 +1,266 @@ +/* + * 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.apache.hadoop.ozone.s3.endpoint; + +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.client.OzoneClient; +import org.apache.hadoop.ozone.client.OzoneClientStub; +import org.apache.hadoop.ozone.client.OzoneVolume; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; +import static java.net.HttpURLConnection.HTTP_OK; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +/** + * This class tests Bucket ACL get/set request. + */ +public class TestBucketAcl { + + private static final String BUCKET_NAME = OzoneConsts.S3_BUCKET; + private OzoneClient client; + + private HttpServletRequest servletRequest; + private Map parameterMap; + private HttpHeaders headers; + private BucketEndpoint bucketEndpoint; + private static final String ACL_MARKER = "acl"; + + @Before + public void setup() throws IOException { + client = new OzoneClientStub(); + client.getObjectStore().createS3Bucket(BUCKET_NAME); + + servletRequest = Mockito.mock(HttpServletRequest.class); + parameterMap = Mockito.mock(Map.class); + headers = Mockito.mock(HttpHeaders.class); + when(servletRequest.getParameterMap()).thenReturn(parameterMap); + + bucketEndpoint = new BucketEndpoint(); + bucketEndpoint.setClient(client); + } + + @After + public void clean() throws IOException { + if (client != null) { + client.close(); + } + } + + @Test + public void testGetAcl() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + Response response = + bucketEndpoint.get(BUCKET_NAME, null, null, null, 0, null, null, + null, null, null, ACL_MARKER, headers); + assertEquals(HTTP_OK, response.getStatus()); + System.out.println(response.getEntity()); + } + + @Test + public void testSetAclWithNotSupportedGranteeType() throws Exception { + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.GROUP.getHeaderType() + "=root"); + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + try { + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + } catch (Exception e) { + Assert.assertTrue(e instanceof OS3Exception && + ((OS3Exception) e).getHttpCode() == HTTP_NOT_IMPLEMENTED); + } + } + + @Test + public void testRead() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.READ.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + } + + @Test + public void testWrite() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_WRITE)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.WRITE.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + } + + @Test + public void testReadACP() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ_CAP)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = + bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.READ_ACP.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + } + + @Test + public void testWriteACP() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_WRITE_CAP)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.WRITE_ACP.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + } + + @Test + public void testFullControl() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.FULL_CONTROL.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + } + + @Test + public void testCombination() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + when(headers.getHeaderString(S3Acl.GRANT_WRITE)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + when(headers.getHeaderString(S3Acl.GRANT_READ_CAP)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + when(headers.getHeaderString(S3Acl.GRANT_WRITE_CAP)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + when(headers.getHeaderString(S3Acl.GRANT_FULL_CONTROL)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(5, getResponse.getAclList().getGrantList().size()); + } + + @Test + public void testPutClearOldAcls() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + // Put READ + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.READ.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + OzoneVolume volume = bucketEndpoint.getVolume(); + assertEquals(1, volume.getAcls().size()); + assertEquals(IAccessAuthorizer.ACLType.READ, + volume.getAcls().get(0).getAclList().get(0)); + + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(null); + when(headers.getHeaderString(S3Acl.GRANT_WRITE)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + //Put WRITE + response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, null); + assertEquals(HTTP_OK, response.getStatus()); + getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(1, getResponse.getAclList().getGrantList().size()); + assertEquals(S3Acl.ACLType.WRITE.getValue(), + getResponse.getAclList().getGrantList().get(0).getPermission()); + volume = bucketEndpoint.getVolume(); + assertEquals(1, volume.getAcls().size()); + assertEquals(IAccessAuthorizer.ACLType.READ, + volume.getAcls().get(0).getAclList().get(0)); + } + + @Test(expected = OS3Exception.class) + public void testAclInBodyWithGroupUser() throws Exception { + InputStream inputBody = TestBucketAcl.class.getClassLoader() + .getResourceAsStream("groupAccessControlList.xml"); + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, inputBody); + } + + @Test + public void testAclInBody() throws Exception { + InputStream inputBody = TestBucketAcl.class.getClassLoader() + .getResourceAsStream("userAccessControlList.xml"); + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + Response response = + bucketEndpoint.put(BUCKET_NAME, ACL_MARKER, headers, inputBody); + assertEquals(HTTP_OK, response.getStatus()); + S3BucketAcl getResponse = bucketEndpoint.getAcl(BUCKET_NAME); + assertEquals(2, getResponse.getAclList().getGrantList().size()); + } + + @Test + public void testBucketNotExist() throws Exception { + when(parameterMap.containsKey(ACL_MARKER)).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + try { + bucketEndpoint.getAcl("bucket-not-exist"); + } catch (Exception e) { + Assert.assertTrue(e instanceof OS3Exception && + ((OS3Exception)e).getHttpCode() == HTTP_NOT_FOUND); + } + } +} diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketGet.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java similarity index 80% rename from hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketGet.java rename to hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java index 9bafae5a4fce..84e7555c3e72 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketGet.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketList.java @@ -33,7 +33,7 @@ /** * Testing basic object list browsing. */ -public class TestBucketGet { +public class TestBucketList { @Test public void listRoot() throws OS3Exception, IOException { @@ -45,8 +45,8 @@ public void listRoot() throws OS3Exception, IOException { getBucket.setClient(client); ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket - .list("b1", "/", null, null, 100, "", null, null, null, null, null) + (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, "", + null, null, null, null, null, null) .getEntity(); Assert.assertEquals(1, getBucketResponse.getCommonPrefixes().size()); @@ -69,8 +69,8 @@ public void listDir() throws OS3Exception, IOException { getBucket.setClient(client); ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, 100, - "dir1", null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, + "dir1", null, null, null, null, null, null).getEntity(); Assert.assertEquals(1, getBucketResponse.getCommonPrefixes().size()); Assert.assertEquals("dir1/", @@ -93,8 +93,8 @@ public void listSubDir() throws OS3Exception, IOException { ListObjectResponse getBucketResponse = (ListObjectResponse) getBucket - .list("b1", "/", null, null, 100, "dir1/", null, null, - null, null, null) + .get("b1", "/", null, null, 100, "dir1/", null, null, + null, null, null, null) .getEntity(); Assert.assertEquals(1, getBucketResponse.getCommonPrefixes().size()); @@ -120,8 +120,8 @@ public void listWithPrefixAndDelimiter() throws OS3Exception, IOException { getBucket.setClient(ozoneClient); ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, 100, - "dir1", null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, + "dir1", null, null, null, null, null, null).getEntity(); Assert.assertEquals(3, getBucketResponse.getCommonPrefixes().size()); @@ -139,8 +139,8 @@ public void listWithPrefixAndDelimiter1() throws OS3Exception, IOException { getBucket.setClient(ozoneClient); ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, 100, - "", null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, + "", null, null, null, null, null, null).getEntity(); Assert.assertEquals(3, getBucketResponse.getCommonPrefixes().size()); Assert.assertEquals("file2", getBucketResponse.getContents().get(0) @@ -160,8 +160,8 @@ public void listWithPrefixAndDelimiter2() throws OS3Exception, IOException { getBucket.setClient(ozoneClient); ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, 100, - "dir1bh", null, null, "dir1/dir2/file2", null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, 100, "dir1bh", + null, null, "dir1/dir2/file2", null, null, null).getEntity(); Assert.assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -183,8 +183,8 @@ public void listWithContinuationToken() throws OS3Exception, IOException { // First time ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", null, null, null, maxKeys, - "", null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", null, null, null, maxKeys, + "", null, null, null, null, null, null).getEntity(); Assert.assertTrue(getBucketResponse.isTruncated()); Assert.assertEquals(2, getBucketResponse.getContents().size()); @@ -192,8 +192,8 @@ public void listWithContinuationToken() throws OS3Exception, IOException { // 2nd time String continueToken = getBucketResponse.getNextToken(); getBucketResponse = - (ListObjectResponse) getBucket.list("b1", null, null, null, maxKeys, - "", null, continueToken, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", null, null, null, maxKeys, + "", null, continueToken, null, null, null, null).getEntity(); Assert.assertTrue(getBucketResponse.isTruncated()); Assert.assertEquals(2, getBucketResponse.getContents().size()); @@ -202,8 +202,8 @@ public void listWithContinuationToken() throws OS3Exception, IOException { //3rd time getBucketResponse = - (ListObjectResponse) getBucket.list("b1", null, null, null, maxKeys, - "", null, continueToken, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", null, null, null, maxKeys, + "", null, continueToken, null, null, null, null).getEntity(); Assert.assertFalse(getBucketResponse.isTruncated()); Assert.assertEquals(1, getBucketResponse.getContents().size()); @@ -234,8 +234,8 @@ public void listWithContinuationTokenDirBreak() ListObjectResponse getBucketResponse; getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, maxKeys, - "test/", null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, + "test/", null, null, null, null, null, null).getEntity(); Assert.assertEquals(0, getBucketResponse.getContents().size()); Assert.assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -245,9 +245,9 @@ public void listWithContinuationTokenDirBreak() getBucketResponse.getCommonPrefixes().get(1).getPrefix()); getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, maxKeys, - "test/", null, getBucketResponse.getNextToken(), null, null, null) - .getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, + "test/", null, getBucketResponse.getNextToken(), null, null, null, + null).getEntity(); Assert.assertEquals(1, getBucketResponse.getContents().size()); Assert.assertEquals(1, getBucketResponse.getCommonPrefixes().size()); Assert.assertEquals("test/dir3/", @@ -277,8 +277,8 @@ public void listWithContinuationToken1() throws OS3Exception, IOException { // First time ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, maxKeys, - "dir", null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, + "dir", null, null, null, null, null, null).getEntity(); Assert.assertTrue(getBucketResponse.isTruncated()); Assert.assertEquals(2, getBucketResponse.getCommonPrefixes().size()); @@ -286,16 +286,16 @@ public void listWithContinuationToken1() throws OS3Exception, IOException { // 2nd time String continueToken = getBucketResponse.getNextToken(); getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, maxKeys, - "dir", null, continueToken, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, + "dir", null, continueToken, null, null, null, null).getEntity(); Assert.assertTrue(getBucketResponse.isTruncated()); Assert.assertEquals(2, getBucketResponse.getCommonPrefixes().size()); //3rd time continueToken = getBucketResponse.getNextToken(); getBucketResponse = - (ListObjectResponse) getBucket.list("b1", "/", null, null, maxKeys, - "dir", null, continueToken, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", "/", null, null, maxKeys, + "dir", null, continueToken, null, null, null, null).getEntity(); Assert.assertFalse(getBucketResponse.isTruncated()); Assert.assertEquals(1, getBucketResponse.getCommonPrefixes().size()); @@ -314,8 +314,8 @@ public void listWithContinuationTokenFail() throws IOException { getBucket.setClient(ozoneClient); try { - getBucket.list("b1", "/", null, null, 2, "dir", null, "random", null, - null, null).getEntity(); + getBucket.get("b1", "/", null, null, 2, "dir", null, "random", null, + null, null, null).getEntity(); fail("listWithContinuationTokenFail"); } catch (OS3Exception ex) { Assert.assertEquals("random", ex.getResource()); @@ -336,8 +336,8 @@ public void testStartAfter() throws IOException, OS3Exception { getBucket.setClient(ozoneClient); ListObjectResponse getBucketResponse = - (ListObjectResponse) getBucket.list("b1", null, null, null, 1000, - null, null, null, null, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", null, null, null, 1000, + null, null, null, null, null, null, null).getEntity(); Assert.assertFalse(getBucketResponse.isTruncated()); Assert.assertEquals(5, getBucketResponse.getContents().size()); @@ -347,15 +347,15 @@ public void testStartAfter() throws IOException, OS3Exception { String startAfter = "dir0/file1"; getBucketResponse = - (ListObjectResponse) getBucket.list("b1", null, null, null, - 1000, null, null, null, startAfter, null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", null, null, null, + 1000, null, null, null, startAfter, null, null, null).getEntity(); Assert.assertFalse(getBucketResponse.isTruncated()); Assert.assertEquals(4, getBucketResponse.getContents().size()); getBucketResponse = - (ListObjectResponse) getBucket.list("b1", null, null, null, - 1000, null, null, null, "random", null, null).getEntity(); + (ListObjectResponse) getBucket.get("b1", null, null, null, + 1000, null, null, null, "random", null, null, null).getEntity(); Assert.assertFalse(getBucketResponse.isTruncated()); Assert.assertEquals(0, getBucketResponse.getContents().size()); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java index 738bb1d11447..dd155186e483 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java @@ -61,7 +61,7 @@ public void setup() throws Exception { public void testBucketFailWithAuthHeaderMissing() throws Exception { try { - bucketEndpoint.put(bucketName, null); + bucketEndpoint.put(bucketName, null, null, null); } catch (OS3Exception ex) { Assert.assertEquals(HTTP_NOT_FOUND, ex.getHttpCode()); Assert.assertEquals(MALFORMED_HEADER.getCode(), ex.getCode()); @@ -70,7 +70,7 @@ public void testBucketFailWithAuthHeaderMissing() throws Exception { @Test public void testBucketPut() throws Exception { - Response response = bucketEndpoint.put(bucketName, null); + Response response = bucketEndpoint.put(bucketName, null, null, null); assertEquals(200, response.getStatus()); assertNotNull(response.getLocation()); } @@ -78,7 +78,7 @@ public void testBucketPut() throws Exception { @Test public void testBucketFailWithInvalidHeader() throws Exception { try { - bucketEndpoint.put(bucketName, null); + bucketEndpoint.put(bucketName, null, null, null); } catch (OS3Exception ex) { Assert.assertEquals(HTTP_NOT_FOUND, ex.getHttpCode()); Assert.assertEquals(MALFORMED_HEADER.getCode(), ex.getCode()); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java index 1c5622e1baab..81b0718e6bae 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java @@ -32,16 +32,19 @@ import org.junit.Test; import org.mockito.Mockito; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.HttpHeaders; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; /** * Test operation permission check result. @@ -115,7 +118,7 @@ public void testCreateBucket() throws IOException { bucketEndpoint.setClient(client); try { - bucketEndpoint.put("bucketName", null); + bucketEndpoint.put("bucketName", null, null, null); Assert.fail("Should fail"); } catch (Exception e) { Assert.assertTrue(e instanceof OS3Exception); @@ -162,8 +165,8 @@ public void testListKey() throws IOException { bucketEndpoint.setClient(client); try { - bucketEndpoint.list("bucketName", null, null, null, 1000, - null, null, null, null, null, null); + bucketEndpoint.get("bucketName", null, null, null, 1000, + null, null, null, null, null, null, null); Assert.fail("Should fail"); } catch (Exception e) { Assert.assertTrue(e instanceof OS3Exception); @@ -191,6 +194,53 @@ public void testDeleteKeys() throws IOException, OS3Exception { response.getErrors().get(0).getCode().equals("PermissionDenied")); } + @Test + public void testGetAcl() throws Exception { + Mockito.when(objectStore.getVolume(anyString())).thenReturn(volume); + Mockito.when(objectStore.getS3Bucket(anyString())).thenReturn(bucket); + doThrow(exception).when(bucket).getAcls(); + + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Map parameterMap = Mockito.mock(Map.class); + when(servletRequest.getParameterMap()).thenReturn(parameterMap); + + when(parameterMap.containsKey("acl")).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + BucketEndpoint bucketEndpoint = new BucketEndpoint(); + bucketEndpoint.setClient(client); + try { + bucketEndpoint.get("bucketName", null, null, null, 1000, + null, null, null, null, null, "acl", null); + } catch (Exception e) { + Assert.assertTrue(e instanceof OS3Exception && + ((OS3Exception)e).getHttpCode() == HTTP_FORBIDDEN); + } + } + + @Test + public void testSetAcl() throws Exception { + Mockito.when(objectStore.getVolume(anyString())).thenReturn(volume); + Mockito.when(objectStore.getS3Bucket(anyString())).thenReturn(bucket); + doThrow(exception).when(bucket).addAcl(any()); + + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Map parameterMap = Mockito.mock(Map.class); + when(servletRequest.getParameterMap()).thenReturn(parameterMap); + + when(parameterMap.containsKey("acl")).thenReturn(true); + when(headers.getHeaderString(S3Acl.GRANT_READ)) + .thenReturn(S3Acl.ACLIdentityType.USER.getHeaderType() + "=root"); + BucketEndpoint bucketEndpoint = new BucketEndpoint(); + bucketEndpoint.setClient(client); + try { + bucketEndpoint.put("bucketName", "acl", headers, null); + } catch (Exception e) { + Assert.assertTrue(e instanceof OS3Exception && + ((OS3Exception)e).getHttpCode() == HTTP_FORBIDDEN); + } + } + /** * Object Endpoint. */ diff --git a/hadoop-ozone/s3gateway/src/test/resources/groupAccessControlList.xml b/hadoop-ozone/s3gateway/src/test/resources/groupAccessControlList.xml new file mode 100644 index 000000000000..a774583bfaee --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/resources/groupAccessControlList.xml @@ -0,0 +1,57 @@ + + + + + 852b113e7a2f25102679df27bb0ae12b3f85be6BucketOwnerCanonicalUserID + owner + + + + + 852b113e7a2f25102679df27bb0ae12b3f85be6BucketOwnerCanonicalUserID + owner + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + READ + + + + http://acs.amazonaws.com/groups/s3/LogDelivery + + WRITE + + + + xyz@amazon.com + + WRITE_ACP + + + + f30716ab7115dcb44a5ef76e9d74b8e20567f63TestAccountCanonicalUserID + + READ_ACP + + + \ No newline at end of file diff --git a/hadoop-ozone/s3gateway/src/test/resources/userAccessControlList.xml b/hadoop-ozone/s3gateway/src/test/resources/userAccessControlList.xml new file mode 100644 index 000000000000..45ce76e119d8 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/resources/userAccessControlList.xml @@ -0,0 +1,42 @@ + + + + + 852b113e7a2f25102679df27bb0ae12b3f85be6BucketOwnerCanonicalUserID + owner + + + + + 852b113e7a2f25102679df27bb0ae12b3f85be6BucketOwnerCanonicalUserID + + owner + + FULL_CONTROL + + + + f30716ab7115dcb44a5ef76e9d74b8e20567f63TestAccountCanonicalUserID + + READ_ACP + + + \ No newline at end of file