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}