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; 022import java.io.IOException; 023import java.security.PrivilegedExceptionAction; 024import java.util.HashMap; 025import java.util.Map; 026import org.apache.hadoop.conf.Configuration; 027import org.apache.hadoop.hbase.AuthUtil; 028import org.apache.hadoop.hbase.Coprocessor; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.HColumnDescriptor; 032import org.apache.hadoop.hbase.HTableDescriptor; 033import org.apache.hadoop.hbase.TableNameTestRule; 034import org.apache.hadoop.hbase.TableNotFoundException; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.client.Connection; 037import org.apache.hadoop.hbase.client.ConnectionFactory; 038import org.apache.hadoop.hbase.client.Delete; 039import org.apache.hadoop.hbase.client.Get; 040import org.apache.hadoop.hbase.client.Increment; 041import org.apache.hadoop.hbase.client.Put; 042import org.apache.hadoop.hbase.client.Table; 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.After; 053import org.junit.AfterClass; 054import org.junit.Before; 055import org.junit.BeforeClass; 056import org.junit.ClassRule; 057import org.junit.Rule; 058import org.junit.Test; 059import org.junit.experimental.categories.Category; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063@Category({SecurityTests.class, MediumTests.class}) 064public class TestCellACLWithMultipleVersions extends SecureTestUtil { 065 066 @ClassRule 067 public static final HBaseClassTestRule CLASS_RULE = 068 HBaseClassTestRule.forClass(TestCellACLWithMultipleVersions.class); 069 070 private static final Logger LOG = LoggerFactory.getLogger(TestCellACLWithMultipleVersions.class); 071 072 @Rule 073 public TableNameTestRule testTable = new TableNameTestRule(); 074 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 075 private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1"); 076 private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2"); 077 private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest"); 078 private static final byte[] TEST_Q1 = Bytes.toBytes("q1"); 079 private static final byte[] TEST_Q2 = Bytes.toBytes("q2"); 080 private static final byte[] ZERO = Bytes.toBytes(0L); 081 private static final byte[] ONE = Bytes.toBytes(1L); 082 private static final byte[] TWO = Bytes.toBytes(2L); 083 084 private static Configuration conf; 085 086 private static final String GROUP = "group"; 087 private static User GROUP_USER; 088 private static User USER_OWNER; 089 private static User USER_OTHER; 090 private static User USER_OTHER2; 091 092 private static String[] usersAndGroups; 093 094 @BeforeClass 095 public static void setupBeforeClass() throws Exception { 096 // setup configuration 097 conf = TEST_UTIL.getConfiguration(); 098 // Enable security 099 enableSecurity(conf); 100 // Verify enableSecurity sets up what we require 101 verifyConfiguration(conf); 102 103 // We expect 0.98 cell ACL semantics 104 conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false); 105 106 TEST_UTIL.startMiniCluster(); 107 MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster() 108 .getMasterCoprocessorHost(); 109 cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); 110 AccessController ac = cpHost.findCoprocessor(AccessController.class); 111 cpHost.createEnvironment(ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); 112 RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0) 113 .getRegionServerCoprocessorHost(); 114 rsHost.createEnvironment(ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); 115 116 // Wait for the ACL table to become available 117 TEST_UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME); 118 119 // create a set of test users 120 USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]); 121 USER_OTHER = User.createUserForTesting(conf, "other", new String[0]); 122 USER_OTHER2 = User.createUserForTesting(conf, "other2", new String[0]); 123 GROUP_USER = User.createUserForTesting(conf, "group_user", new String[] { GROUP }); 124 125 usersAndGroups = new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) }; 126 } 127 128 @AfterClass 129 public static void tearDownAfterClass() throws Exception { 130 TEST_UTIL.shutdownMiniCluster(); 131 } 132 133 @Before 134 public void setUp() throws Exception { 135 HTableDescriptor htd = new HTableDescriptor(testTable.getTableName()); 136 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY1); 137 hcd.setMaxVersions(4); 138 htd.setOwner(USER_OWNER); 139 htd.addFamily(hcd); 140 hcd = new HColumnDescriptor(TEST_FAMILY2); 141 hcd.setMaxVersions(4); 142 htd.setOwner(USER_OWNER); 143 htd.addFamily(hcd); 144 // Create the test table (owner added to the _acl_ table) 145 try (Connection connection = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())) { 146 try (Admin admin = connection.getAdmin()) { 147 admin.createTable(htd, new byte[][] { Bytes.toBytes("s") }); 148 } 149 } 150 TEST_UTIL.waitTableEnabled(testTable.getTableName()); 151 LOG.info("Sleeping a second because of HBASE-12581"); 152 Threads.sleep(1000); 153 } 154 155 @Test 156 public void testCellPermissionwithVersions() throws Exception { 157 // store two sets of values, one store with a cell level ACL, and one 158 // without 159 final Map<String, Permission> writePerms = prepareCellPermissions(usersAndGroups, Action.WRITE); 160 final Map<String, Permission> readPerms = prepareCellPermissions(usersAndGroups, Action.READ); 161 verifyAllowed(new AccessTestAction() { 162 @Override 163 public Object run() throws Exception { 164 try(Connection connection = ConnectionFactory.createConnection(conf); 165 Table t = connection.getTable(testTable.getTableName())) { 166 Put p; 167 // with ro ACL 168 long now = System.currentTimeMillis(); 169 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now, ZERO); 170 p.setACL(writePerms); 171 t.put(p); 172 // with ro ACL 173 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 1, ZERO); 174 p.setACL(readPerms); 175 t.put(p); 176 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 2, ZERO); 177 p.setACL(writePerms); 178 t.put(p); 179 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 3, ZERO); 180 p.setACL(readPerms); 181 t.put(p); 182 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, now + 4, ZERO); 183 p.setACL(writePerms); 184 t.put(p); 185 } 186 return null; 187 } 188 }, USER_OWNER); 189 190 /* ---- Gets ---- */ 191 192 AccessTestAction getQ1 = new AccessTestAction() { 193 @Override 194 public Object run() throws Exception { 195 Get get = new Get(TEST_ROW); 196 get.setMaxVersions(10); 197 try(Connection connection = ConnectionFactory.createConnection(conf); 198 Table t = connection.getTable(testTable.getTableName())) { 199 return t.get(get).listCells(); 200 } 201 } 202 }; 203 204 AccessTestAction get2 = new AccessTestAction() { 205 @Override 206 public Object run() throws Exception { 207 Get get = new Get(TEST_ROW); 208 get.setMaxVersions(10); 209 try(Connection connection = ConnectionFactory.createConnection(conf); 210 Table t = connection.getTable(testTable.getTableName())) { 211 return t.get(get).listCells(); 212 } 213 } 214 }; 215 // Confirm special read access set at cell level 216 217 verifyAllowed(GROUP_USER, getQ1, 2); 218 verifyAllowed(USER_OTHER, getQ1, 2); 219 220 // store two sets of values, one store with a cell level ACL, and one 221 // without 222 verifyAllowed(new AccessTestAction() { 223 @Override 224 public Object run() throws Exception { 225 try(Connection connection = ConnectionFactory.createConnection(conf); 226 Table t = connection.getTable(testTable.getTableName())) { 227 Put p; 228 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 229 p.setACL(writePerms); 230 t.put(p); 231 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 232 p.setACL(readPerms); 233 t.put(p); 234 p = new Put(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 235 p.setACL(writePerms); 236 t.put(p); 237 } 238 return null; 239 } 240 }, USER_OWNER); 241 // Confirm special read access set at cell level 242 243 verifyAllowed(USER_OTHER, get2, 1); 244 verifyAllowed(GROUP_USER, get2, 1); 245 } 246 247 private Map<String, Permission> prepareCellPermissions(String[] users, Action... action) { 248 Map<String, Permission> perms = new HashMap<>(2); 249 for (String user : users) { 250 perms.put(user, new Permission(action)); 251 } 252 return perms; 253 } 254 255 @Test 256 public void testCellPermissionsWithDeleteMutipleVersions() throws Exception { 257 // table/column/qualifier level permissions 258 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 259 final byte[] TEST_ROW2 = Bytes.toBytes("r2"); 260 final byte[] TEST_Q1 = Bytes.toBytes("q1"); 261 final byte[] TEST_Q2 = Bytes.toBytes("q2"); 262 final byte[] ZERO = Bytes.toBytes(0L); 263 264 // additional test user 265 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 266 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 267 268 verifyAllowed(new AccessTestAction() { 269 @Override 270 public Object run() throws Exception { 271 try (Connection connection = ConnectionFactory.createConnection(conf)) { 272 try (Table t = connection.getTable(testTable.getTableName())) { 273 // with rw ACL for "user1" 274 Put p = new Put(TEST_ROW1); 275 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 276 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 277 p.setACL(user1.getShortName(), new Permission(Permission.Action.READ, 278 Permission.Action.WRITE)); 279 t.put(p); 280 // with rw ACL for "user1" 281 p = new Put(TEST_ROW2); 282 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 283 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 284 p.setACL(user1.getShortName(), new Permission(Permission.Action.READ, 285 Permission.Action.WRITE)); 286 t.put(p); 287 } 288 } 289 return null; 290 } 291 }, USER_OWNER); 292 293 verifyAllowed(new AccessTestAction() { 294 @Override 295 public Object run() throws Exception { 296 try (Connection connection = ConnectionFactory.createConnection(conf)) { 297 try (Table t = connection.getTable(testTable.getTableName())) { 298 // with rw ACL for "user1", "user2" and "@group" 299 Put p = new Put(TEST_ROW1); 300 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 301 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 302 Map<String, Permission> perms = 303 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(), 304 AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE); 305 p.setACL(perms); 306 t.put(p); 307 // with rw ACL for "user1", "user2" and "@group" 308 p = new Put(TEST_ROW2); 309 p.addColumn(TEST_FAMILY1, TEST_Q1, ZERO); 310 p.addColumn(TEST_FAMILY1, TEST_Q2, ZERO); 311 p.setACL(perms); 312 t.put(p); 313 } 314 } 315 return null; 316 } 317 }, user1); 318 319 // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both 320 // versions of the cells 321 user1.runAs(new PrivilegedExceptionAction<Void>() { 322 @Override 323 public Void run() throws Exception { 324 try (Connection connection = ConnectionFactory.createConnection(conf)) { 325 try (Table t = connection.getTable(testTable.getTableName())) { 326 Delete d = new Delete(TEST_ROW1); 327 d.addColumns(TEST_FAMILY1, TEST_Q1); 328 d.addColumns(TEST_FAMILY1, TEST_Q2); 329 t.delete(d); 330 } 331 } 332 return null; 333 } 334 }); 335 // user2 should not be allowed to delete TEST_ROW2 as he is having write permission only on one 336 // version of the cells. 337 verifyUserDeniedForDeleteMultipleVersions(user2, TEST_ROW2, TEST_Q1, TEST_Q2); 338 339 // GROUP_USER should not be allowed to delete TEST_ROW2 as he is having write permission only on 340 // one version of the cells. 341 verifyUserDeniedForDeleteMultipleVersions(GROUP_USER, TEST_ROW2, TEST_Q1, TEST_Q2); 342 343 // user1 should be allowed to delete the cf. (All data under cf for a row) 344 user1.runAs(new PrivilegedExceptionAction<Void>() { 345 @Override 346 public Void run() throws Exception { 347 try (Connection connection = ConnectionFactory.createConnection(conf)) { 348 try (Table t = connection.getTable(testTable.getTableName())) { 349 Delete d = new Delete(TEST_ROW2); 350 d.addFamily(TEST_FAMILY1); 351 t.delete(d); 352 } 353 } 354 return null; 355 } 356 }); 357 } 358 359 private void verifyUserDeniedForDeleteMultipleVersions(final User user, final byte[] row, 360 final byte[] q1, final byte[] q2) throws IOException, InterruptedException { 361 user.runAs(new PrivilegedExceptionAction<Void>() { 362 @Override 363 public Void run() throws Exception { 364 try (Connection connection = ConnectionFactory.createConnection(conf)) { 365 try (Table t = connection.getTable(testTable.getTableName())) { 366 Delete d = new Delete(row); 367 d.addColumns(TEST_FAMILY1, q1); 368 d.addColumns(TEST_FAMILY1, q2); 369 t.delete(d); 370 fail(user.getShortName() + " should not be allowed to delete the row"); 371 } catch (Exception e) { 372 373 } 374 } 375 return null; 376 } 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(new String[]{ USER_OTHER.getShortName(), 404 AuthUtil.toGroupEntry(GROUP)}, Action.READ)); 405 t.put(p); 406 } 407 } 408 return null; 409 } 410 }, USER_OWNER); 411 412 // Confirm stores are visible 413 414 AccessTestAction getQ1 = new AccessTestAction() { 415 @Override 416 public Object run() throws Exception { 417 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1); 418 try (Connection connection = ConnectionFactory.createConnection(conf)) { 419 try (Table t = connection.getTable(testTable.getTableName())) { 420 return t.get(get).listCells(); 421 } 422 } 423 } 424 }; 425 426 AccessTestAction getQ2 = new AccessTestAction() { 427 @Override 428 public Object run() throws Exception { 429 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q2); 430 try (Connection connection = ConnectionFactory.createConnection(conf)) { 431 try (Table t = connection.getTable(testTable.getTableName())) { 432 return t.get(get).listCells(); 433 } 434 } 435 } 436 }; 437 438 verifyAllowed(getQ1, USER_OWNER, USER_OTHER, GROUP_USER); 439 verifyAllowed(getQ2, USER_OWNER, USER_OTHER, GROUP_USER); 440 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( 487 new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP), 488 USER_OTHER2.getShortName() }, Permission.Action.READ, 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) }, 497 Action.READ, 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) }, 506 Action.READ, 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, Permission> permsU1andOwner = 562 prepareCellPermissions( 563 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 564 Action.WRITE); 565 Map<String, Permission> permsU2andGUandOwner = 566 prepareCellPermissions( 567 new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP), 568 USER_OWNER.getShortName() }, 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, Permission> permsU1andOwner = 668 prepareCellPermissions( 669 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 670 Action.WRITE); 671 Map<String, Permission> permsU2andGUandOwner = 672 prepareCellPermissions( 673 new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP), 674 USER_OWNER.getShortName() }, 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, Permission> permsU1andOwner = 756 prepareCellPermissions( 757 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 758 Action.WRITE); 759 Map<String, Permission> permsU2andGUandOwner = 760 prepareCellPermissions( 761 new String[] { user1.getShortName(), AuthUtil.toGroupEntry(GROUP), 762 USER_OWNER.getShortName() }, Action.READ, Action.WRITE); 763 permsU2andGUandOwner.put(user2.getShortName(), new Permission(Permission.Action.READ, 764 Permission.Action.WRITE)); 765 permsU2andGUandOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ, 766 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(), new Permission(Permission.Action.READ, 802 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, 816 InterruptedException { 817 user.runAs(new PrivilegedExceptionAction<Void>() { 818 @Override 819 public Void run() throws Exception { 820 try (Connection connection = ConnectionFactory.createConnection(conf)) { 821 try (Table t = connection.getTable(testTable.getTableName())) { 822 Put p = new Put(row); 823 // column Q1 covers version at 123 fr which user2 do not have permission 824 p.addColumn(TEST_FAMILY1, q1, 124, value); 825 p.addColumn(TEST_FAMILY1, q2, value); 826 t.put(p); 827 fail(user.getShortName() + " cannot do the put."); 828 } catch (Exception e) { 829 830 } 831 } 832 return null; 833 } 834 }); 835 } 836 837 @Test 838 public void testCellPermissionsForCheckAndDelete() throws Exception { 839 final byte[] TEST_ROW1 = Bytes.toBytes("r1"); 840 final byte[] TEST_Q3 = Bytes.toBytes("q3"); 841 final byte[] ZERO = Bytes.toBytes(0L); 842 843 final User user1 = User.createUserForTesting(conf, "user1", new String[0]); 844 final User user2 = User.createUserForTesting(conf, "user2", new String[0]); 845 846 verifyAllowed(new AccessTestAction() { 847 @Override 848 public Object run() throws Exception { 849 try (Connection connection = ConnectionFactory.createConnection(conf)) { 850 try (Table t = connection.getTable(testTable.getTableName())) { 851 Map<String, Permission> permsU1andOwner = 852 prepareCellPermissions( 853 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ, 854 Action.WRITE); 855 Map<String, Permission> permsU1andU2andGUandOwner = 856 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(), 857 AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, Action.READ, 858 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) 907 .ifEquals(ZERO).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}