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}