Index: hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java (revision 1351739) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java (working copy) @@ -32,6 +32,7 @@ import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.client.Append; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Increment; @@ -142,8 +143,12 @@ .append(toContextString()).toString(); } - public static AuthResult allow(String reason, User user, - Permission.Action action, byte[] table) { + public static AuthResult allow(String reason, User user, Permission.Action action, + byte[] table, byte[] family, byte[] qualifier) { + return new AuthResult(true, reason, user, action, table, family, qualifier); + } + + public static AuthResult allow(String reason, User user, Permission.Action action, byte[] table) { return new AuthResult(true, reason, user, action, table, null, null); } @@ -356,22 +361,26 @@ } /** - * Authorizes that the current user has any of the given permissions for the given table. + * Authorizes that the current user has any of the given permissions for the + * given table, column family and column qualifier. * @param tableName Table requested + * @param family Column family requested + * @param qualifier Column qualifier requested * @throws IOException if obtaining the current user fails * @throws AccessDeniedException if user has no authorization */ - private void requireTablePermission(byte[] tableName, Action... permissions) throws IOException { + private void requirePermission(byte[] tableName, byte[] family, byte[] qualifier, + Action... permissions) throws IOException { User user = getActiveUser(); AuthResult result = null; for (Action permission : permissions) { - if (authManager.authorize(user, tableName, (byte[]) null, permission)) { - result = AuthResult.allow("Table permission granted", user, permission, tableName); + if (authManager.authorize(user, tableName, null, null, permission)) { + result = AuthResult.allow("Table permission granted", user, permission, tableName, family, qualifier); break; } else { // rest of the world - result = AuthResult.deny("Insufficient permissions", user, permission, tableName); + result = AuthResult.deny("Insufficient permissions", user, permission, tableName, family, qualifier); } } logResult(result); @@ -540,10 +549,11 @@ HTableDescriptor desc, HRegionInfo[] regions) throws IOException {} @Override - public void preDeleteTable(ObserverContext c, - byte[] tableName) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preDeleteTable(ObserverContext c, byte[] tableName) + throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preDeleteTableHandler(ObserverContext c, byte[] tableName) throws IOException {} @@ -557,10 +567,11 @@ byte[] tableName) throws IOException {} @Override - public void preModifyTable(ObserverContext c, - byte[] tableName, HTableDescriptor htd) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preModifyTable(ObserverContext c, byte[] tableName, + HTableDescriptor htd) throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preModifyTableHandler(ObserverContext c, byte[] tableName, HTableDescriptor htd) throws IOException {} @@ -582,10 +593,11 @@ @Override - public void preAddColumn(ObserverContext c, - byte[] tableName, HColumnDescriptor column) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preAddColumn(ObserverContext c, byte[] tableName, + HColumnDescriptor column) throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preAddColumnHandler(ObserverContext c, byte[] tableName, HColumnDescriptor column) throws IOException {} @@ -597,10 +609,11 @@ byte[] tableName, HColumnDescriptor column) throws IOException {} @Override - public void preModifyColumn(ObserverContext c, - byte[] tableName, HColumnDescriptor descriptor) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preModifyColumn(ObserverContext c, byte[] tableName, + HColumnDescriptor descriptor) throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preModifyColumnHandler(ObserverContext c, byte[] tableName, HColumnDescriptor descriptor) throws IOException {} @@ -613,10 +626,11 @@ @Override - public void preDeleteColumn(ObserverContext c, - byte[] tableName, byte[] col) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preDeleteColumn(ObserverContext c, byte[] tableName, + byte[] col) throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preDeleteColumnHandler(ObserverContext c, byte[] tableName, byte[] col) throws IOException {} @@ -631,10 +645,11 @@ byte[] tableName, byte[] col) throws IOException {} @Override - public void preEnableTable(ObserverContext c, - byte[] tableName) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preEnableTable(ObserverContext c, byte[] tableName) + throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preEnableTableHandler(ObserverContext c, byte[] tableName) throws IOException {} @@ -646,10 +661,11 @@ byte[] tableName) throws IOException {} @Override - public void preDisableTable(ObserverContext c, - byte[] tableName) throws IOException { - requireTablePermission(tableName, Action.ADMIN, Action.CREATE); + public void preDisableTable(ObserverContext c, byte[] tableName) + throws IOException { + requirePermission(tableName, null, null, Action.ADMIN, Action.CREATE); } + @Override public void preDisableTableHandler(ObserverContext c, byte[] tableName) throws IOException {} @@ -762,18 +778,18 @@ @Override public void preFlush(ObserverContext e) throws IOException { - requireTablePermission(getTableName(e.getEnvironment()), Action.ADMIN); + requirePermission(getTableName(e.getEnvironment()), null, null, Action.ADMIN); } @Override public void preSplit(ObserverContext e) throws IOException { - requireTablePermission(getTableName(e.getEnvironment()), Action.ADMIN); + requirePermission(getTableName(e.getEnvironment()), null, null, Action.ADMIN); } @Override public InternalScanner preCompact(ObserverContext e, final Store store, final InternalScanner scanner) throws IOException { - requireTablePermission(getTableName(e.getEnvironment()), Action.ADMIN); + requirePermission(getTableName(e.getEnvironment()), null, null, Action.ADMIN); return scanner; } @@ -900,6 +916,13 @@ } @Override + public Result preAppend(ObserverContext c, Append append) + throws IOException { + requirePermission(TablePermission.Action.WRITE, c.getEnvironment(), append.getFamilyMap()); + return null; + } + + @Override public Result preIncrement(final ObserverContext c, final Increment increment) throws IOException { @@ -1003,25 +1026,23 @@ * This will be restricted by both client side and endpoint implementations. */ @Override - public void grant(UserPermission userPermission) - throws IOException { + public void grant(UserPermission perm) throws IOException { // verify it's only running at .acl. if (aclRegion) { if (LOG.isDebugEnabled()) { - LOG.debug("Received request to grant access permission " + userPermission.toString()); + LOG.debug("Received request to grant access permission " + perm.toString()); } - requirePermission(Permission.Action.ADMIN); + requirePermission(perm.getTable(), perm.getFamily(), perm.getQualifier(), Action.ADMIN); - AccessControlLists.addUserPermission(regionEnv.getConfiguration(), userPermission); + AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm); if (AUDITLOG.isTraceEnabled()) { // audit log should store permission changes in addition to auth results - AUDITLOG.trace("Granted permission " + userPermission.toString()); + AUDITLOG.trace("Granted permission " + perm.toString()); } } else { - throw new CoprocessorException(AccessController.class, "This method " + - "can only execute at " + - Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table."); + throw new CoprocessorException(AccessController.class, "This method " + + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table."); } } @@ -1035,25 +1056,23 @@ } @Override - public void revoke(UserPermission userPermission) - throws IOException{ + public void revoke(UserPermission perm) throws IOException { // only allowed to be called on _acl_ region if (aclRegion) { if (LOG.isDebugEnabled()) { - LOG.debug("Received request to revoke access permission " + userPermission.toString()); + LOG.debug("Received request to revoke access permission " + perm.toString()); } - requirePermission(Permission.Action.ADMIN); + requirePermission(perm.getTable(), perm.getFamily(), perm.getQualifier(), Action.ADMIN); - AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), userPermission); + AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm); if (AUDITLOG.isTraceEnabled()) { // audit log should record all permission changes - AUDITLOG.trace("Revoked permission " + userPermission.toString()); + AUDITLOG.trace("Revoked permission " + perm.toString()); } } else { - throw new CoprocessorException(AccessController.class, "This method " + - "can only execute at " + - Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table."); + throw new CoprocessorException(AccessController.class, "This method " + + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table."); } } @@ -1067,19 +1086,17 @@ } @Override - public List getUserPermissions(final byte[] tableName) - throws IOException { + public List getUserPermissions(final byte[] tableName) throws IOException { // only allowed to be called on _acl_ region if (aclRegion) { - requirePermission(Permission.Action.ADMIN); + requirePermission(tableName, null, null, Action.ADMIN); - List perms = AccessControlLists.getUserPermissions - (regionEnv.getConfiguration(), tableName); + List perms = AccessControlLists.getUserPermissions( + regionEnv.getConfiguration(), tableName); return perms; } else { - throw new CoprocessorException(AccessController.class, "This method " + - "can only execute at " + - Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table."); + throw new CoprocessorException(AccessController.class, "This method " + + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table."); } } Index: hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java (revision 1351739) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java (working copy) @@ -36,6 +36,7 @@ import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.LargeTests; import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.client.Append; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HBaseAdmin; @@ -591,7 +592,68 @@ } @Test + public void testAppend() throws Exception { + + PrivilegedExceptionAction appendAction = new PrivilegedExceptionAction() { + public Object run() throws Exception { + Append append = new Append(TEST_TABLE); + append.add(TEST_FAMILY, Bytes.toBytes("qualifier"), Bytes.toBytes("value")); + ACCESS_CONTROLLER.preAppend(ObserverContext.createAndPrepare(RCP_ENV, null), append); + return null; + } + }; + + verifyAllowed(appendAction, SUPERUSER, USER_ADMIN, USER_OWNER, USER_RW); + verifyDenied(appendAction, USER_CREATE, USER_RO, USER_NONE); + } + + @Test public void testGrantRevoke() throws Exception { + + PrivilegedExceptionAction grantAction = new PrivilegedExceptionAction() { + public Object run() throws Exception { + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + AccessControllerProtocol protocol = acl.coprocessorProxy(AccessControllerProtocol.class, + TEST_TABLE); + protocol.grant(new UserPermission(Bytes.toBytes(USER_RO.getShortName()), TEST_TABLE, + TEST_FAMILY, (byte[]) null, Action.READ)); + return null; + } + }; + + PrivilegedExceptionAction revokeAction = new PrivilegedExceptionAction() { + public Object run() throws Exception { + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + AccessControllerProtocol protocol = acl.coprocessorProxy(AccessControllerProtocol.class, + TEST_TABLE); + protocol.revoke(new UserPermission(Bytes.toBytes(USER_RO.getShortName()), TEST_TABLE, + TEST_FAMILY, (byte[]) null, Action.READ)); + return null; + } + }; + + PrivilegedExceptionAction getPermissionsAction = new PrivilegedExceptionAction() { + public Object run() throws Exception { + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + AccessControllerProtocol protocol = acl.coprocessorProxy(AccessControllerProtocol.class, + TEST_TABLE); + protocol.getUserPermissions(TEST_TABLE); + return null; + } + }; + + verifyAllowed(grantAction, SUPERUSER, USER_ADMIN, USER_OWNER); + verifyDenied(grantAction, USER_CREATE, USER_RW, USER_RO, USER_NONE); + + verifyAllowed(revokeAction, SUPERUSER, USER_ADMIN, USER_OWNER); + verifyDenied(revokeAction, USER_CREATE, USER_RW, USER_RO, USER_NONE); + + verifyAllowed(getPermissionsAction, SUPERUSER, USER_ADMIN, USER_OWNER); + verifyDenied(getPermissionsAction, USER_CREATE, USER_RW, USER_RO, USER_NONE); + } + + @Test + public void testPostGrantRevoke() throws Exception { final byte[] tableName = Bytes.toBytes("TempTable"); final byte[] family1 = Bytes.toBytes("f1"); final byte[] family2 = Bytes.toBytes("f2"); @@ -823,7 +885,7 @@ } @Test - public void testGrantRevokeAtQualifierLevel() throws Exception { + public void testPostGrantRevokeAtQualifierLevel() throws Exception { final byte[] tableName = Bytes.toBytes("testGrantRevokeAtQualifierLevel"); final byte[] family1 = Bytes.toBytes("f1"); final byte[] family2 = Bytes.toBytes("f2");