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