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