HBASE-10970 [AccessController] Issues with covering cell permission checks. (Anoop)

git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1589593 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
anoopsamjohn 2014-04-24 05:46:02 +00:00
parent b733180663
commit 26db1dc318
3 changed files with 441 additions and 70 deletions

View File

@ -22,6 +22,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
@ -476,9 +477,31 @@ public class AccessController extends BaseRegionObserver
} }
} }
private void requireCoveringPermission(String request, RegionCoprocessorEnvironment e, private enum OpType {
byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, GET_CLOSEST_ROW_BEFORE("getClosestRowBefore"),
boolean allVersions, Action...actions) throws IOException { PUT("put"),
DELETE("delete"),
CHECK_AND_PUT("checkAndPut"),
CHECK_AND_DELETE("checkAndDelete"),
INCREMENT_COLUMN_VALUE("incrementColumnValue"),
APPEND("append"),
INCREMENT("increment");
private String type;
private OpType(String type) {
this.type = type;
}
@Override
public String toString() {
return type;
}
}
private void requireCoveringPermission(OpType request, RegionCoprocessorEnvironment e,
byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, Action... actions)
throws IOException {
User user = getActiveUser(); User user = getActiveUser();
// First check table or CF level permissions, if they grant access we can // First check table or CF level permissions, if they grant access we can
@ -490,7 +513,7 @@ public class AccessController extends BaseRegionObserver
// HBASE-7123. // HBASE-7123.
AuthResult results[] = new AuthResult[actions.length]; AuthResult results[] = new AuthResult[actions.length];
for (int i = 0; i < actions.length; i++) { for (int i = 0; i < actions.length; i++) {
results[i] = permissionGranted(request, user, actions[i], e, familyMap); results[i] = permissionGranted(request.type, user, actions[i], e, familyMap);
if (!results[i].isAllowed()) { if (!results[i].isAllowed()) {
if (LOG.isTraceEnabled()) { if (LOG.isTraceEnabled()) {
LOG.trace("Got " + results[i] + ", added to cellCheckActions"); LOG.trace("Got " + results[i] + ", added to cellCheckActions");
@ -512,15 +535,23 @@ public class AccessController extends BaseRegionObserver
// Table or CF permissions do not allow, enumerate the covered KVs. We // Table or CF permissions do not allow, enumerate the covered KVs. We
// can stop at the first which does not grant access. // can stop at the first which does not grant access.
int cellsChecked = 0; int cellsChecked = 0;
opTs = opTs != HConstants.LATEST_TIMESTAMP ? opTs : 0;
long latestCellTs = 0; long latestCellTs = 0;
if (canPersistCellACLs) { if (canPersistCellACLs) {
Get get = new Get(row); Get get = new Get(row);
if (allVersions) { // Only in case of Put/Delete op, consider TS within cell (if set for individual cells).
// When every cell, within a Mutation, can be linked with diff TS we can not rely on only one
// version. We have to get every cell version and check its TS against the TS asked for in
// Mutation and skip those Cells which is outside this Mutation TS.In case of Put, we have to
// consider only one such passing cell. In case of Delete we have to consider all the cell
// versions under this passing version. When Delete Mutation contains columns which are a
// version delete just consider only one version for those column cells.
boolean considerCellTs = (request == OpType.PUT || request == OpType.DELETE);
if (considerCellTs) {
get.setMaxVersions(); get.setMaxVersions();
} else { } else {
get.setMaxVersions(1); get.setMaxVersions(1);
} }
boolean diffCellTsFromOpTs = false;
for (Map.Entry<byte[], ? extends Collection<?>> entry: familyMap.entrySet()) { for (Map.Entry<byte[], ? extends Collection<?>> entry: familyMap.entrySet()) {
byte[] col = entry.getKey(); byte[] col = entry.getKey();
// TODO: HBASE-7114 could possibly unify the collection type in family // TODO: HBASE-7114 could possibly unify the collection type in family
@ -548,8 +579,10 @@ public class AccessController extends BaseRegionObserver
} else { } else {
get.addColumn(col, CellUtil.cloneQualifier(cell)); get.addColumn(col, CellUtil.cloneQualifier(cell));
} }
if (cell.getTimestamp() != HConstants.LATEST_TIMESTAMP) { if (considerCellTs) {
latestCellTs = Math.max(latestCellTs, cell.getTimestamp()); long cellTs = cell.getTimestamp();
latestCellTs = Math.max(latestCellTs, cellTs);
diffCellTsFromOpTs = diffCellTsFromOpTs || (opTs != cellTs);
} }
} }
} }
@ -565,15 +598,35 @@ public class AccessController extends BaseRegionObserver
// the upper bound of a timerange is exclusive yet we need to examine // the upper bound of a timerange is exclusive yet we need to examine
// any cells found there inclusively. // any cells found there inclusively.
long latestTs = Math.max(opTs, latestCellTs); long latestTs = Math.max(opTs, latestCellTs);
if (latestTs == 0) { if (latestTs == 0 || latestTs == HConstants.LATEST_TIMESTAMP) {
latestTs = EnvironmentEdgeManager.currentTimeMillis(); latestTs = EnvironmentEdgeManager.currentTimeMillis();
} }
get.setTimeRange(0, latestTs + 1); get.setTimeRange(0, latestTs + 1);
// In case of Put operation we set to read all versions. This was done to consider the case
// where columns are added with TS other than the Mutation TS. But normally this wont be the
// case with Put. There no need to get all versions but get latest version only.
if (!diffCellTsFromOpTs && request == OpType.PUT) {
get.setMaxVersions(1);
}
if (LOG.isTraceEnabled()) { if (LOG.isTraceEnabled()) {
LOG.trace("Scanning for cells with " + get); LOG.trace("Scanning for cells with " + get);
} }
// This Map is identical to familyMap. The key is a BR rather than byte[].
// It will be easy to do gets over this new Map as we can create get keys over the Cell cf by
// new SimpleByteRange(cell.familyArray, cell.familyOffset, cell.familyLen)
Map<ByteRange, List<Cell>> familyMap1 = new HashMap<ByteRange, List<Cell>>();
for (Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) {
if (entry.getValue() instanceof List) {
familyMap1.put(new SimpleByteRange(entry.getKey()), (List<Cell>) entry.getValue());
}
}
RegionScanner scanner = getRegion(e).getScanner(new Scan(get)); RegionScanner scanner = getRegion(e).getScanner(new Scan(get));
List<Cell> cells = Lists.newArrayList(); List<Cell> cells = Lists.newArrayList();
Cell prevCell = null;
ByteRange curFam = new SimpleByteRange();
boolean curColAllVersions = (request == OpType.DELETE);
long curColCheckTs = opTs;
boolean foundColumn = false;
try { try {
boolean more = false; boolean more = false;
do { do {
@ -584,10 +637,43 @@ public class AccessController extends BaseRegionObserver
if (LOG.isTraceEnabled()) { if (LOG.isTraceEnabled()) {
LOG.trace("Found cell " + cell); LOG.trace("Found cell " + cell);
} }
boolean colChange = prevCell == null || !CellUtil.matchingColumn(prevCell, cell);
if (colChange) foundColumn = false;
prevCell = cell;
if (!curColAllVersions && foundColumn) {
continue;
}
if (colChange && considerCellTs) {
curFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
List<Cell> cols = familyMap1.get(curFam);
for (Cell col : cols) {
// null/empty qualifier is used to denote a Family delete. The TS and delete type
// associated with this is applicable for all columns within the family. That is
// why the below (col.getQualifierLength() == 0) check.
if ((col.getQualifierLength() == 0 && request == OpType.DELETE)
|| CellUtil.matchingQualifier(cell, col)) {
byte type = col.getTypeByte();
if (considerCellTs)
curColCheckTs = col.getTimestamp();
// For a Delete op we pass allVersions as true. When a Delete Mutation contains
// a version delete for a column no need to check all the covering cells within
// that column. Check all versions when Type is DeleteColumn or DeleteFamily
// One version delete types are Delete/DeleteFamilyVersion
curColAllVersions = (KeyValue.Type.DeleteColumn.getCode() == type)
|| (KeyValue.Type.DeleteFamily.getCode() == type);
break;
}
}
}
if (cell.getTimestamp() > curColCheckTs) {
// Just ignore this cell. This is not a covering cell.
continue;
}
foundColumn = true;
for (Action action: cellCheckActions) { for (Action action: cellCheckActions) {
// Are there permissions for this user for the cell? // Are there permissions for this user for the cell?
if (!authManager.authorize(user, getTableName(e), cell, false, action)) { if (!authManager.authorize(user, getTableName(e), cell, false, action)) {
AuthResult authResult = AuthResult.deny(request, "Insufficient permissions", AuthResult authResult = AuthResult.deny(request.type, "Insufficient permissions",
user, action, getTableName(e), CellUtil.cloneFamily(cell), user, action, getTableName(e), CellUtil.cloneFamily(cell),
CellUtil.cloneQualifier(cell)); CellUtil.cloneQualifier(cell));
logResult(authResult); logResult(authResult);
@ -612,7 +698,7 @@ public class AccessController extends BaseRegionObserver
if (LOG.isTraceEnabled()) { if (LOG.isTraceEnabled()) {
LOG.trace("No cells found with scan"); LOG.trace("No cells found with scan");
} }
AuthResult authResult = AuthResult.deny(request, "Insufficient permissions", AuthResult authResult = AuthResult.deny(request.type, "Insufficient permissions",
user, cellCheckActions.get(0), getTableName(e), familyMap); user, cellCheckActions.get(0), getTableName(e), familyMap);
logResult(authResult); logResult(authResult);
throw new AccessDeniedException("Insufficient permissions " + throw new AccessDeniedException("Insufficient permissions " +
@ -623,7 +709,7 @@ public class AccessController extends BaseRegionObserver
// thousands of fine grained decisions with providing detail. // thousands of fine grained decisions with providing detail.
for (byte[] family: familyMap.keySet()) { for (byte[] family: familyMap.keySet()) {
for (Action action: actions) { for (Action action: actions) {
logResult(AuthResult.allow(request, "Permission granted", user, action, logResult(AuthResult.allow(request.type, "Permission granted", user, action,
getTableName(e), family, null)); getTableName(e), family, null));
} }
} }
@ -1166,8 +1252,8 @@ public class AccessController extends BaseRegionObserver
final byte [] row, final byte [] family, final Result result) final byte [] row, final byte [] family, final Result result)
throws IOException { throws IOException {
assert family != null; assert family != null;
requireCoveringPermission("getClosestRowBefore", c.getEnvironment(), row, requireCoveringPermission(OpType.GET_CLOSEST_ROW_BEFORE, c.getEnvironment(), row,
makeFamilyMap(family, null), HConstants.LATEST_TIMESTAMP, false, Permission.Action.READ); makeFamilyMap(family, null), HConstants.LATEST_TIMESTAMP, Permission.Action.READ);
} }
@Override @Override
@ -1193,8 +1279,8 @@ public class AccessController extends BaseRegionObserver
// HBase value. A new ACL in a new Put applies to that Put. It doesn't // HBase value. A new ACL in a new Put applies to that Put. It doesn't
// change the ACL of any previous Put. This allows simple evolution of // change the ACL of any previous Put. This allows simple evolution of
// security policy over time without requiring expensive updates. // security policy over time without requiring expensive updates.
requireCoveringPermission("put", c.getEnvironment(), put.getRow(), requireCoveringPermission(OpType.PUT, c.getEnvironment(), put.getRow(),
put.getFamilyCellMap(), put.getTimeStamp(), false, Permission.Action.WRITE); put.getFamilyCellMap(), put.getTimeStamp(), Permission.Action.WRITE);
byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
if (bytes != null) { if (bytes != null) {
if (canPersistCellACLs) { if (canPersistCellACLs) {
@ -1226,8 +1312,8 @@ public class AccessController extends BaseRegionObserver
// compaction could remove them. If the user doesn't have permission to // compaction could remove them. If the user doesn't have permission to
// overwrite any of the visible versions ('visible' defined as not covered // overwrite any of the visible versions ('visible' defined as not covered
// by a tombstone already) then we have to disallow this operation. // by a tombstone already) then we have to disallow this operation.
requireCoveringPermission("delete", c.getEnvironment(), delete.getRow(), requireCoveringPermission(OpType.DELETE, c.getEnvironment(), delete.getRow(),
delete.getFamilyCellMap(), delete.getTimeStamp(), true, Action.WRITE); delete.getFamilyCellMap(), delete.getTimeStamp(), Action.WRITE);
} }
@Override @Override
@ -1246,9 +1332,8 @@ public class AccessController extends BaseRegionObserver
final ByteArrayComparable comparator, final Put put, final ByteArrayComparable comparator, final Put put,
final boolean result) throws IOException { final boolean result) throws IOException {
// Require READ and WRITE permissions on the table, CF, and KV to update // Require READ and WRITE permissions on the table, CF, and KV to update
requireCoveringPermission("checkAndPut", c.getEnvironment(), row, requireCoveringPermission(OpType.CHECK_AND_PUT, c.getEnvironment(), row,
makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false, makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, Action.READ, Action.WRITE);
Action.READ, Action.WRITE);
byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
if (bytes != null) { if (bytes != null) {
if (canPersistCellACLs) { if (canPersistCellACLs) {
@ -1273,9 +1358,8 @@ public class AccessController extends BaseRegionObserver
} }
// Require READ and WRITE permissions on the table, CF, and the KV covered // Require READ and WRITE permissions on the table, CF, and the KV covered
// by the delete // by the delete
requireCoveringPermission("checkAndDelete", c.getEnvironment(), row, requireCoveringPermission(OpType.CHECK_AND_DELETE, c.getEnvironment(), row,
makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false, makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, Action.READ, Action.WRITE);
Action.READ, Action.WRITE);
return result; return result;
} }
@ -1286,9 +1370,8 @@ public class AccessController extends BaseRegionObserver
throws IOException { throws IOException {
// Require WRITE permission to the table, CF, and the KV to be replaced by the // Require WRITE permission to the table, CF, and the KV to be replaced by the
// incremented value // incremented value
requireCoveringPermission("incrementColumnValue", c.getEnvironment(), row, requireCoveringPermission(OpType.INCREMENT_COLUMN_VALUE, c.getEnvironment(), row,
makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false, makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, Action.WRITE);
Action.WRITE);
return -1; return -1;
} }
@ -1296,9 +1379,8 @@ public class AccessController extends BaseRegionObserver
public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append) public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
throws IOException { throws IOException {
// Require WRITE permission to the table, CF, and the KV to be appended // Require WRITE permission to the table, CF, and the KV to be appended
requireCoveringPermission("append", c.getEnvironment(), append.getRow(), requireCoveringPermission(OpType.APPEND, c.getEnvironment(), append.getRow(),
append.getFamilyCellMap(), append.getTimeStamp(), false, append.getFamilyCellMap(), HConstants.LATEST_TIMESTAMP, Action.WRITE);
Action.WRITE);
byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
if (bytes != null) { if (bytes != null) {
if (canPersistCellACLs) { if (canPersistCellACLs) {
@ -1316,9 +1398,8 @@ public class AccessController extends BaseRegionObserver
throws IOException { throws IOException {
// Require WRITE permission to the table, CF, and the KV to be replaced by // Require WRITE permission to the table, CF, and the KV to be replaced by
// the incremented value // the incremented value
requireCoveringPermission("increment", c.getEnvironment(), increment.getRow(), requireCoveringPermission(OpType.INCREMENT, c.getEnvironment(), increment.getRow(),
increment.getFamilyCellMap(), increment.getTimeRange().getMax(), false, increment.getFamilyCellMap(), increment.getTimeRange().getMax(), Action.WRITE);
Action.WRITE);
byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
if (bytes != null) { if (bytes != null) {
if (canPersistCellACLs) { if (canPersistCellACLs) {

View File

@ -169,7 +169,7 @@ public class SecureTestUtil {
if (results != null && results.isEmpty()) { if (results != null && results.isEmpty()) {
fail("Empty non null results from action for user '" + user.getShortName() + "'"); fail("Empty non null results from action for user '" + user.getShortName() + "'");
} }
assertEquals(results.size(), count); assertEquals(count, results.size());
} }
} catch (AccessDeniedException ade) { } catch (AccessDeniedException ade) {
fail("Expected action to pass for user '" + user.getShortName() + "' but was denied"); fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");

View File

@ -37,6 +37,7 @@ import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Increment;
import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost; import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
@ -69,7 +70,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
@Rule @Rule
public TestTableName TEST_TABLE = new TestTableName(); public TestTableName TEST_TABLE = new TestTableName();
private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static final byte[] TEST_FAMILY = Bytes.toBytes("f1"); private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1");
private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2");
private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest"); private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest");
private static final byte[] TEST_Q1 = Bytes.toBytes("q1"); private static final byte[] TEST_Q1 = Bytes.toBytes("q1");
private static final byte[] TEST_Q2 = Bytes.toBytes("q2"); private static final byte[] TEST_Q2 = Bytes.toBytes("q2");
@ -130,7 +132,11 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
// Create the test table (owner added to the _acl_ table) // Create the test table (owner added to the _acl_ table)
HBaseAdmin admin = TEST_UTIL.getHBaseAdmin(); HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName());
HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY1);
hcd.setMaxVersions(4);
htd.setOwner(USER_OWNER);
htd.addFamily(hcd);
hcd = new HColumnDescriptor(TEST_FAMILY2);
hcd.setMaxVersions(4); hcd.setMaxVersions(4);
htd.setOwner(USER_OWNER); htd.setOwner(USER_OWNER);
htd.addFamily(hcd); htd.addFamily(hcd);
@ -149,20 +155,20 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
try { try {
Put p; Put p;
// with ro ACL // with ro ACL
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE));
t.put(p); t.put(p);
// with ro ACL // with ro ACL
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ));
t.put(p); t.put(p);
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE));
t.put(p); t.put(p);
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ));
t.put(p); t.put(p);
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE));
t.put(p); t.put(p);
} finally { } finally {
@ -213,13 +219,13 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
Put p; Put p;
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE));
t.put(p); t.put(p);
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ));
t.put(p); t.put(p);
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.WRITE));
t.put(p); t.put(p);
} finally { } finally {
@ -253,15 +259,15 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
try { try {
// with rw ACL for "user1" // with rw ACL for "user1"
Put p = new Put(TEST_ROW1); Put p = new Put(TEST_ROW1);
p.add(TEST_FAMILY, TEST_Q1, ZERO); p.add(TEST_FAMILY1, TEST_Q1, ZERO);
p.add(TEST_FAMILY, TEST_Q2, ZERO); p.add(TEST_FAMILY1, TEST_Q2, ZERO);
p.setACL(user1.getShortName(), new Permission(Permission.Action.READ, p.setACL(user1.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
t.put(p); t.put(p);
// with rw ACL for "user1" // with rw ACL for "user1"
p = new Put(TEST_ROW2); p = new Put(TEST_ROW2);
p.add(TEST_FAMILY, TEST_Q1, ZERO); p.add(TEST_FAMILY1, TEST_Q1, ZERO);
p.add(TEST_FAMILY, TEST_Q2, ZERO); p.add(TEST_FAMILY1, TEST_Q2, ZERO);
p.setACL(user1.getShortName(), new Permission(Permission.Action.READ, p.setACL(user1.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
t.put(p); t.put(p);
@ -279,8 +285,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
try { try {
// with rw ACL for "user1" and "user2" // with rw ACL for "user1" and "user2"
Put p = new Put(TEST_ROW1); Put p = new Put(TEST_ROW1);
p.add(TEST_FAMILY, TEST_Q1, ZERO); p.add(TEST_FAMILY1, TEST_Q1, ZERO);
p.add(TEST_FAMILY, TEST_Q2, ZERO); p.add(TEST_FAMILY1, TEST_Q2, ZERO);
Map<String, Permission> perms = new HashMap<String, Permission>(); Map<String, Permission> perms = new HashMap<String, Permission>();
perms.put(user1.getShortName(), new Permission(Permission.Action.READ, perms.put(user1.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
@ -290,8 +296,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
t.put(p); t.put(p);
// with rw ACL for "user1" and "user2" // with rw ACL for "user1" and "user2"
p = new Put(TEST_ROW2); p = new Put(TEST_ROW2);
p.add(TEST_FAMILY, TEST_Q1, ZERO); p.add(TEST_FAMILY1, TEST_Q1, ZERO);
p.add(TEST_FAMILY, TEST_Q2, ZERO); p.add(TEST_FAMILY1, TEST_Q2, ZERO);
p.setACL(perms); p.setACL(perms);
t.put(p); t.put(p);
} finally { } finally {
@ -309,8 +315,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
Delete d = new Delete(TEST_ROW1); Delete d = new Delete(TEST_ROW1);
d.deleteColumns(TEST_FAMILY, TEST_Q1); d.deleteColumns(TEST_FAMILY1, TEST_Q1);
d.deleteColumns(TEST_FAMILY, TEST_Q2); d.deleteColumns(TEST_FAMILY1, TEST_Q2);
t.delete(d); t.delete(d);
} finally { } finally {
t.close(); t.close();
@ -326,8 +332,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
Delete d = new Delete(TEST_ROW2); Delete d = new Delete(TEST_ROW2);
d.deleteColumns(TEST_FAMILY, TEST_Q1); d.deleteColumns(TEST_FAMILY1, TEST_Q1);
d.deleteColumns(TEST_FAMILY, TEST_Q2); d.deleteColumns(TEST_FAMILY1, TEST_Q2);
t.delete(d); t.delete(d);
fail("user2 should not be allowed to delete the row"); fail("user2 should not be allowed to delete the row");
} catch (Exception e) { } catch (Exception e) {
@ -345,7 +351,7 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
Delete d = new Delete(TEST_ROW2); Delete d = new Delete(TEST_ROW2);
d.deleteFamily(TEST_FAMILY); d.deleteFamily(TEST_FAMILY1);
t.delete(d); t.delete(d);
} finally { } finally {
t.close(); t.close();
@ -366,13 +372,13 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
// Store read only ACL at a future time // Store read only ACL at a future time
Put p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, Put p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1,
EnvironmentEdgeManager.currentTimeMillis() + 1000000, EnvironmentEdgeManager.currentTimeMillis() + 1000000,
ZERO); ZERO);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ)); p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ));
t.put(p); t.put(p);
// Store a read write ACL without a timestamp, server will use current time // Store a read write ACL without a timestamp, server will use current time
p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q2, ONE); p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q2, ONE);
p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ, p.setACL(USER_OTHER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
t.put(p); t.put(p);
@ -388,7 +394,7 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
AccessTestAction getQ1 = new AccessTestAction() { AccessTestAction getQ1 = new AccessTestAction() {
@Override @Override
public Object run() throws Exception { public Object run() throws Exception {
Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1); Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1);
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
return t.get(get).listCells(); return t.get(get).listCells();
@ -401,7 +407,7 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
AccessTestAction getQ2 = new AccessTestAction() { AccessTestAction getQ2 = new AccessTestAction() {
@Override @Override
public Object run() throws Exception { public Object run() throws Exception {
Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2); Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q2);
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
return t.get(get).listCells(); return t.get(get).listCells();
@ -421,7 +427,7 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
AccessTestAction deleteFamily = new AccessTestAction() { AccessTestAction deleteFamily = new AccessTestAction() {
@Override @Override
public Object run() throws Exception { public Object run() throws Exception {
Delete delete = new Delete(TEST_ROW).deleteFamily(TEST_FAMILY); Delete delete = new Delete(TEST_ROW).deleteFamily(TEST_FAMILY1);
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
t.delete(delete); t.delete(delete);
@ -452,8 +458,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
try { try {
// This version (TS = 123) with rw ACL for USER_OTHER and USER_OTHER2 // This version (TS = 123) with rw ACL for USER_OTHER and USER_OTHER2
Put p = new Put(TEST_ROW); Put p = new Put(TEST_ROW);
p.add(TEST_FAMILY, TEST_Q1, 123L, ZERO); p.add(TEST_FAMILY1, TEST_Q1, 123L, ZERO);
p.add(TEST_FAMILY, TEST_Q2, 123L, ZERO); p.add(TEST_FAMILY1, TEST_Q2, 123L, ZERO);
Map<String, Permission> perms = new HashMap<String, Permission>(); Map<String, Permission> perms = new HashMap<String, Permission>();
perms.put(USER_OTHER.getShortName(), new Permission(Permission.Action.READ, perms.put(USER_OTHER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
@ -464,8 +470,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
// This version (TS = 125) with rw ACL for USER_OTHER // This version (TS = 125) with rw ACL for USER_OTHER
p = new Put(TEST_ROW); p = new Put(TEST_ROW);
p.add(TEST_FAMILY, TEST_Q1, 125L, ONE); p.add(TEST_FAMILY1, TEST_Q1, 125L, ONE);
p.add(TEST_FAMILY, TEST_Q2, 125L, ONE); p.add(TEST_FAMILY1, TEST_Q2, 125L, ONE);
perms = new HashMap<String, Permission>(); perms = new HashMap<String, Permission>();
perms.put(USER_OTHER.getShortName(), new Permission(Permission.Action.READ, perms.put(USER_OTHER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
@ -474,8 +480,8 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
// This version (TS = 127) with rw ACL for USER_OTHER // This version (TS = 127) with rw ACL for USER_OTHER
p = new Put(TEST_ROW); p = new Put(TEST_ROW);
p.add(TEST_FAMILY, TEST_Q1, 127L, TWO); p.add(TEST_FAMILY1, TEST_Q1, 127L, TWO);
p.add(TEST_FAMILY, TEST_Q2, 127L, TWO); p.add(TEST_FAMILY1, TEST_Q2, 127L, TWO);
perms = new HashMap<String, Permission>(); perms = new HashMap<String, Permission>();
perms.put(USER_OTHER.getShortName(), new Permission(Permission.Action.READ, perms.put(USER_OTHER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE)); Permission.Action.WRITE));
@ -496,7 +502,7 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
Delete d = new Delete(TEST_ROW, 124L); Delete d = new Delete(TEST_ROW, 124L);
d.deleteColumns(TEST_FAMILY, TEST_Q1); d.deleteColumns(TEST_FAMILY1, TEST_Q1);
t.delete(d); t.delete(d);
} finally { } finally {
t.close(); t.close();
@ -512,7 +518,7 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
HTable t = new HTable(conf, TEST_TABLE.getTableName()); HTable t = new HTable(conf, TEST_TABLE.getTableName());
try { try {
Delete d = new Delete(TEST_ROW); Delete d = new Delete(TEST_ROW);
d.deleteColumns(TEST_FAMILY, TEST_Q2, 124L); d.deleteColumns(TEST_FAMILY1, TEST_Q2, 124L);
t.delete(d); t.delete(d);
} finally { } finally {
t.close(); t.close();
@ -522,6 +528,290 @@ public class TestCellACLWithMultipleVersions extends SecureTestUtil {
}); });
} }
@Test
public void testCellPermissionsWithDeleteExactVersion() throws Exception {
final byte[] TEST_ROW1 = Bytes.toBytes("r1");
final byte[] TEST_Q1 = Bytes.toBytes("q1");
final byte[] TEST_Q2 = Bytes.toBytes("q2");
final byte[] ZERO = Bytes.toBytes(0L);
final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
verifyAllowed(new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Map<String, Permission> permsU1andOwner = new HashMap<String, Permission>();
permsU1andOwner.put(user1.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
permsU1andOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
Map<String, Permission> permsU2andOwner = new HashMap<String, Permission>();
permsU2andOwner.put(user2.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
permsU2andOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
Put p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY2, TEST_Q1, 123, ZERO);
p.add(TEST_FAMILY2, TEST_Q2, 123, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY2, TEST_Q1, 125, ZERO);
p.add(TEST_FAMILY2, TEST_Q2, 125, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY2, TEST_Q1, 129, ZERO);
p.add(TEST_FAMILY2, TEST_Q2, 129, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
} finally {
t.close();
}
return null;
}
}, USER_OWNER);
// user1 should be allowed to delete TEST_ROW1 as he is having write permission on both
// versions of the cells
user1.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Delete d = new Delete(TEST_ROW1);
d.deleteColumn(TEST_FAMILY1, TEST_Q1, 123);
d.deleteColumn(TEST_FAMILY1, TEST_Q2);
d.deleteFamilyVersion(TEST_FAMILY2, 125);
t.delete(d);
} finally {
t.close();
}
return null;
}
});
user2.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Delete d = new Delete(TEST_ROW1, 127);
d.deleteColumns(TEST_FAMILY1, TEST_Q1);
d.deleteColumns(TEST_FAMILY1, TEST_Q2);
d.deleteFamily(TEST_FAMILY2, 129);
t.delete(d);
fail("user2 can not do the delete");
} catch (Exception e) {
} finally {
t.close();
}
return null;
}
});
}
@Test
public void testCellPermissionsForIncrementWithMultipleVersions() throws Exception {
final byte[] TEST_ROW1 = Bytes.toBytes("r1");
final byte[] TEST_Q1 = Bytes.toBytes("q1");
final byte[] TEST_Q2 = Bytes.toBytes("q2");
final byte[] ZERO = Bytes.toBytes(0L);
final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
verifyAllowed(new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Map<String, Permission> permsU1andOwner = new HashMap<String, Permission>();
permsU1andOwner.put(user1.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
permsU1andOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
Map<String, Permission> permsU2andOwner = new HashMap<String, Permission>();
permsU2andOwner.put(user2.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
permsU2andOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
Put p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
} finally {
t.close();
}
return null;
}
}, USER_OWNER);
// Increment considers the TimeRange set on it.
user1.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Increment inc = new Increment(TEST_ROW1);
inc.setTimeRange(0, 123);
inc.addColumn(TEST_FAMILY1, TEST_Q1, 2L);
t.increment(inc);
t.incrementColumnValue(TEST_ROW1, TEST_FAMILY1, TEST_Q2, 1L);
} finally {
t.close();
}
return null;
}
});
user2.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Increment inc = new Increment(TEST_ROW1);
inc.setTimeRange(0, 127);
inc.addColumn(TEST_FAMILY1, TEST_Q2, 2L);
t.increment(inc);
fail();
} catch (Exception e) {
} finally {
t.close();
}
return null;
}
});
}
@Test
public void testCellPermissionsForPutWithMultipleVersions() throws Exception {
final byte[] TEST_ROW1 = Bytes.toBytes("r1");
final byte[] TEST_Q1 = Bytes.toBytes("q1");
final byte[] TEST_Q2 = Bytes.toBytes("q2");
final byte[] ZERO = Bytes.toBytes(0L);
final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
verifyAllowed(new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Map<String, Permission> permsU1andOwner = new HashMap<String, Permission>();
permsU1andOwner.put(user1.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
permsU1andOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
Map<String, Permission> permsU2andOwner = new HashMap<String, Permission>();
permsU2andOwner.put(user2.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
permsU2andOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
Put p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
p.setACL(permsU2andOwner);
t.put(p);
p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
p.setACL(permsU1andOwner);
t.put(p);
} finally {
t.close();
}
return null;
}
}, USER_OWNER);
// new Put with TEST_Q1 column having TS=125. This covers old cell with TS 123 and user1 is
// having RW permission. While TEST_Q2 is with latest TS and so it covers old cell with TS 127.
// User1 is having RW permission on that too.
user1.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Put p = new Put(TEST_ROW1);
p.add(TEST_FAMILY1, TEST_Q1, 125, ZERO);
p.add(TEST_FAMILY1, TEST_Q2, ZERO);
p.setACL(user2.getShortName(), new Permission(Permission.Action.READ,
Permission.Action.WRITE));
t.put(p);
} finally {
t.close();
}
return null;
}
});
// Should be denied.
user2.runAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
HTable t = new HTable(conf, TEST_TABLE.getTableName());
try {
Put p = new Put(TEST_ROW1);
// column Q1 covers version at 123 fr which user2 do not have permission
p.add(TEST_FAMILY1, TEST_Q1, 124, ZERO);
p.add(TEST_FAMILY1, TEST_Q2, ZERO);
t.put(p);
fail();
} catch (Exception e) {
} finally {
t.close();
}
return null;
}
});
}
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
// Clean the _acl_ table // Clean the _acl_ table