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