001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security.access; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.fail; 022 023import java.io.IOException; 024import java.security.PrivilegedExceptionAction; 025import java.util.HashMap; 026import java.util.Map; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.AuthUtil; 029import org.apache.hadoop.hbase.Coprocessor; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseTestingUtility; 032import org.apache.hadoop.hbase.HColumnDescriptor; 033import org.apache.hadoop.hbase.HTableDescriptor; 034import org.apache.hadoop.hbase.TableNameTestRule; 035import org.apache.hadoop.hbase.TableNotFoundException; 036import org.apache.hadoop.hbase.client.Admin; 037import org.apache.hadoop.hbase.client.Connection; 038import org.apache.hadoop.hbase.client.ConnectionFactory; 039import org.apache.hadoop.hbase.client.Delete; 040import org.apache.hadoop.hbase.client.Get; 041import org.apache.hadoop.hbase.client.Increment; 042import org.apache.hadoop.hbase.client.Put; 043import org.apache.hadoop.hbase.client.Table; 044import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 045import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; 046import org.apache.hadoop.hbase.security.User; 047import org.apache.hadoop.hbase.security.access.Permission.Action; 048import org.apache.hadoop.hbase.testclassification.MediumTests; 049import org.apache.hadoop.hbase.testclassification.SecurityTests; 050import org.apache.hadoop.hbase.util.Bytes; 051import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 052import org.apache.hadoop.hbase.util.Threads; 053import org.junit.After; 054import org.junit.AfterClass; 055import org.junit.Before; 056import org.junit.BeforeClass; 057import org.junit.ClassRule; 058import org.junit.Rule; 059import org.junit.Test; 060import org.junit.experimental.categories.Category; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064@Category({ SecurityTests.class, MediumTests.class }) 065public class TestCellACLWithMultipleVersions extends SecureTestUtil { 066 067 @ClassRule 068 public static final HBaseClassTestRule CLASS_RULE = 069 HBaseClassTestRule.forClass(TestCellACLWithMultipleVersions.class); 070 071 private static final Logger LOG = LoggerFactory.getLogger(TestCellACLWithMultipleVersions.class); 072 073 @Rule 074 public TableNameTestRule testTable = new TableNameTestRule(); 075 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 076 private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1"); 077 private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2"); 078 private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest"); 079 private static final byte[] TEST_Q1 = Bytes.toBytes("q1"); 080 private static final byte[] TEST_Q2 = Bytes.toBytes("q2"); 081 private static final byte[] ZERO = Bytes.toBytes(0L); 082 private static final byte[] ONE = Bytes.toBytes(1L); 083 private static final byte[] TWO = Bytes.toBytes(2L); 084 085 private static Configuration conf; 086 087 private static final String GROUP = "group"; 088 private static User GROUP_USER; 089 private static User USER_OWNER; 090 private static User USER_OTHER; 091 private static User USER_OTHER2; 092 093 private static String[] usersAndGroups; 094 095 @BeforeClass 096 public static void setupBeforeClass() throws Exception { 097 // setup configuration 098 conf = TEST_UTIL.getConfiguration(); 099 // Enable security 100 enableSecurity(conf); 101 // Verify enableSecurity sets up what we require 102 verifyConfiguration(conf); 103 104 // We expect 0.98 cell ACL semantics 105 conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false); 106 107 TEST_UTIL.startMiniCluster(); 108 MasterCoprocessorHost cpHost = 109 TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterCoprocessorHost(); 110 cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); 111 AccessController ac = cpHost.findCoprocessor(AccessController.class); 112 cpHost.createEnvironment(ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); 113 RegionServerCoprocessorHost rsHost = 114 TEST_UTIL.getMiniHBaseCluster().getRegionServer(0).getRegionServerCoprocessorHost(); 115 rsHost.createEnvironment(ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); 116 117 // Wait for the ACL table to become available 118 TEST_UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME); 119 120 // create a set of test users 121 USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]); 122 USER_OTHER = User.createUserForTesting(conf, "other", new String[0]); 123 USER_OTHER2 = User.createUserForTesting(conf, "other2", new String[0]); 124 GROUP_USER = User.createUserForTesting(conf, "group_user", new String[] { GROUP }); 125 126 usersAndGroups = new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) }; 127 } 128 129 @AfterClass 130 public static void tearDownAfterClass() throws Exception { 131 TEST_UTIL.shutdownMiniCluster(); 132 } 133 134 @Before 135 public void setUp() throws Exception { 136 HTableDescriptor htd = new HTableDescriptor(testTable.getTableName()); 137 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY1); 138 hcd.setMaxVersions(4); 139 htd.setOwner(USER_OWNER); 140 htd.addFamily(hcd); 141 hcd = new HColumnDescriptor(TEST_FAMILY2); 142 hcd.setMaxVersions(4); 143 htd.setOwner(USER_OWNER); 144 htd.addFamily(hcd); 145 // Create the test table (owner added to the _acl_ table) 146 try (Connection connection = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())) { 147 try (Admin admin = connection.getAdmin()) { 148 admin.createTable(htd, new byte[][] { Bytes.toBytes("s") }); 149 } 150 } 151 TEST_UTIL.waitTableEnabled(testTable.getTableName()); 152 LOG.info("Sleeping a second because of HBASE-12581"); 153 Threads.sleep(1000); 154 } 155 156 @Test 157 public void testCellPermissionwithVersions() throws Exception { 158 // store two sets of values, one store with a cell level ACL, and one 159 // without 160 final Map<String, Permission> writePerms = prepareCellPermissions(usersAndGroups, Action.WRITE); 161 final Map<String, Permission> readPerms = prepareCellPermissions(usersAndGroups, Action.READ); 162 verifyAllowed(new AccessTestAction() { 163 @Override 164 public Object run() throws Exception { 165 try (Connection connection = ConnectionFactory.createConnection(conf); 166 Table t = connection.getTable(testTable.getTableName())) { 167 Put p; 168 // with ro ACL 169 long now = EnvironmentEdgeManager.currentTime(); 170 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now, ZERO); 171 p.setACL(writePerms); 172 t.put(p); 173 // with ro ACL 174 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 1, ZERO); 175 p.setACL(readPerms); 176 t.put(p); 177 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 2, ZERO); 178 p.setACL(writePerms); 179 t.put(p); 180 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 3, ZERO); 181 p.setACL(readPerms); 182 t.put(p); 183 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 4, ZERO); 184 p.setACL(writePerms); 185 t.put(p); 186 } 187 return null; 188 } 189 }, USER_OWNER); 190 191 /* ---- Gets ---- */ 192 193 AccessTestAction getQ1 = new AccessTestAction() { 194 @Override 195 public Object run() throws Exception { 196 Get get = new Get(TEST_ROW); 197 get.setMaxVersions(10); 198 try (Connection connection = ConnectionFactory.createConnection(conf); 199 Table t = connection.getTable(testTable.getTableName())) { 200 return t.get(get).listCells(); 201 } 202 } 203 }; 204 205 AccessTestAction get2 = new AccessTestAction() { 206 @Override 207 public Object run() throws Exception { 208 Get get = new Get(TEST_ROW); 209 get.setMaxVersions(10); 210 try (Connection connection = ConnectionFactory.createConnection(conf); 211 Table t = connection.getTable(testTable.getTableName())) { 212 return t.get(get).listCells(); 213 } 214 } 215 }; 216 // Confirm special read access set at cell level 217 218 verifyAllowed(GROUP_USER, getQ1, 2); 219 verifyAllowed(USER_OTHER, getQ1, 2); 220 221 // store two sets of values, one store with a cell level ACL, and one 222 // without 223 verifyAllowed(new AccessTestAction() { 224 @Override 225 public Object run() throws Exception { 226 try (Connection connection = ConnectionFactory.createConnection(conf); 227 Table t = connection.getTable(testTable.getTableName())) { 228 Put p; 229 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 230 p.setACL(writePerms); 231 t.put(p); 232 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 233 p.setACL(readPerms); 234 t.put(p); 235 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 236 p.setACL(writePerms); 237 t.put(p); 238 } 239 return null; 240 } 241 }, USER_OWNER); 242 // Confirm special read access set at cell level 243 244 verifyAllowed(USER_OTHER, get2, 1); 245 verifyAllowed(GROUP_USER, get2, 1); 246 } 247 248 private Map<String, Permission> prepareCellPermissions(String[] users, Action... action) { 249 Map<String, Permission> perms = new HashMap<>(2); 250 for (String user : users) { 251 perms.put(user, new Permission(action)); 252 } 253 return perms; 254 } 255 256 @Test 257 public void testCellPermissionsWithDeleteMutipleVersions() throws Exception { 258 // table/column/qualifier level permissions 259 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 260 final byte[] TEST_ROW2 = Bytes.toBytes("r2"); 261 final byte[] TEST_Q1 = Bytes.toBytes("q1"); 262 final byte[] TEST_Q2 = Bytes.toBytes("q2"); 263 final byte[] ZERO = Bytes.toBytes(0L); 264 265 // additional test user 266 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 267 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 268 269 verifyAllowed(new AccessTestAction() { 270 @Override 271 public Object run() throws Exception { 272 try (Connection connection = ConnectionFactory.createConnection(conf)) { 273 try (Table t = connection.getTable(testTable.getTableName())) { 274 // with rw ACL for "user1" 275 Put p = new Put(TEST_ROW1); 276 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 277 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 278 p.setACL(user1.getShortName(), 279 new Permission(Permission.Action.READ, Permission.Action.WRITE)); 280 t.put(p); 281 // with rw ACL for "user1" 282 p = new Put(TEST_ROW2); 283 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 284 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 285 p.setACL(user1.getShortName(), 286 new Permission(Permission.Action.READ, Permission.Action.WRITE)); 287 t.put(p); 288 } 289 } 290 return null; 291 } 292 }, USER_OWNER); 293 294 verifyAllowed(new AccessTestAction() { 295 @Override 296 public Object run() throws Exception { 297 try (Connection connection = ConnectionFactory.createConnection(conf)) { 298 try (Table t = connection.getTable(testTable.getTableName())) { 299 // with rw ACL for "user1", "user2" and "@group" 300 Put p = new Put(TEST_ROW1); 301 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 302 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 303 Map<String, Permission> perms = 304 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(), 305 AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE); 306 p.setACL(perms); 307 t.put(p); 308 // with rw ACL for "user1", "user2" and "@group" 309 p = new Put(TEST_ROW2); 310 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 311 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 312 p.setACL(perms); 313 t.put(p); 314 } 315 } 316 return null; 317 } 318 }, user1); 319 320 // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both 321 // versions of the cells 322 user1.runAs(new PrivilegedExceptionAction<Void>() { 323 @Override 324 public Void run() throws Exception { 325 try (Connection connection = ConnectionFactory.createConnection(conf)) { 326 try (Table t = connection.getTable(testTable.getTableName())) { 327 Delete d = new Delete(TEST_ROW1); 328 d.addColumns(TEST_FAMILY1, TEST_Q1); 329 d.addColumns(TEST_FAMILY1, TEST_Q2); 330 t.delete(d); 331 } 332 } 333 return null; 334 } 335 }); 336 // user2 should not be allowed to delete TEST_ROW2 as he is having write permission only on one 337 // version of the cells. 338 verifyUserDeniedForDeleteMultipleVersions(user2, TEST_ROW2, TEST_Q1, TEST_Q2); 339 340 // GROUP_USER should not be allowed to delete TEST_ROW2 as he is having write permission only on 341 // one version of the cells. 342 verifyUserDeniedForDeleteMultipleVersions(GROUP_USER, TEST_ROW2, TEST_Q1, TEST_Q2); 343 344 // user1 should be allowed to delete the cf. (All data under cf for a row) 345 user1.runAs(new PrivilegedExceptionAction<Void>() { 346 @Override 347 public Void run() throws Exception { 348 try (Connection connection = ConnectionFactory.createConnection(conf)) { 349 try (Table t = connection.getTable(testTable.getTableName())) { 350 Delete d = new Delete(TEST_ROW2); 351 d.addFamily(TEST_FAMILY1); 352 t.delete(d); 353 } 354 } 355 return null; 356 } 357 }); 358 } 359 360 private void verifyUserDeniedForDeleteMultipleVersions(final User user, final byte[] row, 361 final byte[] q1, final byte[] q2) throws IOException, InterruptedException { 362 user.runAs(new PrivilegedExceptionAction<Void>() { 363 @Override 364 public Void run() throws Exception { 365 try (Connection connection = ConnectionFactory.createConnection(conf)) { 366 try (Table t = connection.getTable(testTable.getTableName())) { 367 Delete d = new Delete(row); 368 d.addColumns(TEST_FAMILY1, q1); 369 d.addColumns(TEST_FAMILY1, q2); 370 t.delete(d); 371 fail(user.getShortName() + " should not be allowed to delete the row"); 372 } catch (Exception e) { 373 374 } 375 } 376 return null; 377 } 378 }); 379 } 380 381 @Test 382 public void testDeleteWithFutureTimestamp() throws Exception { 383 // Store two values, one in the future 384 385 verifyAllowed(new AccessTestAction() { 386 @Override 387 public Object run() throws Exception { 388 try (Connection connection = ConnectionFactory.createConnection(conf)) { 389 try (Table t = connection.getTable(testTable.getTableName())) { 390 // Store a read write ACL without a timestamp, server will use current time 391 Put p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q2, ONE); 392 Map<String, Permission> readAndWritePerms = 393 prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE); 394 p.setACL(readAndWritePerms); 395 t.put(p); 396 p = new Put(TEST_ROW).addColumn(TEST_FAMILY2, TEST_Q2, ONE); 397 p.setACL(readAndWritePerms); 398 t.put(p); 399 LOG.info("Stored at current time"); 400 // Store read only ACL at a future time 401 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, 402 EnvironmentEdgeManager.currentTime() + 1000000, ZERO); 403 p.setACL(prepareCellPermissions( 404 new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) }, 405 Action.READ)); 406 t.put(p); 407 } 408 } 409 return null; 410 } 411 }, USER_OWNER); 412 413 // Confirm stores are visible 414 415 AccessTestAction getQ1 = new AccessTestAction() { 416 @Override 417 public Object run() throws Exception { 418 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1); 419 try (Connection connection = ConnectionFactory.createConnection(conf)) { 420 try (Table t = connection.getTable(testTable.getTableName())) { 421 return t.get(get).listCells(); 422 } 423 } 424 } 425 }; 426 427 AccessTestAction getQ2 = new AccessTestAction() { 428 @Override 429 public Object run() throws Exception { 430 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q2); 431 try (Connection connection = ConnectionFactory.createConnection(conf)) { 432 try (Table t = connection.getTable(testTable.getTableName())) { 433 return t.get(get).listCells(); 434 } 435 } 436 } 437 }; 438 439 verifyAllowed(getQ1, USER_OWNER, USER_OTHER, GROUP_USER); 440 verifyAllowed(getQ2, USER_OWNER, USER_OTHER, GROUP_USER); 441 442 // Issue a DELETE for the family, should succeed because the future ACL is 443 // not considered 444 AccessTestAction deleteFamily1 = getDeleteFamilyAction(TEST_FAMILY1); 445 AccessTestAction deleteFamily2 = getDeleteFamilyAction(TEST_FAMILY2); 446 447 verifyAllowed(deleteFamily1, USER_OTHER); 448 verifyAllowed(deleteFamily2, GROUP_USER); 449 450 // The future put should still exist 451 452 verifyAllowed(getQ1, USER_OWNER, USER_OTHER, GROUP_USER); 453 454 // The other put should be covered by the tombstone 455 456 verifyIfNull(getQ2, USER_OTHER, GROUP_USER); 457 } 458 459 private AccessTestAction getDeleteFamilyAction(final byte[] fam) { 460 AccessTestAction deleteFamilyAction = new AccessTestAction() { 461 @Override 462 public Object run() throws Exception { 463 Delete delete = new Delete(TEST_ROW).addFamily(fam); 464 try (Connection connection = ConnectionFactory.createConnection(conf)) { 465 try (Table t = connection.getTable(testTable.getTableName())) { 466 t.delete(delete); 467 } 468 } 469 return null; 470 } 471 }; 472 return deleteFamilyAction; 473 } 474 475 @Test 476 public void testCellPermissionsWithDeleteWithUserTs() throws Exception { 477 USER_OWNER.runAs(new AccessTestAction() { 478 @Override 479 public Object run() throws Exception { 480 try (Connection connection = ConnectionFactory.createConnection(conf)) { 481 try (Table t = connection.getTable(testTable.getTableName())) { 482 // This version (TS = 123) with rw ACL for USER_OTHER and USER_OTHER2 483 Put p = new Put(TEST_ROW); 484 p.addColumn(TEST_FAMILY1, TEST_Q1, 123L, ZERO); 485 p.addColumn(TEST_FAMILY1, TEST_Q2, 123L, ZERO); 486 p.setACL(prepareCellPermissions(new String[] { USER_OTHER.getShortName(), 487 AuthUtil.toGroupEntry(GROUP), USER_OTHER2.getShortName() }, Permission.Action.READ, 488 Permission.Action.WRITE)); 489 t.put(p); 490 491 // This version (TS = 125) with rw ACL for USER_OTHER 492 p = new Put(TEST_ROW); 493 p.addColumn(TEST_FAMILY1, TEST_Q1, 125L, ONE); 494 p.addColumn(TEST_FAMILY1, TEST_Q2, 125L, ONE); 495 p.setACL(prepareCellPermissions( 496 new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) }, Action.READ, 497 Action.WRITE)); 498 t.put(p); 499 500 // This version (TS = 127) with rw ACL for USER_OTHER 501 p = new Put(TEST_ROW); 502 p.addColumn(TEST_FAMILY1, TEST_Q1, 127L, TWO); 503 p.addColumn(TEST_FAMILY1, TEST_Q2, 127L, TWO); 504 p.setACL(prepareCellPermissions( 505 new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) }, Action.READ, 506 Action.WRITE)); 507 t.put(p); 508 509 return null; 510 } 511 } 512 } 513 }); 514 515 // USER_OTHER2 should be allowed to delete the column f1:q1 versions older than TS 124L 516 USER_OTHER2.runAs(new AccessTestAction() { 517 @Override 518 public Object run() throws Exception { 519 try (Connection connection = ConnectionFactory.createConnection(conf)) { 520 try (Table t = connection.getTable(testTable.getTableName())) { 521 Delete d = new Delete(TEST_ROW, 124L); 522 d.addColumns(TEST_FAMILY1, TEST_Q1); 523 t.delete(d); 524 } 525 } 526 return null; 527 } 528 }); 529 530 // USER_OTHER2 should be allowed to delete the column f1:q2 versions older than TS 124L 531 USER_OTHER2.runAs(new AccessTestAction() { 532 @Override 533 public Object run() throws Exception { 534 try (Connection connection = ConnectionFactory.createConnection(conf)) { 535 try (Table t = connection.getTable(testTable.getTableName())) { 536 Delete d = new Delete(TEST_ROW); 537 d.addColumns(TEST_FAMILY1, TEST_Q2, 124L); 538 t.delete(d); 539 } 540 } 541 return null; 542 } 543 }); 544 } 545 546 @Test 547 public void testCellPermissionsWithDeleteExactVersion() throws Exception { 548 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 549 final byte[] TEST_Q1 = Bytes.toBytes("q1"); 550 final byte[] TEST_Q2 = Bytes.toBytes("q2"); 551 final byte[] ZERO = Bytes.toBytes(0L); 552 553 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 554 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 555 556 verifyAllowed(new AccessTestAction() { 557 @Override 558 public Object run() throws Exception { 559 try (Connection connection = ConnectionFactory.createConnection(conf)) { 560 try (Table t = connection.getTable(testTable.getTableName())) { 561 Map<String, 562 Permission> permsU1andOwner = prepareCellPermissions( 563 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 564 Action.WRITE); 565 Map<String, 566 Permission> permsU2andGUandOwner = prepareCellPermissions(new String[] { 567 user2.getShortName(), AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, 568 Action.READ, Action.WRITE); 569 Put p = new Put(TEST_ROW1); 570 p.addColumn(TEST_FAMILY1, TEST_Q1, 123, ZERO); 571 p.setACL(permsU1andOwner); 572 t.put(p); 573 p = new Put(TEST_ROW1); 574 p.addColumn(TEST_FAMILY1, TEST_Q2, 123, ZERO); 575 p.setACL(permsU2andGUandOwner); 576 t.put(p); 577 p = new Put(TEST_ROW1); 578 p.addColumn(TEST_FAMILY2, TEST_Q1, 123, ZERO); 579 p.addColumn(TEST_FAMILY2, TEST_Q2, 123, ZERO); 580 p.setACL(permsU2andGUandOwner); 581 t.put(p); 582 583 p = new Put(TEST_ROW1); 584 p.addColumn(TEST_FAMILY2, TEST_Q1, 125, ZERO); 585 p.addColumn(TEST_FAMILY2, TEST_Q2, 125, ZERO); 586 p.setACL(permsU1andOwner); 587 t.put(p); 588 589 p = new Put(TEST_ROW1); 590 p.addColumn(TEST_FAMILY1, TEST_Q1, 127, ZERO); 591 p.setACL(permsU2andGUandOwner); 592 t.put(p); 593 p = new Put(TEST_ROW1); 594 p.addColumn(TEST_FAMILY1, TEST_Q2, 127, ZERO); 595 p.setACL(permsU1andOwner); 596 t.put(p); 597 p = new Put(TEST_ROW1); 598 p.addColumn(TEST_FAMILY2, TEST_Q1, 129, ZERO); 599 p.addColumn(TEST_FAMILY2, TEST_Q2, 129, ZERO); 600 p.setACL(permsU1andOwner); 601 t.put(p); 602 } 603 } 604 return null; 605 } 606 }, USER_OWNER); 607 608 // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both 609 // versions of the cells 610 user1.runAs(new PrivilegedExceptionAction<Void>() { 611 @Override 612 public Void run() throws Exception { 613 try (Connection connection = ConnectionFactory.createConnection(conf)) { 614 try (Table t = connection.getTable(testTable.getTableName())) { 615 Delete d = new Delete(TEST_ROW1); 616 d.addColumn(TEST_FAMILY1, TEST_Q1, 123); 617 d.addColumn(TEST_FAMILY1, TEST_Q2); 618 d.addFamilyVersion(TEST_FAMILY2, 125); 619 t.delete(d); 620 } 621 } 622 return null; 623 } 624 }); 625 626 verifyUserDeniedForDeleteExactVersion(user2, TEST_ROW1, TEST_Q1, TEST_Q2); 627 verifyUserDeniedForDeleteExactVersion(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2); 628 } 629 630 private void verifyUserDeniedForDeleteExactVersion(final User user, final byte[] row, 631 final byte[] q1, final byte[] q2) throws IOException, InterruptedException { 632 user.runAs(new PrivilegedExceptionAction<Void>() { 633 @Override 634 public Void run() throws Exception { 635 try (Connection connection = ConnectionFactory.createConnection(conf)) { 636 try (Table t = connection.getTable(testTable.getTableName())) { 637 Delete d = new Delete(row, 127); 638 d.addColumns(TEST_FAMILY1, q1); 639 d.addColumns(TEST_FAMILY1, q2); 640 d.addFamily(TEST_FAMILY2, 129); 641 t.delete(d); 642 fail(user.getShortName() + " can not do the delete"); 643 } catch (Exception e) { 644 645 } 646 } 647 return null; 648 } 649 }); 650 } 651 652 @Test 653 public void testCellPermissionsForIncrementWithMultipleVersions() throws Exception { 654 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 655 final byte[] TEST_Q1 = Bytes.toBytes("q1"); 656 final byte[] TEST_Q2 = Bytes.toBytes("q2"); 657 final byte[] ZERO = Bytes.toBytes(0L); 658 659 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 660 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 661 662 verifyAllowed(new AccessTestAction() { 663 @Override 664 public Object run() throws Exception { 665 try (Connection connection = ConnectionFactory.createConnection(conf)) { 666 try (Table t = connection.getTable(testTable.getTableName())) { 667 Map<String, 668 Permission> permsU1andOwner = prepareCellPermissions( 669 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 670 Action.WRITE); 671 Map<String, 672 Permission> permsU2andGUandOwner = prepareCellPermissions(new String[] { 673 user2.getShortName(), AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, 674 Action.READ, Action.WRITE); 675 Put p = new Put(TEST_ROW1); 676 p.addColumn(TEST_FAMILY1, TEST_Q1, 123, ZERO); 677 p.setACL(permsU1andOwner); 678 t.put(p); 679 p = new Put(TEST_ROW1); 680 p.addColumn(TEST_FAMILY1, TEST_Q2, 123, ZERO); 681 p.setACL(permsU2andGUandOwner); 682 t.put(p); 683 684 p = new Put(TEST_ROW1); 685 p.addColumn(TEST_FAMILY1, TEST_Q1, 127, ZERO); 686 p.setACL(permsU2andGUandOwner); 687 t.put(p); 688 p = new Put(TEST_ROW1); 689 p.addColumn(TEST_FAMILY1, TEST_Q2, 127, ZERO); 690 p.setACL(permsU1andOwner); 691 t.put(p); 692 } 693 } 694 return null; 695 } 696 }, USER_OWNER); 697 698 // Increment considers the TimeRange set on it. 699 user1.runAs(new PrivilegedExceptionAction<Void>() { 700 @Override 701 public Void run() throws Exception { 702 try (Connection connection = ConnectionFactory.createConnection(conf)) { 703 try (Table t = connection.getTable(testTable.getTableName())) { 704 Increment inc = new Increment(TEST_ROW1); 705 inc.setTimeRange(0, 123); 706 inc.addColumn(TEST_FAMILY1, TEST_Q1, 2L); 707 t.increment(inc); 708 t.incrementColumnValue(TEST_ROW1, TEST_FAMILY1, TEST_Q2, 1L); 709 } 710 } 711 return null; 712 } 713 }); 714 715 verifyUserDeniedForIncrementMultipleVersions(user2, TEST_ROW1, TEST_Q2); 716 verifyUserDeniedForIncrementMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q2); 717 } 718 719 private void verifyUserDeniedForIncrementMultipleVersions(final User user, final byte[] row, 720 final byte[] q1) throws IOException, InterruptedException { 721 user.runAs(new PrivilegedExceptionAction<Void>() { 722 @Override 723 public Void run() throws Exception { 724 try (Connection connection = ConnectionFactory.createConnection(conf)) { 725 try (Table t = connection.getTable(testTable.getTableName())) { 726 Increment inc = new Increment(row); 727 inc.setTimeRange(0, 127); 728 inc.addColumn(TEST_FAMILY1, q1, 2L); 729 t.increment(inc); 730 fail(user.getShortName() + " cannot do the increment."); 731 } catch (Exception e) { 732 733 } 734 } 735 return null; 736 } 737 }); 738 } 739 740 @Test 741 public void testCellPermissionsForPutWithMultipleVersions() throws Exception { 742 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 743 final byte[] TEST_Q1 = Bytes.toBytes("q1"); 744 final byte[] TEST_Q2 = Bytes.toBytes("q2"); 745 final byte[] ZERO = Bytes.toBytes(0L); 746 747 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 748 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 749 750 verifyAllowed(new AccessTestAction() { 751 @Override 752 public Object run() throws Exception { 753 try (Connection connection = ConnectionFactory.createConnection(conf)) { 754 try (Table t = connection.getTable(testTable.getTableName())) { 755 Map<String, 756 Permission> permsU1andOwner = prepareCellPermissions( 757 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 758 Action.WRITE); 759 Map<String, 760 Permission> permsU2andGUandOwner = prepareCellPermissions(new String[] { 761 user1.getShortName(), AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, 762 Action.READ, Action.WRITE); 763 permsU2andGUandOwner.put(user2.getShortName(), 764 new Permission(Permission.Action.READ, Permission.Action.WRITE)); 765 permsU2andGUandOwner.put(USER_OWNER.getShortName(), 766 new Permission(Permission.Action.READ, Permission.Action.WRITE)); 767 Put p = new Put(TEST_ROW1); 768 p.addColumn(TEST_FAMILY1, TEST_Q1, 123, ZERO); 769 p.setACL(permsU1andOwner); 770 t.put(p); 771 p = new Put(TEST_ROW1); 772 p.addColumn(TEST_FAMILY1, TEST_Q2, 123, ZERO); 773 p.setACL(permsU2andGUandOwner); 774 t.put(p); 775 776 p = new Put(TEST_ROW1); 777 p.addColumn(TEST_FAMILY1, TEST_Q1, 127, ZERO); 778 p.setACL(permsU2andGUandOwner); 779 t.put(p); 780 p = new Put(TEST_ROW1); 781 p.addColumn(TEST_FAMILY1, TEST_Q2, 127, ZERO); 782 p.setACL(permsU1andOwner); 783 t.put(p); 784 } 785 } 786 return null; 787 } 788 }, USER_OWNER); 789 790 // new Put with TEST_Q1 column having TS=125. This covers old cell with TS 123 and user1 is 791 // having RW permission. While TEST_Q2 is with latest TS and so it covers old cell with TS 127. 792 // User1 is having RW permission on that too. 793 user1.runAs(new PrivilegedExceptionAction<Void>() { 794 @Override 795 public Void run() throws Exception { 796 try (Connection connection = ConnectionFactory.createConnection(conf)) { 797 try (Table t = connection.getTable(testTable.getTableName())) { 798 Put p = new Put(TEST_ROW1); 799 p.addColumn(TEST_FAMILY1, TEST_Q1, 125, ZERO); 800 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 801 p.setACL(user2.getShortName(), 802 new Permission(Permission.Action.READ, Permission.Action.WRITE)); 803 t.put(p); 804 } 805 } 806 return null; 807 } 808 }); 809 810 verifyUserDeniedForPutMultipleVersions(user2, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO); 811 verifyUserDeniedForPutMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO); 812 } 813 814 private void verifyUserDeniedForPutMultipleVersions(final User user, final byte[] row, 815 final byte[] q1, final byte[] q2, final byte[] value) throws IOException, InterruptedException { 816 user.runAs(new PrivilegedExceptionAction<Void>() { 817 @Override 818 public Void run() throws Exception { 819 try (Connection connection = ConnectionFactory.createConnection(conf)) { 820 try (Table t = connection.getTable(testTable.getTableName())) { 821 Put p = new Put(row); 822 // column Q1 covers version at 123 fr which user2 do not have permission 823 p.addColumn(TEST_FAMILY1, q1, 124, value); 824 p.addColumn(TEST_FAMILY1, q2, value); 825 t.put(p); 826 fail(user.getShortName() + " cannot do the put."); 827 } catch (Exception e) { 828 829 } 830 } 831 return null; 832 } 833 }); 834 } 835 836 @Test 837 public void testCellPermissionsForCheckAndDelete() throws Exception { 838 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 839 final byte[] TEST_Q3 = Bytes.toBytes("q3"); 840 final byte[] ZERO = Bytes.toBytes(0L); 841 842 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 843 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 844 845 verifyAllowed(new AccessTestAction() { 846 @Override 847 public Object run() throws Exception { 848 try (Connection connection = ConnectionFactory.createConnection(conf)) { 849 try (Table t = connection.getTable(testTable.getTableName())) { 850 Map<String, 851 Permission> permsU1andOwner = prepareCellPermissions( 852 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 853 Action.WRITE); 854 Map<String, 855 Permission> permsU1andU2andGUandOwner = prepareCellPermissions( 856 new String[] { user1.getShortName(), user2.getShortName(), 857 AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, 858 Action.READ, Action.WRITE); 859 Map<String, Permission> permsU1_U2andGU = 860 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(), 861 AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE); 862 863 Put p = new Put(TEST_ROW1); 864 p.addColumn(TEST_FAMILY1, TEST_Q1, 120, ZERO); 865 p.addColumn(TEST_FAMILY1, TEST_Q2, 120, ZERO); 866 p.addColumn(TEST_FAMILY1, TEST_Q3, 120, ZERO); 867 p.setACL(permsU1andU2andGUandOwner); 868 t.put(p); 869 870 p = new Put(TEST_ROW1); 871 p.addColumn(TEST_FAMILY1, TEST_Q1, 123, ZERO); 872 p.addColumn(TEST_FAMILY1, TEST_Q2, 123, ZERO); 873 p.addColumn(TEST_FAMILY1, TEST_Q3, 123, ZERO); 874 p.setACL(permsU1andOwner); 875 t.put(p); 876 877 p = new Put(TEST_ROW1); 878 p.addColumn(TEST_FAMILY1, TEST_Q1, 127, ZERO); 879 p.setACL(permsU1_U2andGU); 880 t.put(p); 881 882 p = new Put(TEST_ROW1); 883 p.addColumn(TEST_FAMILY1, TEST_Q2, 127, ZERO); 884 p.setACL(user2.getShortName(), new Permission(Permission.Action.READ)); 885 t.put(p); 886 887 p = new Put(TEST_ROW1); 888 p.addColumn(TEST_FAMILY1, TEST_Q3, 127, ZERO); 889 p.setACL(AuthUtil.toGroupEntry(GROUP), new Permission(Permission.Action.READ)); 890 t.put(p); 891 } 892 } 893 return null; 894 } 895 }, USER_OWNER); 896 897 // user1 should be allowed to do the checkAndDelete. user1 having read permission on the latest 898 // version cell and write permission on all versions 899 user1.runAs(new PrivilegedExceptionAction<Void>() { 900 @Override 901 public Void run() throws Exception { 902 try (Connection connection = ConnectionFactory.createConnection(conf)) { 903 try (Table t = connection.getTable(testTable.getTableName())) { 904 Delete d = new Delete(TEST_ROW1); 905 d.addColumns(TEST_FAMILY1, TEST_Q1, 120); 906 t.checkAndMutate(TEST_ROW1, TEST_FAMILY1).qualifier(TEST_Q1).ifEquals(ZERO) 907 .thenDelete(d); 908 } 909 } 910 return null; 911 } 912 }); 913 // user2 shouldn't be allowed to do the checkAndDelete. user2 having RW permission on the latest 914 // version cell but not on cell version TS=123 915 verifyUserDeniedForCheckAndDelete(user2, TEST_ROW1, ZERO); 916 917 // GROUP_USER shouldn't be allowed to do the checkAndDelete. GROUP_USER having RW permission on 918 // the latest 919 // version cell but not on cell version TS=123 920 verifyUserDeniedForCheckAndDelete(GROUP_USER, TEST_ROW1, ZERO); 921 922 // user2 should be allowed to do the checkAndDelete when delete tries to delete the old version 923 // TS=120. user2 having R permission on the latest version(no W permission) cell 924 // and W permission on cell version TS=120. 925 verifyUserAllowedforCheckAndDelete(user2, TEST_ROW1, TEST_Q2, ZERO); 926 927 // GROUP_USER should be allowed to do the checkAndDelete when delete tries to delete the old 928 // version 929 // TS=120. user2 having R permission on the latest version(no W permission) cell 930 // and W permission on cell version TS=120. 931 verifyUserAllowedforCheckAndDelete(GROUP_USER, TEST_ROW1, TEST_Q3, ZERO); 932 } 933 934 private void verifyUserAllowedforCheckAndDelete(final User user, final byte[] row, 935 final byte[] q1, final byte[] value) throws IOException, InterruptedException { 936 user.runAs(new PrivilegedExceptionAction<Void>() { 937 @Override 938 public Void run() throws Exception { 939 try (Connection connection = ConnectionFactory.createConnection(conf)) { 940 try (Table t = connection.getTable(testTable.getTableName())) { 941 Delete d = new Delete(row); 942 d.addColumn(TEST_FAMILY1, q1, 120); 943 t.checkAndMutate(row, TEST_FAMILY1).qualifier(q1).ifEquals(value).thenDelete(d); 944 } 945 } 946 return null; 947 } 948 }); 949 } 950 951 private void verifyUserDeniedForCheckAndDelete(final User user, final byte[] row, 952 final byte[] value) throws IOException, InterruptedException { 953 user.runAs(new PrivilegedExceptionAction<Void>() { 954 @Override 955 public Void run() throws Exception { 956 try (Connection connection = ConnectionFactory.createConnection(conf)) { 957 try (Table t = connection.getTable(testTable.getTableName())) { 958 Delete d = new Delete(row); 959 d.addColumns(TEST_FAMILY1, TEST_Q1); 960 t.checkAndMutate(row, TEST_FAMILY1).qualifier(TEST_Q1).ifEquals(value).thenDelete(d); 961 fail(user.getShortName() + " should not be allowed to do checkAndDelete"); 962 } catch (Exception e) { 963 } 964 } 965 return null; 966 } 967 }); 968 } 969 970 @After 971 public void tearDown() throws Exception { 972 // Clean the _acl_ table 973 try { 974 TEST_UTIL.deleteTable(testTable.getTableName()); 975 } catch (TableNotFoundException ex) { 976 // Test deleted the table, no problem 977 LOG.info("Test deleted table " + testTable.getTableName()); 978 } 979 assertEquals(0, PermissionStorage.getTablePermissions(conf, testTable.getTableName()).size()); 980 } 981}