diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt index c300eef1552..79439929cf0 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt +++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt @@ -20,3 +20,4 @@ HDFS-8644. OzoneHandler : Add volume handler. (Anu Engineer via Arpit Agarwal) + HDFS-8654. OzoneHandler : Add ACL support. (Anu Engineer via Arpit Agarwal) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneAcl.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneAcl.java new file mode 100644 index 00000000000..521c2776dbd --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneAcl.java @@ -0,0 +1,203 @@ +/* + * 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.web.request; + +import org.apache.hadoop.ozone.web.utils.OzoneConsts; + +import java.util.Objects; + +/** + * OzoneACL classes define bucket ACLs used in OZONE. + * + * ACLs in Ozone follow this pattern. + * • user:name:rw + * • group:name:rw + * • world::rw + */ +public class OzoneAcl { + private OzoneACLType type; + private String name; + private OzoneACLRights rights; + + /** + * Constructor for OzoneAcl. + * + * @param type - Type + * @param name - Name of user + * @param rights - Rights + */ + public OzoneAcl(OzoneACLType type, String name, OzoneACLRights rights) { + this.name = name; + this.rights = rights; + this.type = type; + } + + /** + * Parses an ACL string and returns the ACL object. + * + * @param acl - Acl String , Ex. user:anu:rw + * + * @return - Ozone ACLs + */ + public static OzoneAcl parseAcl(String acl) throws IllegalArgumentException { + if ((acl == null) || acl.isEmpty()) { + throw new IllegalArgumentException("ACLs cannot be null or empty"); + } + String[] parts = acl.trim().split(":"); + if (parts.length < 3) { + throw new IllegalArgumentException("ACLs are not in expected format"); + } + + OzoneACLType aclType = OzoneACLType.valueOf(parts[0].toUpperCase()); + OzoneACLRights rights = OzoneACLRights.getACLRight(parts[2].toLowerCase()); + + if (((aclType == OzoneACLType.USER) || (aclType == OzoneACLType.GROUP)) + && (parts[1].length() == 0)) { + throw new IllegalArgumentException("User or group name is required"); + } + + if ((aclType == OzoneACLType.WORLD) && (parts[1].length() != 0)) { + throw new IllegalArgumentException("Unexpected name part in world type"); + } + // TODO : Support sanitation of these user names by calling into + // userAuth Interface. + return new OzoneAcl(aclType, parts[1], rights); + } + + /** + * Returns a hash code value for the object. This method is + * supported for the benefit of hash tables. + * + * @return a hash code value for this object. + * + * @see Object#equals(Object) + * @see System#identityHashCode + */ + @Override + public int hashCode() { + return Objects.hash(this.getName(), this.getRights().toString(), + this.getType().toString()); + } + + /** + * Returns name. + * + * @return name + */ + public String getName() { + return name; + } + + /** + * Returns Rights. + * + * @return - Rights + */ + public OzoneACLRights getRights() { + return rights; + } + + /** + * Returns Type. + * + * @return type + */ + public OzoneACLType getType() { + return type; + } + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param obj the reference object with which to compare. + * + * @return {@code true} if this object is the same as the obj + * argument; {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + OzoneAcl otherAcl = (OzoneAcl) obj; + return otherAcl.getName().equals(this.getName()) && + otherAcl.getRights() == this.getRights() && + otherAcl.getType() == this.getType(); + } + + /** + * ACL types. + */ + public enum OzoneACLType { + USER(OzoneConsts.OZONE_ACL_USER_TYPE), + GROUP(OzoneConsts.OZONE_ACL_GROUP_TYPE), + WORLD(OzoneConsts.OZONE_ACL_WORLD_TYPE); + + /** + * String value for this Enum. + */ + private final String value; + + /** + * Init OzoneACLtypes enum. + * + * @param val String type for this enum. + */ + OzoneACLType(String val) { + value = val; + } + } + + /** + * ACL rights. + */ + public enum OzoneACLRights { + READ, WRITE, READ_WRITE; + + /** + * Returns the ACL rights based on passed in String. + * + * @param type ACL right string + * + * @return OzoneACLRights + */ + public static OzoneACLRights getACLRight(String type) { + if (type == null || type.isEmpty()) { + throw new IllegalArgumentException("ACL right cannot be empty"); + } + + switch (type) { + case OzoneConsts.OZONE_ACL_READ: + return OzoneACLRights.READ; + case OzoneConsts.OZONE_ACL_WRITE: + return OzoneACLRights.WRITE; + case OzoneConsts.OZONE_ACL_READ_WRITE: + case OzoneConsts.OZONE_ACL_WRITE_READ: + return OzoneACLRights.READ_WRITE; + default: + throw new IllegalArgumentException("ACL right is not recognized"); + } + + } + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java index 56c814ee12a..bdd7b6d8a89 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java @@ -34,6 +34,15 @@ public final class OzoneConsts { public static final int OZONE_MIN_BUCKET_NAME_LENGTH = 3; public static final int OZONE_MAX_BUCKET_NAME_LENGTH = 63; + public static final String OZONE_ACL_USER_TYPE = "user"; + public static final String OZONE_ACL_GROUP_TYPE = "group"; + public static final String OZONE_ACL_WORLD_TYPE = "world"; + + public static final String OZONE_ACL_READ = "r"; + public static final String OZONE_ACL_WRITE = "w"; + public static final String OZONE_ACL_READ_WRITE = "rw"; + public static final String OZONE_ACL_WRITE_READ = "wr"; + private OzoneConsts() { // Never Constructed } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestOzoneAcls.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestOzoneAcls.java new file mode 100644 index 00000000000..87e0a2889cc --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestOzoneAcls.java @@ -0,0 +1,139 @@ +/** + * 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.web; + +import org.apache.hadoop.ozone.web.request.OzoneAcl; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TestOzoneAcls { + + @Test + public void TestACLParse() { + HashMap testMatrix; + testMatrix = new HashMap<>(); + + testMatrix.put("user:bilbo:r", Boolean.TRUE); + testMatrix.put("user:bilbo:w", Boolean.TRUE); + testMatrix.put("user:bilbo:rw", Boolean.TRUE); + testMatrix.put("user:bilbo:wr", Boolean.TRUE); + testMatrix.put(" user:bilbo:wr ", Boolean.TRUE); + + + // ACLs makes no judgement on the quality of + // user names. it is for the userAuth interface + // to determine if a user name is really a name + testMatrix.put(" user:*:rw", Boolean.TRUE); + testMatrix.put(" user:~!:rw", Boolean.TRUE); + + + testMatrix.put("", Boolean.FALSE); + testMatrix.put(null, Boolean.FALSE); + testMatrix.put(" user:bilbo:", Boolean.FALSE); + testMatrix.put(" user:bilbo:rx", Boolean.FALSE); + testMatrix.put(" user:bilbo:mk", Boolean.FALSE); + testMatrix.put(" user::rw", Boolean.FALSE); + testMatrix.put("user11:bilbo:rw", Boolean.FALSE); + testMatrix.put(" user:::rw", Boolean.FALSE); + + testMatrix.put(" group:hobbit:r", Boolean.TRUE); + testMatrix.put(" group:hobbit:w", Boolean.TRUE); + testMatrix.put(" group:hobbit:rw", Boolean.TRUE); + testMatrix.put(" group:hobbit:wr", Boolean.TRUE); + testMatrix.put(" group:*:rw", Boolean.TRUE); + testMatrix.put(" group:~!:rw", Boolean.TRUE); + + testMatrix.put(" group:hobbit:", Boolean.FALSE); + testMatrix.put(" group:hobbit:rx", Boolean.FALSE); + testMatrix.put(" group:hobbit:mk", Boolean.FALSE); + testMatrix.put(" group::", Boolean.FALSE); + testMatrix.put(" group::rw", Boolean.FALSE); + testMatrix.put(" group22:hobbit:", Boolean.FALSE); + testMatrix.put(" group:::rw", Boolean.FALSE); + + testMatrix.put("JUNK group:hobbit:r", Boolean.FALSE); + testMatrix.put("JUNK group:hobbit:w", Boolean.FALSE); + testMatrix.put("JUNK group:hobbit:rw", Boolean.FALSE); + testMatrix.put("JUNK group:hobbit:wr", Boolean.FALSE); + testMatrix.put("JUNK group:*:rw", Boolean.FALSE); + testMatrix.put("JUNK group:~!:rw", Boolean.FALSE); + + testMatrix.put(" world::r", Boolean.TRUE); + testMatrix.put(" world::w", Boolean.TRUE); + testMatrix.put(" world::rw", Boolean.TRUE); + testMatrix.put(" world::wr", Boolean.TRUE); + + testMatrix.put(" world:bilbo:w", Boolean.FALSE); + testMatrix.put(" world:bilbo:rw", Boolean.FALSE); + + Set keys = testMatrix.keySet(); + for (String key : keys) { + if (testMatrix.get(key)) { + OzoneAcl.parseAcl(key); + } else { + try { + OzoneAcl.parseAcl(key); + // should never get here since parseAcl will throw + fail("An exception was expected but did not happen."); + } catch (IllegalArgumentException e) { + // nothing to do + } + } + } + } + + @Test + public void TestACLValues() { + OzoneAcl acl = OzoneAcl.parseAcl("user:bilbo:rw"); + assertEquals(acl.getName(), "bilbo"); + assertEquals(acl.getRights(), OzoneAcl.OzoneACLRights.READ_WRITE); + assertEquals(acl.getType(), OzoneAcl.OzoneACLType.USER); + + acl = OzoneAcl.parseAcl("user:bilbo:wr"); + assertEquals(acl.getName(), "bilbo"); + assertEquals(acl.getRights(), OzoneAcl.OzoneACLRights.READ_WRITE); + assertEquals(acl.getType(), OzoneAcl.OzoneACLType.USER); + + acl = OzoneAcl.parseAcl("user:bilbo:r"); + assertEquals(acl.getName(), "bilbo"); + assertEquals(acl.getRights(), OzoneAcl.OzoneACLRights.READ); + assertEquals(acl.getType(), OzoneAcl.OzoneACLType.USER); + + acl = OzoneAcl.parseAcl("user:bilbo:w"); + assertEquals(acl.getName(), "bilbo"); + assertEquals(acl.getRights(), OzoneAcl.OzoneACLRights.WRITE); + assertEquals(acl.getType(), OzoneAcl.OzoneACLType.USER); + + acl = OzoneAcl.parseAcl("group:hobbit:wr"); + assertEquals(acl.getName(), "hobbit"); + assertEquals(acl.getRights(), OzoneAcl.OzoneACLRights.READ_WRITE); + assertEquals(acl.getType(), OzoneAcl.OzoneACLType.GROUP); + + acl = OzoneAcl.parseAcl("world::wr"); + assertEquals(acl.getName(), ""); + assertEquals(acl.getRights(), OzoneAcl.OzoneACLRights.READ_WRITE); + assertEquals(acl.getType(), OzoneAcl.OzoneACLType.WORLD); + } + +}