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