Repository: hbase Updated Branches: refs/heads/branch-1 964e17f7b -> 45357c078
HBASE-17472: Correct the semantic of permission grant Signed-off-by: zhangduo <zhang...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/hbase/repo Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/45357c07 Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/45357c07 Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/45357c07 Branch: refs/heads/branch-1 Commit: 45357c078d566ebd2f32594d73f7ad35feebe6dc Parents: 964e17f Author: huzheng <open...@gmail.com> Authored: Thu Feb 16 18:20:41 2017 +0800 Committer: zhangduo <zhang...@apache.org> Committed: Mon Feb 20 20:24:09 2017 +0800 ---------------------------------------------------------------------- .../hadoop/hbase/protobuf/ProtobufUtil.java | 25 ++-- .../hadoop/hbase/protobuf/RequestConverter.java | 28 ++-- .../security/access/AccessControlClient.java | 85 ++++++++++-- .../hbase/security/access/TablePermission.java | 25 ++-- .../protobuf/generated/AccessControlProtos.java | 139 +++++++++++++++---- .../src/main/protobuf/AccessControl.proto | 1 + .../security/access/AccessControlLists.java | 59 +++++--- .../hbase/security/access/AccessController.java | 5 +- .../hbase/security/access/SecureTestUtil.java | 70 ++++++++-- .../security/access/TestAccessController.java | 125 ++++++++++++++++- .../security/access/TestNamespaceCommands.java | 4 +- .../security/access/TestTablePermissions.java | 3 +- 12 files changed, 462 insertions(+), 107 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java ---------------------------------------------------------------------- diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java index fc8c030..bfdcfbd 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java @@ -2354,15 +2354,15 @@ public final class ProtobufUtil { */ public static void grant(RpcController controller, AccessControlService.BlockingInterface protocol, String userShortName, - Permission.Action... actions) throws ServiceException { + boolean mergeExistingPermissions, Permission.Action... actions) throws ServiceException { List<AccessControlProtos.Permission.Action> permActions = Lists.newArrayListWithCapacity(actions.length); for (Permission.Action a : actions) { permActions.add(ProtobufUtil.toPermissionAction(a)); } - AccessControlProtos.GrantRequest request = RequestConverter. - buildGrantRequest(userShortName, permActions.toArray( - new AccessControlProtos.Permission.Action[actions.length])); + AccessControlProtos.GrantRequest request = + RequestConverter.buildGrantRequest(userShortName, mergeExistingPermissions, + permActions.toArray(new AccessControlProtos.Permission.Action[actions.length])); protocol.grant(controller, request); } @@ -2382,15 +2382,16 @@ public final class ProtobufUtil { */ public static void grant(RpcController controller, AccessControlService.BlockingInterface protocol, String userShortName, TableName tableName, - byte[] f, byte[] q, Permission.Action... actions) throws ServiceException { + byte[] f, byte[] q, boolean mergeExistingPermissions, Permission.Action... actions) + throws ServiceException { List<AccessControlProtos.Permission.Action> permActions = Lists.newArrayListWithCapacity(actions.length); for (Permission.Action a : actions) { permActions.add(ProtobufUtil.toPermissionAction(a)); } - AccessControlProtos.GrantRequest request = RequestConverter. - buildGrantRequest(userShortName, tableName, f, q, permActions.toArray( - new AccessControlProtos.Permission.Action[actions.length])); + AccessControlProtos.GrantRequest request = + RequestConverter.buildGrantRequest(userShortName, tableName, f, q, mergeExistingPermissions, + permActions.toArray(new AccessControlProtos.Permission.Action[actions.length])); protocol.grant(controller, request); } @@ -2406,15 +2407,15 @@ public final class ProtobufUtil { */ public static void grant(RpcController controller, AccessControlService.BlockingInterface protocol, String userShortName, String namespace, - Permission.Action... actions) throws ServiceException { + boolean mergeExistingPermissions, Permission.Action... actions) throws ServiceException { List<AccessControlProtos.Permission.Action> permActions = Lists.newArrayListWithCapacity(actions.length); for (Permission.Action a : actions) { permActions.add(ProtobufUtil.toPermissionAction(a)); } - AccessControlProtos.GrantRequest request = RequestConverter. - buildGrantRequest(userShortName, namespace, permActions.toArray( - new AccessControlProtos.Permission.Action[actions.length])); + AccessControlProtos.GrantRequest request = + RequestConverter.buildGrantRequest(userShortName, namespace, mergeExistingPermissions, + permActions.toArray(new AccessControlProtos.Permission.Action[actions.length])); protocol.grant(controller, request); } http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java ---------------------------------------------------------------------- diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java index 5e4a163..8163130 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java @@ -1542,8 +1542,8 @@ public final class RequestConverter { * @param actions the permissions to be granted * @return A {@link AccessControlProtos} GrantRequest */ - public static AccessControlProtos.GrantRequest buildGrantRequest( - String username, AccessControlProtos.Permission.Action... actions) { + public static AccessControlProtos.GrantRequest buildGrantRequest(String username, + boolean mergeExistingPermissions, AccessControlProtos.Permission.Action... actions) { AccessControlProtos.Permission.Builder ret = AccessControlProtos.Permission.newBuilder(); AccessControlProtos.GlobalPermission.Builder permissionBuilder = @@ -1554,11 +1554,9 @@ public final class RequestConverter { ret.setType(AccessControlProtos.Permission.Type.Global) .setGlobalPermission(permissionBuilder); return AccessControlProtos.GrantRequest.newBuilder() - .setUserPermission( - AccessControlProtos.UserPermission.newBuilder() - .setUser(ByteString.copyFromUtf8(username)) - .setPermission(ret) - ).build(); + .setUserPermission(AccessControlProtos.UserPermission.newBuilder() + .setUser(ByteString.copyFromUtf8(username)).setPermission(ret)) + .setMergeExistingPermissions(mergeExistingPermissions).build(); } /** @@ -1572,7 +1570,7 @@ public final class RequestConverter { * @return A {@link AccessControlProtos} GrantRequest */ public static AccessControlProtos.GrantRequest buildGrantRequest( - String username, TableName tableName, byte[] family, byte[] qualifier, + String username, TableName tableName, byte[] family, byte[] qualifier, boolean mergeExistingPermissions, AccessControlProtos.Permission.Action... actions) { AccessControlProtos.Permission.Builder ret = AccessControlProtos.Permission.newBuilder(); @@ -1599,7 +1597,7 @@ public final class RequestConverter { AccessControlProtos.UserPermission.newBuilder() .setUser(ByteString.copyFromUtf8(username)) .setPermission(ret) - ).build(); + ).setMergeExistingPermissions(mergeExistingPermissions).build(); } /** @@ -1610,8 +1608,8 @@ public final class RequestConverter { * @param actions the permissions to be granted * @return A {@link AccessControlProtos} GrantRequest */ - public static AccessControlProtos.GrantRequest buildGrantRequest( - String username, String namespace, + public static AccessControlProtos.GrantRequest buildGrantRequest(String username, + String namespace, boolean mergeExistingPermissions, AccessControlProtos.Permission.Action... actions) { AccessControlProtos.Permission.Builder ret = AccessControlProtos.Permission.newBuilder(); @@ -1626,11 +1624,9 @@ public final class RequestConverter { ret.setType(AccessControlProtos.Permission.Type.Namespace) .setNamespacePermission(permissionBuilder); return AccessControlProtos.GrantRequest.newBuilder() - .setUserPermission( - AccessControlProtos.UserPermission.newBuilder() - .setUser(ByteString.copyFromUtf8(username)) - .setPermission(ret) - ).build(); + .setUserPermission(AccessControlProtos.UserPermission.newBuilder() + .setUser(ByteString.copyFromUtf8(username)).setPermission(ret)) + .setMergeExistingPermissions(mergeExistingPermissions).build(); } /** http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java ---------------------------------------------------------------------- diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java index 81dfae0..5799aaf 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java @@ -90,53 +90,110 @@ public class AccessControlClient { * @param userName * @param family * @param qual + * @param mergeExistingPermissions If set to false, later granted permissions will override + * previous granted permissions. otherwise, it'll merge with previous granted + * permissions. * @param actions * @throws Throwable */ - public static void grant(final Connection connection, final TableName tableName, - final String userName, final byte[] family, final byte[] qual, + public static void grant(Connection connection, final TableName tableName, final String userName, + final byte[] family, final byte[] qual, boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable { - PayloadCarryingRpcController controller - = ((ClusterConnection) connection).getRpcControllerFactory().newController(); + PayloadCarryingRpcController controller = + ((ClusterConnection) connection).getRpcControllerFactory().newController(); controller.setPriority(tableName); try (Table table = connection.getTable(ACL_TABLE_NAME)) { ProtobufUtil.grant(controller, getAccessControlServiceStub(table), userName, tableName, - family, qual, actions); + family, qual, mergeExistingPermissions, actions); } } /** + * Grants permission on the specified table for the specified user. If permissions for a specified + * user exists, later granted permissions will override previous granted permissions. + * @param connection + * @param tableName + * @param userName + * @param family + * @param qual + * @param actions + * @throws Throwable + */ + public static void grant(final Connection connection, final TableName tableName, + final String userName, final byte[] family, final byte[] qual, + final Permission.Action... actions) throws Throwable { + grant(connection, tableName, userName, family, qual, false, actions); + } + + /** * Grants permission on the specified namespace for the specified user. - * @param connection The Connection instance to use + * @param connection * @param namespace * @param userName + * @param mergeExistingPermissions If set to false, later granted permissions will override + * previous granted permissions. otherwise, it'll merge with previous granted + * permissions. * @param actions * @throws Throwable */ public static void grant(final Connection connection, final String namespace, - final String userName, final Permission.Action... actions) throws Throwable { - PayloadCarryingRpcController controller - = ((ClusterConnection) connection).getRpcControllerFactory().newController(); + final String userName, boolean mergeExistingPermissions, final Permission.Action... actions) + throws Throwable { + PayloadCarryingRpcController controller = + ((ClusterConnection) connection).getRpcControllerFactory().newController(); try (Table table = connection.getTable(ACL_TABLE_NAME)) { ProtobufUtil.grant(controller, getAccessControlServiceStub(table), userName, namespace, - actions); + mergeExistingPermissions, actions); } } /** + * Grants permission on the specified namespace for the specified user. If permissions for a + * specified user exists, later granted permissions will override previous granted permissions. * @param connection The Connection instance to use + * @param namespace + * @param userName + * @param actions + * @throws Throwable + */ + public static void grant(final Connection connection, final String namespace, + final String userName, final Permission.Action... actions) throws Throwable { + grant(connection, namespace, userName, false, actions); + } + + /** * Grant global permissions for the specified user. + * @param connection The Connection instance to use + * @param userName + * @param mergeExistingPermissions If set to false, later granted permissions will override + * previous granted permissions. otherwise, it'll merge with previous granted + * permissions. + * @param actions + * @throws Throwable */ public static void grant(final Connection connection, final String userName, - final Permission.Action... actions) throws Throwable { - PayloadCarryingRpcController controller - = ((ClusterConnection) connection).getRpcControllerFactory().newController(); + boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable { + PayloadCarryingRpcController controller = + ((ClusterConnection) connection).getRpcControllerFactory().newController(); try (Table table = connection.getTable(ACL_TABLE_NAME)) { - ProtobufUtil.grant(controller, getAccessControlServiceStub(table), userName, actions); + ProtobufUtil.grant(controller, getAccessControlServiceStub(table), userName, + mergeExistingPermissions, actions); } } + /** + * Grant global permissions for the specified user. + * @param connection The Connection instance to use + * @param userName + * @param actions + * @throws Throwable + */ + public static void grant(final Connection connection, final String userName, + final Permission.Action... actions) throws Throwable { + grant(connection, userName, false, actions); + } + public static boolean isAccessControllerRunning(final Connection connection) throws MasterNotRunningException, ZooKeeperConnectionException, IOException { try (Admin admin = connection.getAdmin()) { http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/TablePermission.java ---------------------------------------------------------------------- diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/TablePermission.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/TablePermission.java index 55eeebb..4fb7548 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/TablePermission.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/TablePermission.java @@ -311,6 +311,21 @@ public class TablePermission extends Permission { return super.implies(action); } + public boolean tableFieldsEqual(TablePermission other) { + if (!(((table == null && other.getTableName() == null) + || (table != null && table.equals(other.getTableName()))) + && ((family == null && other.getFamily() == null) + || Bytes.equals(family, other.getFamily())) + && ((qualifier == null && other.getQualifier() == null) + || Bytes.equals(qualifier, other.getQualifier())) + && ((namespace == null && other.getNamespace() == null) + || (namespace != null && namespace.equals(other.getNamespace()))))) { + return false; + } else { + return true; + } + } + @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NP_NULL_ON_SOME_PATH", justification="Passed on construction except on constructor not to be used") @@ -320,15 +335,7 @@ public class TablePermission extends Permission { } TablePermission other = (TablePermission)obj; - if (!(((table == null && other.getTableName() == null) || - (table != null && table.equals(other.getTableName()))) && - ((family == null && other.getFamily() == null) || - Bytes.equals(family, other.getFamily())) && - ((qualifier == null && other.getQualifier() == null) || - Bytes.equals(qualifier, other.getQualifier())) && - ((namespace == null && other.getNamespace() == null) || - (namespace != null && namespace.equals(other.getNamespace()))) - )) { + if(!this.tableFieldsEqual(other)){ return false; } http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AccessControlProtos.java ---------------------------------------------------------------------- diff --git a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AccessControlProtos.java b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AccessControlProtos.java index cd1dda1..b72e6e5 100644 --- a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AccessControlProtos.java +++ b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AccessControlProtos.java @@ -5569,6 +5569,16 @@ public final class AccessControlProtos { * <code>required .hbase.pb.UserPermission user_permission = 1;</code> */ org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.UserPermissionOrBuilder getUserPermissionOrBuilder(); + + // optional bool merge_existing_permissions = 2 [default = false]; + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + boolean hasMergeExistingPermissions(); + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + boolean getMergeExistingPermissions(); } /** * Protobuf type {@code hbase.pb.GrantRequest} @@ -5634,6 +5644,11 @@ public final class AccessControlProtos { bitField0_ |= 0x00000001; break; } + case 16: { + bitField0_ |= 0x00000002; + mergeExistingPermissions_ = input.readBool(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -5696,8 +5711,25 @@ public final class AccessControlProtos { return userPermission_; } + // optional bool merge_existing_permissions = 2 [default = false]; + public static final int MERGE_EXISTING_PERMISSIONS_FIELD_NUMBER = 2; + private boolean mergeExistingPermissions_; + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + public boolean hasMergeExistingPermissions() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + public boolean getMergeExistingPermissions() { + return mergeExistingPermissions_; + } + private void initFields() { userPermission_ = org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.UserPermission.getDefaultInstance(); + mergeExistingPermissions_ = false; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -5722,6 +5754,9 @@ public final class AccessControlProtos { if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeMessage(1, userPermission_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBool(2, mergeExistingPermissions_); + } getUnknownFields().writeTo(output); } @@ -5735,6 +5770,10 @@ public final class AccessControlProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(1, userPermission_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(2, mergeExistingPermissions_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -5763,6 +5802,11 @@ public final class AccessControlProtos { result = result && getUserPermission() .equals(other.getUserPermission()); } + result = result && (hasMergeExistingPermissions() == other.hasMergeExistingPermissions()); + if (hasMergeExistingPermissions()) { + result = result && (getMergeExistingPermissions() + == other.getMergeExistingPermissions()); + } result = result && getUnknownFields().equals(other.getUnknownFields()); return result; @@ -5780,6 +5824,10 @@ public final class AccessControlProtos { hash = (37 * hash) + USER_PERMISSION_FIELD_NUMBER; hash = (53 * hash) + getUserPermission().hashCode(); } + if (hasMergeExistingPermissions()) { + hash = (37 * hash) + MERGE_EXISTING_PERMISSIONS_FIELD_NUMBER; + hash = (53 * hash) + hashBoolean(getMergeExistingPermissions()); + } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -5896,6 +5944,8 @@ public final class AccessControlProtos { userPermissionBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000001); + mergeExistingPermissions_ = false; + bitField0_ = (bitField0_ & ~0x00000002); return this; } @@ -5932,6 +5982,10 @@ public final class AccessControlProtos { } else { result.userPermission_ = userPermissionBuilder_.build(); } + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.mergeExistingPermissions_ = mergeExistingPermissions_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -5951,6 +6005,9 @@ public final class AccessControlProtos { if (other.hasUserPermission()) { mergeUserPermission(other.getUserPermission()); } + if (other.hasMergeExistingPermissions()) { + setMergeExistingPermissions(other.getMergeExistingPermissions()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -6103,6 +6160,39 @@ public final class AccessControlProtos { return userPermissionBuilder_; } + // optional bool merge_existing_permissions = 2 [default = false]; + private boolean mergeExistingPermissions_ ; + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + public boolean hasMergeExistingPermissions() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + public boolean getMergeExistingPermissions() { + return mergeExistingPermissions_; + } + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + public Builder setMergeExistingPermissions(boolean value) { + bitField0_ |= 0x00000002; + mergeExistingPermissions_ = value; + onChanged(); + return this; + } + /** + * <code>optional bool merge_existing_permissions = 2 [default = false];</code> + */ + public Builder clearMergeExistingPermissions() { + bitField0_ = (bitField0_ & ~0x00000002); + mergeExistingPermissions_ = false; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:hbase.pb.GrantRequest) } @@ -10432,29 +10522,30 @@ public final class AccessControlProtos { "\0132-.hbase.pb.UsersAndPermissions.UserPer" + "missions\032J\n\017UserPermissions\022\014\n\004user\030\001 \002(" + "\014\022)\n\013permissions\030\002 \003(\0132\024.hbase.pb.Permis" + - "sion\"A\n\014GrantRequest\0221\n\017user_permission\030" + - "\001 \002(\0132\030.hbase.pb.UserPermission\"\017\n\rGrant" + - "Response\"B\n\rRevokeRequest\0221\n\017user_permis" + - "sion\030\001 \002(\0132\030.hbase.pb.UserPermission\"\020\n\016" + - "RevokeResponse\"\205\001\n\031GetUserPermissionsReq" + - "uest\022\'\n\004type\030\001 \001(\0162\031.hbase.pb.Permission" + - ".Type\022\'\n\ntable_name\030\002 \001(\0132\023.hbase.pb.Tab", - "leName\022\026\n\016namespace_name\030\003 \001(\014\"O\n\032GetUse" + - "rPermissionsResponse\0221\n\017user_permission\030" + - "\001 \003(\0132\030.hbase.pb.UserPermission\"C\n\027Check" + - "PermissionsRequest\022(\n\npermission\030\001 \003(\0132\024" + - ".hbase.pb.Permission\"\032\n\030CheckPermissions" + - "Response2\311\002\n\024AccessControlService\0228\n\005Gra" + - "nt\022\026.hbase.pb.GrantRequest\032\027.hbase.pb.Gr" + - "antResponse\022;\n\006Revoke\022\027.hbase.pb.RevokeR" + - "equest\032\030.hbase.pb.RevokeResponse\022_\n\022GetU" + - "serPermissions\022#.hbase.pb.GetUserPermiss", - "ionsRequest\032$.hbase.pb.GetUserPermission" + - "sResponse\022Y\n\020CheckPermissions\022!.hbase.pb" + - ".CheckPermissionsRequest\032\".hbase.pb.Chec" + - "kPermissionsResponseBI\n*org.apache.hadoo" + - "p.hbase.protobuf.generatedB\023AccessContro" + - "lProtosH\001\210\001\001\240\001\001" + "sion\"l\n\014GrantRequest\0221\n\017user_permission\030" + + "\001 \002(\0132\030.hbase.pb.UserPermission\022)\n\032merge" + + "_existing_permissions\030\002 \001(\010:\005false\"\017\n\rGr" + + "antResponse\"B\n\rRevokeRequest\0221\n\017user_per" + + "mission\030\001 \002(\0132\030.hbase.pb.UserPermission\"" + + "\020\n\016RevokeResponse\"\205\001\n\031GetUserPermissions" + + "Request\022\'\n\004type\030\001 \001(\0162\031.hbase.pb.Permiss", + "ion.Type\022\'\n\ntable_name\030\002 \001(\0132\023.hbase.pb." + + "TableName\022\026\n\016namespace_name\030\003 \001(\014\"O\n\032Get" + + "UserPermissionsResponse\0221\n\017user_permissi" + + "on\030\001 \003(\0132\030.hbase.pb.UserPermission\"C\n\027Ch" + + "eckPermissionsRequest\022(\n\npermission\030\001 \003(" + + "\0132\024.hbase.pb.Permission\"\032\n\030CheckPermissi" + + "onsResponse2\311\002\n\024AccessControlService\0228\n\005" + + "Grant\022\026.hbase.pb.GrantRequest\032\027.hbase.pb" + + ".GrantResponse\022;\n\006Revoke\022\027.hbase.pb.Revo" + + "keRequest\032\030.hbase.pb.RevokeResponse\022_\n\022G", + "etUserPermissions\022#.hbase.pb.GetUserPerm" + + "issionsRequest\032$.hbase.pb.GetUserPermiss" + + "ionsResponse\022Y\n\020CheckPermissions\022!.hbase" + + ".pb.CheckPermissionsRequest\032\".hbase.pb.C" + + "heckPermissionsResponseBI\n*org.apache.ha" + + "doop.hbase.protobuf.generatedB\023AccessCon" + + "trolProtosH\001\210\001\001\240\001\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -10508,7 +10599,7 @@ public final class AccessControlProtos { internal_static_hbase_pb_GrantRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_hbase_pb_GrantRequest_descriptor, - new java.lang.String[] { "UserPermission", }); + new java.lang.String[] { "UserPermission", "MergeExistingPermissions", }); internal_static_hbase_pb_GrantResponse_descriptor = getDescriptor().getMessageTypes().get(7); internal_static_hbase_pb_GrantResponse_fieldAccessorTable = new http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-protocol/src/main/protobuf/AccessControl.proto ---------------------------------------------------------------------- diff --git a/hbase-protocol/src/main/protobuf/AccessControl.proto b/hbase-protocol/src/main/protobuf/AccessControl.proto index e67540b..cc0d4a5 100644 --- a/hbase-protocol/src/main/protobuf/AccessControl.proto +++ b/hbase-protocol/src/main/protobuf/AccessControl.proto @@ -79,6 +79,7 @@ message UsersAndPermissions { message GrantRequest { required UserPermission user_permission = 1; + optional bool merge_existing_permissions = 2 [default = false]; } message GrantResponse { http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java index 50d575e..ff75958 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java @@ -139,15 +139,8 @@ public class AccessControlLists { master.createSystemTable(ACL_TABLEDESC); } - /** - * Stores a new user permission grant in the access control lists table. - * @param conf the configuration - * @param userPerm the details of the permission to be granted - * @param t acl table instance. It is closed upon method return - * @throws IOException in the case of an error accessing the metadata table - */ - static void addUserPermission(Configuration conf, UserPermission userPerm, Table t) - throws IOException { + static void addUserPermission(Configuration conf, UserPermission userPerm, Table t, + boolean mergeExistingPermissions) throws IOException { Permission.Action[] actions = userPerm.getActions(); byte[] rowKey = userPermissionRowKey(userPerm); Put p = new Put(rowKey); @@ -159,16 +152,38 @@ public class AccessControlLists { throw new IOException(msg); } - byte[] value = new byte[actions.length]; - for (int i = 0; i < actions.length; i++) { - value[i] = actions[i].code(); + Set<Permission.Action> actionSet = new TreeSet<Permission.Action>(); + if (mergeExistingPermissions) { + List<UserPermission> perms = getUserPermissions(conf, rowKey); + UserPermission currentPerm = null; + for (UserPermission perm : perms) { + if (Bytes.equals(perm.getUser(), userPerm.getUser()) + && ((userPerm.isGlobal() && ACL_TABLE_NAME.equals(perm.getTableName())) + || perm.tableFieldsEqual(userPerm))) { + currentPerm = perm; + break; + } + } + + if (currentPerm != null && currentPerm.getActions() != null) { + actionSet.addAll(Arrays.asList(currentPerm.getActions())); + } } + + // merge current action with new action. + actionSet.addAll(Arrays.asList(actions)); + + // serialize to byte array. + byte[] value = new byte[actionSet.size()]; + int index = 0; + for (Permission.Action action : actionSet) { + value[index++] = action.code(); + } + p.addImmutable(ACL_LIST_FAMILY, key, value); if (LOG.isDebugEnabled()) { - LOG.debug("Writing permission with rowKey "+ - Bytes.toString(rowKey)+" "+ - Bytes.toString(key)+": "+Bytes.toStringBinary(value) - ); + LOG.debug("Writing permission with rowKey " + Bytes.toString(rowKey) + " " + + Bytes.toString(key) + ": " + Bytes.toStringBinary(value)); } try { t.put(p); @@ -178,6 +193,18 @@ public class AccessControlLists { } /** + * Stores a new user permission grant in the access control lists table. + * @param conf the configuration + * @param userPerm the details of the permission to be granted + * @param t acl table instance. It is closed upon method return + * @throws IOException in the case of an error accessing the metadata table + */ + static void addUserPermission(Configuration conf, UserPermission userPerm, Table t) + throws IOException { + addUserPermission(conf, userPerm, t, false); + } + + /** * Removes a previously granted permission from the stored access control * lists. The {@link TablePermission} being removed must exactly match what * is stored -- no wildcard matching is attempted. Ie, if user "bob" has http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java index 7be4540..dd15c3c 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java @@ -2237,7 +2237,7 @@ public class AccessController extends BaseMasterAndRegionObserver @Override public void grant(RpcController controller, - AccessControlProtos.GrantRequest request, + final AccessControlProtos.GrantRequest request, RpcCallback<AccessControlProtos.GrantResponse> done) { final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission()); AccessControlProtos.GrantResponse response = null; @@ -2266,7 +2266,8 @@ public class AccessController extends BaseMasterAndRegionObserver @Override public Void run() throws Exception { AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm, - regionEnv.getTable(AccessControlLists.ACL_TABLE_NAME)); + regionEnv.getTable(AccessControlLists.ACL_TABLE_NAME), + request.getMergeExistingPermissions()); return null; } }); http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java index 22a9748..e208fae 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java @@ -372,7 +372,7 @@ public class SecureTestUtil { BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW); AccessControlService.BlockingInterface protocol = AccessControlService.newBlockingStub(service); - ProtobufUtil.grant(null, protocol, user, actions); + ProtobufUtil.grant(null, protocol, user, false, actions); } } return null; @@ -418,7 +418,7 @@ public class SecureTestUtil { BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW); AccessControlService.BlockingInterface protocol = AccessControlService.newBlockingStub(service); - ProtobufUtil.grant(null, protocol, user, namespace, actions); + ProtobufUtil.grant(null, protocol, user, namespace, false, actions); } } return null; @@ -440,7 +440,23 @@ public class SecureTestUtil { try { AccessControlClient.grant(connection, namespace, user, actions); } catch (Throwable t) { - t.printStackTrace(); + LOG.error("grant failed: ", t); + } + return null; + } + }); + } + + public static void grantOnNamespaceUsingAccessControlClient(final HBaseTestingUtility util, + final Connection connection, final String user, final String namespace, + final boolean mergeExistingPermissions, final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable<Void>() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.grant(connection, namespace, user, mergeExistingPermissions, actions); + } catch (Throwable t) { + LOG.error("grant failed: ", t); } return null; } @@ -461,7 +477,7 @@ public class SecureTestUtil { try { AccessControlClient.revoke(connection, namespace, user, actions); } catch (Throwable t) { - t.printStackTrace(); + LOG.error("revoke failed: ", t); } return null; } @@ -507,7 +523,7 @@ public class SecureTestUtil { BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW); AccessControlService.BlockingInterface protocol = AccessControlService.newBlockingStub(service); - ProtobufUtil.grant(null, protocol, user, table, family, qualifier, actions); + ProtobufUtil.grant(null, protocol, user, table, family, qualifier, false, actions); } } return null; @@ -529,13 +545,33 @@ public class SecureTestUtil { try { AccessControlClient.grant(connection, table, user, family, qualifier, actions); } catch (Throwable t) { - t.printStackTrace(); + LOG.error("grant failed: ", t); + } + return null; + } + }); + } + + public static void grantOnTableUsingAccessControlClient(final HBaseTestingUtility util, + final Connection connection, final String user, final TableName table, final byte[] family, + final byte[] qualifier, final boolean mergeExistingPermissions, + final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable<Void>() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.grant(connection, table, user, family, qualifier, + mergeExistingPermissions, actions); + } catch (Throwable t) { + LOG.error("grant failed: ", t); } return null; } }); } + + /** * Grant global permissions to the given user using AccessControlClient. Will wait until all * active AccessController instances have updated their permissions caches or will @@ -550,7 +586,23 @@ public class SecureTestUtil { try { AccessControlClient.grant(connection, user, actions); } catch (Throwable t) { - t.printStackTrace(); + LOG.error("grant failed: ", t); + } + return null; + } + }); + } + + public static void grantGlobalUsingAccessControlClient(final HBaseTestingUtility util, + final Connection connection, final String user, final boolean mergeExistingPermissions, + final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable<Void>() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.grant(connection, user, mergeExistingPermissions, actions); + } catch (Throwable t) { + LOG.error("grant failed: ", t); } return null; } @@ -595,7 +647,7 @@ public class SecureTestUtil { try { AccessControlClient.revoke(connection, table, user, family, qualifier, actions); } catch (Throwable t) { - t.printStackTrace(); + LOG.error("revoke failed: ", t); } return null; } @@ -616,7 +668,7 @@ public class SecureTestUtil { try { AccessControlClient.revoke(connection, user, actions); } catch (Throwable t) { - t.printStackTrace(); + LOG.error("revoke failed: ", t); } return null; } http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java index 1c1ab39..99112d3 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java @@ -58,7 +58,6 @@ import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotFoundException; -import org.apache.hadoop.hbase.Tag; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Append; import org.apache.hadoop.hbase.client.Connection; @@ -1192,7 +1191,7 @@ public class TestAccessController extends SecureTestUtil { BlockingRpcChannel service = acl.coprocessorService(TEST_TABLE.getName()); AccessControlService.BlockingInterface protocol = AccessControlService.newBlockingStub(service); - ProtobufUtil.grant(null, protocol, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null, + ProtobufUtil.grant(null, protocol, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null, false, Action.READ); } return null; @@ -2405,6 +2404,128 @@ public class TestAccessController extends SecureTestUtil { } } + @Test(timeout = 180000) + public void testAccessControlClientGrantRevokeWithMultiActions() throws Exception { + User testGrantRevoke = User.createUserForTesting(conf, "testGrantRevoke", new String[0]); + AccessTestAction getAction = new AccessTestAction() { + @Override + public Object run() throws Exception { + try (Connection conn = ConnectionFactory.createConnection(conf); + Table t = conn.getTable(TEST_TABLE)) { + return t.get(new Get(TEST_ROW)); + } + } + }; + + AccessTestAction putAction = new AccessTestAction() { + @Override + public Object run() throws Exception { + Put p = new Put(TEST_ROW); + p.addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes(1)); + try (Connection conn = ConnectionFactory.createConnection(conf); + Table t = conn.getTable(TEST_TABLE)) { + t.put(p); + return null; + } + } + }; + + verifyDenied(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + + // Grant global READ permissions to testGrantRevoke. + String userName = testGrantRevoke.getShortName(); + try { + grantGlobalUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, true, + Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. ", e); + } + verifyAllowed(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + + // Grant global READ permissions to testGrantRevoke. + try { + grantGlobalUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, true, + Permission.Action.WRITE); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. ", e); + } + verifyAllowed(getAction, testGrantRevoke); + verifyAllowed(putAction, testGrantRevoke); + + // Revoke global READ permission to testGrantRevoke. + try { + revokeGlobalUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, + Permission.Action.READ, Permission.Action.WRITE); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.revoke ", e); + } + verifyDenied(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + + // Grant table READ & WRITE permissions to testGrantRevoke + try { + grantOnTableUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, TEST_TABLE, + null, null, true, Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. ", e); + } + verifyAllowed(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + + // Grant table WRITE permissions to testGrantRevoke + try { + grantOnTableUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, TEST_TABLE, + null, null, true, Action.WRITE); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. ", e); + } + verifyAllowed(getAction, testGrantRevoke); + verifyAllowed(putAction, testGrantRevoke); + + // Revoke table READ & WRITE permission to testGrantRevoke. + try { + revokeFromTableUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, TEST_TABLE, + null, null, Permission.Action.READ, Permission.Action.WRITE); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.revoke ", e); + } + verifyDenied(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + + // Grant Namespace READ permissions to testGrantRevoke + String namespace = TEST_TABLE.getNamespaceAsString(); + try { + grantOnNamespaceUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, + namespace, true, Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. ", e); + } + verifyAllowed(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + + // Grant Namespace WRITE permissions to testGrantRevoke + try { + grantOnNamespaceUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, + namespace, true, Permission.Action.WRITE); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. ", e); + } + verifyAllowed(getAction, testGrantRevoke); + verifyAllowed(putAction, testGrantRevoke); + + // Revoke table READ & WRITE permission to testGrantRevoke. + try { + revokeFromNamespaceUsingAccessControlClient(TEST_UTIL, systemUserConnection, userName, + TEST_TABLE.getNamespaceAsString(), Permission.Action.READ, Permission.Action.WRITE); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.revoke ", e); + } + verifyDenied(getAction, testGrantRevoke); + verifyDenied(putAction, testGrantRevoke); + } + @Test (timeout=180000) public void testAccessControlClientGrantRevokeOnNamespace() throws Exception { // Create user for testing, who has no READ privileges by default. http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java index 377a3f6..a20ceeb 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java @@ -357,7 +357,7 @@ public class TestNamespaceCommands extends SecureTestUtil { acl.coprocessorService(HConstants.EMPTY_START_ROW); AccessControlService.BlockingInterface protocol = AccessControlService.newBlockingStub(service); - ProtobufUtil.grant(null, protocol, testUser, TEST_NAMESPACE, Action.WRITE); + ProtobufUtil.grant(null, protocol, testUser, TEST_NAMESPACE, false, Action.WRITE); } return null; } @@ -372,7 +372,7 @@ public class TestNamespaceCommands extends SecureTestUtil { AccessControlService.BlockingInterface protocol = AccessControlService.newBlockingStub(service); ProtobufUtil.grant(null, protocol, USER_GROUP_NS_ADMIN.getShortName(), - TEST_NAMESPACE, Action.READ); + TEST_NAMESPACE, false, Action.READ); } return null; } http://git-wip-us.apache.org/repos/asf/hbase/blob/45357c07/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java index b6d15e7..ba586f5 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java @@ -45,6 +45,7 @@ import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.security.access.Permission.Action; import org.apache.hadoop.hbase.testclassification.LargeTests; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; @@ -449,7 +450,7 @@ public class TestTablePermissions { assertEquals("Should have 1 permission for user3", 1, user3Perms.size()); assertEquals("user3 should have ADMIN, READ, CREATE permission", new Permission.Action[] { - Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE + Permission.Action.READ, Permission.Action.CREATE, Permission.Action.ADMIN, }, user3Perms.get(0).getActions()); }