View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.security.access;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.fail;
22  import java.io.IOException;
23  import java.security.PrivilegedExceptionAction;
24  import java.util.HashMap;
25  import java.util.Map;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.hbase.AuthUtil;
30  import org.apache.hadoop.hbase.Coprocessor;
31  import org.apache.hadoop.hbase.HBaseTestingUtility;
32  import org.apache.hadoop.hbase.HColumnDescriptor;
33  import org.apache.hadoop.hbase.HTableDescriptor;
34  import org.apache.hadoop.hbase.TableNameTestRule;
35  import org.apache.hadoop.hbase.TableNotFoundException;
36  import org.apache.hadoop.hbase.client.Connection;
37  import org.apache.hadoop.hbase.client.ConnectionFactory;
38  import org.apache.hadoop.hbase.client.Delete;
39  import org.apache.hadoop.hbase.client.Get;
40  import org.apache.hadoop.hbase.client.Increment;
41  import org.apache.hadoop.hbase.client.Put;
42  import org.apache.hadoop.hbase.client.Table;
43  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
44  import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
45  import org.apache.hadoop.hbase.security.User;
46  import org.apache.hadoop.hbase.security.access.Permission.Action;
47  import org.apache.hadoop.hbase.testclassification.MediumTests;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
50  import org.apache.hadoop.hbase.util.Threads;
51  import org.apache.log4j.Level;
52  import org.apache.log4j.Logger;
53  import org.junit.After;
54  import org.junit.AfterClass;
55  import org.junit.Before;
56  import org.junit.BeforeClass;
57  import org.junit.Rule;
58  import org.junit.Test;
59  import org.junit.experimental.categories.Category;
60  
61  @Category(MediumTests.class)
62  public class TestCellACLWithMultipleVersions extends SecureTestUtil {
63    private static final Log LOG = LogFactory.getLog(TestCellACLWithMultipleVersions.class);
64  
65    static {
66      Logger.getLogger(AccessController.class).setLevel(Level.TRACE);
67      Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE);
68      Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE);
69    }
70  
71    @Rule
72    public TableNameTestRule testTable = new TableNameTestRule();
73    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
74    private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1");
75    private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2");
76    private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest");
77    private static final byte[] TEST_Q1 = Bytes.toBytes("q1");
78    private static final byte[] TEST_Q2 = Bytes.toBytes("q2");
79    private static final byte[] ZERO = Bytes.toBytes(0L);
80    private static final byte[] ONE = Bytes.toBytes(1L);
81    private static final byte[] TWO = Bytes.toBytes(2L);
82  
83    private static Configuration conf;
84  
85    private static final String GROUP = "group";
86    private static User GROUP_USER;
87    private static User USER_OWNER;
88    private static User USER_OTHER;
89    private static User USER_OTHER2;
90  
91    private static String[] usersAndGroups;
92  
93    @BeforeClass
94    public static void setupBeforeClass() throws Exception {
95      // setup configuration
96      conf = TEST_UTIL.getConfiguration();
97      // Enable security
98      enableSecurity(conf);
99      // Verify enableSecurity sets up what we require
100     verifyConfiguration(conf);
101 
102     // We expect 0.98 cell ACL semantics
103     conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false);
104 
105     TEST_UTIL.startMiniCluster();
106     MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster()
107         .getMasterCoprocessorHost();
108     cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf);
109     AccessController ac = (AccessController)
110       cpHost.findCoprocessor(AccessController.class.getName());
111     cpHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
112     RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0)
113         .getRegionServerCoprocessorHost();
114     rsHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
115 
116     // Wait for the ACL table to become available
117     TEST_UTIL.waitTableEnabled(AccessControlLists.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     TEST_UTIL.createTable(htd, new byte[][] { Bytes.toBytes("s") });
146     TEST_UTIL.waitUntilAllRegionsAssigned(testTable.getTableName());
147     LOG.info("Sleeping a second because of HBASE-12581");
148     Threads.sleep(1000);
149   }
150 
151   @Test
152   public void testCellPermissionwithVersions() throws Exception {
153     // store two sets of values, one store with a cell level ACL, and one
154     // without
155     final Map<String, Permission> writePerms = prepareCellPermissions(usersAndGroups, Action.WRITE);
156     final Map<String, Permission> readPerms = prepareCellPermissions(usersAndGroups, Action.READ);
157     verifyAllowed(new AccessTestAction() {
158       @Override
159       public Object run() throws Exception {
160         try(Connection conn = ConnectionFactory.createConnection(conf);
161             Table t = conn.getTable(testTable.getTableName())) {
162           Put p;
163           // with ro ACL
164           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
165           p.setACL(writePerms);
166           t.put(p);
167           // with ro ACL
168           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
169           p.setACL(readPerms);
170           t.put(p);
171           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
172           p.setACL(writePerms);
173           t.put(p);
174           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
175           p.setACL(readPerms);
176           t.put(p);
177           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
178           p.setACL(writePerms);
179           t.put(p);
180         }
181         return null;
182       }
183     }, USER_OWNER);
184 
185     /* ---- Gets ---- */
186 
187     AccessTestAction getQ1 = new AccessTestAction() {
188       @Override
189       public Object run() throws Exception {
190         Get get = new Get(TEST_ROW);
191         get.setMaxVersions(10);
192         try(Connection conn = ConnectionFactory.createConnection(conf);
193             Table t = conn.getTable(testTable.getTableName())) {
194           return t.get(get).listCells();
195         }
196       }
197     };
198 
199     AccessTestAction get2 = new AccessTestAction() {
200       @Override
201       public Object run() throws Exception {
202         Get get = new Get(TEST_ROW);
203         get.setMaxVersions(10);
204         try(Connection conn = ConnectionFactory.createConnection(conf);
205             Table t = conn.getTable(testTable.getTableName())) {
206           return t.get(get).listCells();
207         }
208       }
209     };
210     // Confirm special read access set at cell level
211 
212     verifyAllowed(GROUP_USER, getQ1, 2);
213     verifyAllowed(USER_OTHER, getQ1, 2);
214 
215     // store two sets of values, one store with a cell level ACL, and one
216     // without
217     verifyAllowed(new AccessTestAction() {
218       @Override
219       public Object run() throws Exception {
220         try(Connection conn = ConnectionFactory.createConnection(conf);
221             Table t = conn.getTable(testTable.getTableName())) {
222           Put p;
223           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
224           p.setACL(writePerms);
225           t.put(p);
226           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
227           p.setACL(readPerms);
228           t.put(p);
229           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
230           p.setACL(writePerms);
231           t.put(p);
232         }
233         return null;
234       }
235     }, USER_OWNER);
236     // Confirm special read access set at cell level
237 
238     verifyAllowed(USER_OTHER, get2, 1);
239     verifyAllowed(GROUP_USER, get2, 1);
240   }
241 
242   private Map<String, Permission> prepareCellPermissions(String[] users, Action... action) {
243     Map<String, Permission> perms = new HashMap<String, Permission>(2);
244     for (String user : users) {
245       perms.put(user, new Permission(action));
246     }
247     return perms;
248   }
249 
250   @Test
251   public void testCellPermissionsWithDeleteMutipleVersions() throws Exception {
252     // table/column/qualifier level permissions
253     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
254     final byte[] TEST_ROW2 = Bytes.toBytes("r2");
255     final byte[] TEST_Q1 = Bytes.toBytes("q1");
256     final byte[] TEST_Q2 = Bytes.toBytes("q2");
257     final byte[] ZERO = Bytes.toBytes(0L);
258 
259     // additional test user
260     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
261     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
262 
263     verifyAllowed(new AccessTestAction() {
264       @Override
265       public Object run() throws Exception {
266         try (Connection connection = ConnectionFactory.createConnection(conf)) {
267           try (Table t = connection.getTable(testTable.getTableName())) {
268             // with rw ACL for "user1"
269             Put p = new Put(TEST_ROW1);
270             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
271             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
272             p.setACL(user1.getShortName(), new Permission(Permission.Action.READ,
273                 Permission.Action.WRITE));
274             t.put(p);
275             // with rw ACL for "user1"
276             p = new Put(TEST_ROW2);
277             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
278             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
279             p.setACL(user1.getShortName(), new Permission(Permission.Action.READ,
280                 Permission.Action.WRITE));
281             t.put(p);
282           }
283         }
284         return null;
285       }
286     }, USER_OWNER);
287 
288     verifyAllowed(new AccessTestAction() {
289       @Override
290       public Object run() throws Exception {
291         try (Connection connection = ConnectionFactory.createConnection(conf)) {
292           try (Table t = connection.getTable(testTable.getTableName())) {
293             // with rw ACL for "user1", "user2" and "@group"
294             Put p = new Put(TEST_ROW1);
295             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
296             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
297             Map<String, Permission> perms =
298                 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
299                     AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE);
300             p.setACL(perms);
301             t.put(p);
302             // with rw ACL for "user1", "user2" and "@group"
303             p = new Put(TEST_ROW2);
304             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
305             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
306             p.setACL(perms);
307             t.put(p);
308           }
309         }
310         return null;
311       }
312     }, user1);
313 
314     // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both
315     // versions of the cells
316     user1.runAs(new PrivilegedExceptionAction<Void>() {
317       @Override
318       public Void run() throws Exception {
319         try (Connection connection = ConnectionFactory.createConnection(conf)) {
320           try (Table t = connection.getTable(testTable.getTableName())) {
321             Delete d = new Delete(TEST_ROW1);
322             d.deleteColumns(TEST_FAMILY1, TEST_Q1);
323             d.deleteColumns(TEST_FAMILY1, TEST_Q2);
324             t.delete(d);
325           }
326         }
327         return null;
328       }
329     });
330     // user2 should not be allowed to delete TEST_ROW2 as he is having write permission only on one
331     // version of the cells.
332     verifyUserDeniedForDeleteMultipleVersions(user2, TEST_ROW2, TEST_Q1, TEST_Q2);
333 
334     // GROUP_USER should not be allowed to delete TEST_ROW2 as he is having write permission only on
335     // one version of the cells.
336     verifyUserDeniedForDeleteMultipleVersions(GROUP_USER, TEST_ROW2, TEST_Q1, TEST_Q2);
337 
338     // user1 should be allowed to delete the cf. (All data under cf for a row)
339     user1.runAs(new PrivilegedExceptionAction<Void>() {
340       @Override
341       public Void run() throws Exception {
342         try (Connection connection = ConnectionFactory.createConnection(conf)) {
343           try (Table t = connection.getTable(testTable.getTableName())) {
344             Delete d = new Delete(TEST_ROW2);
345             d.deleteFamily(TEST_FAMILY1);
346             t.delete(d);
347           }
348         }
349         return null;
350       }
351     });
352   }
353 
354   private void verifyUserDeniedForDeleteMultipleVersions(final User user, final byte[] row,
355       final byte[] q1, final byte[] q2) throws IOException, InterruptedException {
356     user.runAs(new PrivilegedExceptionAction<Void>() {
357       @Override
358       public Void run() throws Exception {
359         try (Connection connection = ConnectionFactory.createConnection(conf)) {
360           try (Table t = connection.getTable(testTable.getTableName())) {
361             Delete d = new Delete(row);
362             d.addColumns(TEST_FAMILY1, q1);
363             d.addColumns(TEST_FAMILY1, q2);
364             t.delete(d);
365             fail(user.getShortName() + " should not be allowed to delete the row");
366           } catch (Exception e) {
367 
368           }
369         }
370         return null;
371       }
372     });
373   }
374 
375 
376   @Test
377   public void testDeleteWithFutureTimestamp() throws Exception {
378     // Store two values, one in the future
379 
380     verifyAllowed(new AccessTestAction() {
381       @Override
382       public Object run() throws Exception {
383         try (Connection connection = ConnectionFactory.createConnection(conf)) {
384           try (Table t = connection.getTable(testTable.getTableName())) {
385             // Store a read write ACL without a timestamp, server will use current time
386             Put p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q2, ONE);
387             Map<String, Permission> readAndWritePerms =
388                 prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE);
389             p.setACL(readAndWritePerms);
390             t.put(p);
391             p = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q2, ONE);
392             p.setACL(readAndWritePerms);
393             t.put(p);
394             LOG.info("Stored at current time");
395             // Store read only ACL at a future time
396             p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1,
397                 EnvironmentEdgeManager.currentTime() + 1000000, ZERO);
398             p.setACL(prepareCellPermissions(new String[]{ USER_OTHER.getShortName(),
399                 AuthUtil.toGroupEntry(GROUP)}, Action.READ));
400             t.put(p);
401           }
402         }
403         return null;
404       }
405     }, USER_OWNER);
406 
407     // Confirm stores are visible
408 
409     AccessTestAction getQ1 = new AccessTestAction() {
410       @Override
411       public Object run() throws Exception {
412         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1);
413         try (Connection connection = ConnectionFactory.createConnection(conf)) {
414           try (Table t = connection.getTable(testTable.getTableName())) {
415             return t.get(get).listCells();
416           }
417         }
418       }
419     };
420 
421     AccessTestAction getQ2 = new AccessTestAction() {
422       @Override
423       public Object run() throws Exception {
424         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q2);
425         try (Connection connection = ConnectionFactory.createConnection(conf)) {
426           try (Table t = connection.getTable(testTable.getTableName())) {
427             return t.get(get).listCells();
428           }
429         }
430       }
431     };
432 
433     verifyAllowed(getQ1, USER_OWNER, USER_OTHER, GROUP_USER);
434     verifyAllowed(getQ2, USER_OWNER, USER_OTHER, GROUP_USER);
435 
436 
437     // Issue a DELETE for the family, should succeed because the future ACL is
438     // not considered
439     AccessTestAction deleteFamily1 = getDeleteFamilyAction(TEST_FAMILY1);
440     AccessTestAction deleteFamily2 = getDeleteFamilyAction(TEST_FAMILY2);
441 
442     verifyAllowed(deleteFamily1, USER_OTHER);
443     verifyAllowed(deleteFamily2, GROUP_USER);
444 
445     // The future put should still exist
446 
447     verifyAllowed(getQ1, USER_OWNER, USER_OTHER,GROUP_USER);
448 
449     // The other put should be covered by the tombstone
450 
451     verifyIfNull(getQ2, USER_OTHER, GROUP_USER);
452   }
453 
454   private AccessTestAction getDeleteFamilyAction(final byte[] fam) {
455     AccessTestAction deleteFamilyAction = new AccessTestAction() {
456       @Override
457       public Object run() throws Exception {
458         Delete delete = new Delete(TEST_ROW).addFamily(fam);
459         try (Connection connection = ConnectionFactory.createConnection(conf)) {
460           try (Table t = connection.getTable(testTable.getTableName())) {
461             t.delete(delete);
462           }
463         }
464         return null;
465       }
466     };
467     return deleteFamilyAction;
468   }
469 
470   @Test
471   public void testCellPermissionsWithDeleteWithUserTs() throws Exception {
472     USER_OWNER.runAs(new AccessTestAction() {
473       @Override
474       public Object run() throws Exception {
475         try (Connection connection = ConnectionFactory.createConnection(conf)) {
476           try (Table t = connection.getTable(testTable.getTableName())) {
477             // This version (TS = 123) with rw ACL for USER_OTHER and USER_OTHER2
478             Put p = new Put(TEST_ROW);
479             p.add(TEST_FAMILY1, TEST_Q1, 123L, ZERO);
480             p.add(TEST_FAMILY1, TEST_Q2, 123L, ZERO);
481             p.setACL(prepareCellPermissions(
482               new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP),
483                   USER_OTHER2.getShortName() }, Permission.Action.READ, Permission.Action.WRITE));
484             t.put(p);
485 
486             // This version (TS = 125) with rw ACL for USER_OTHER
487             p = new Put(TEST_ROW);
488             p.add(TEST_FAMILY1, TEST_Q1, 125L, ONE);
489             p.add(TEST_FAMILY1, TEST_Q2, 125L, ONE);
490             p.setACL(prepareCellPermissions(
491               new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) },
492               Action.READ, Action.WRITE));
493             t.put(p);
494 
495             // This version (TS = 127) with rw ACL for USER_OTHER
496             p = new Put(TEST_ROW);
497             p.add(TEST_FAMILY1, TEST_Q1, 127L, TWO);
498             p.add(TEST_FAMILY1, TEST_Q2, 127L, TWO);
499             p.setACL(prepareCellPermissions(
500               new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) },
501               Action.READ, Action.WRITE));
502             t.put(p);
503 
504             return null;
505           }
506         }
507       }
508     });
509 
510     // USER_OTHER2 should be allowed to delete the column f1:q1 versions older than TS 124L
511     USER_OTHER2.runAs(new AccessTestAction() {
512       @Override
513       public Object run() throws Exception {
514         try (Connection connection = ConnectionFactory.createConnection(conf)) {
515           try (Table t = connection.getTable(testTable.getTableName())) {
516             Delete d = new Delete(TEST_ROW, 124L);
517             d.deleteColumns(TEST_FAMILY1, TEST_Q1);
518             t.delete(d);
519           }
520         }
521         return null;
522       }
523     });
524 
525     // USER_OTHER2 should be allowed to delete the column f1:q2 versions older than TS 124L
526     USER_OTHER2.runAs(new AccessTestAction() {
527       @Override
528       public Object run() throws Exception {
529         try (Connection connection = ConnectionFactory.createConnection(conf)) {
530           try (Table t = connection.getTable(testTable.getTableName())) {
531             Delete d = new Delete(TEST_ROW);
532             d.deleteColumns(TEST_FAMILY1, TEST_Q2, 124L);
533             t.delete(d);
534           }
535         }
536         return null;
537       }
538     });
539   }
540 
541   @Test
542   public void testCellPermissionsWithDeleteExactVersion() throws Exception {
543     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
544     final byte[] TEST_Q1 = Bytes.toBytes("q1");
545     final byte[] TEST_Q2 = Bytes.toBytes("q2");
546     final byte[] ZERO = Bytes.toBytes(0L);
547 
548     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
549     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
550 
551     verifyAllowed(new AccessTestAction() {
552       @Override
553       public Object run() throws Exception {
554         try (Connection connection = ConnectionFactory.createConnection(conf)) {
555           try (Table t = connection.getTable(testTable.getTableName())) {
556             Map<String, Permission> permsU1andOwner =
557                 prepareCellPermissions(
558                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
559                   Action.WRITE);
560             Map<String, Permission> permsU2andGUandOwner =
561                 prepareCellPermissions(
562                   new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP),
563                       USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
564             Put p = new Put(TEST_ROW1);
565             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
566             p.setACL(permsU1andOwner);
567             t.put(p);
568             p = new Put(TEST_ROW1);
569             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
570             p.setACL(permsU2andGUandOwner);
571             t.put(p);
572             p = new Put(TEST_ROW1);
573             p.add(TEST_FAMILY2, TEST_Q1, 123, ZERO);
574             p.add(TEST_FAMILY2, TEST_Q2, 123, ZERO);
575             p.setACL(permsU2andGUandOwner);
576             t.put(p);
577 
578             p = new Put(TEST_ROW1);
579             p.add(TEST_FAMILY2, TEST_Q1, 125, ZERO);
580             p.add(TEST_FAMILY2, TEST_Q2, 125, ZERO);
581             p.setACL(permsU1andOwner);
582             t.put(p);
583 
584             p = new Put(TEST_ROW1);
585             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
586             p.setACL(permsU2andGUandOwner);
587             t.put(p);
588             p = new Put(TEST_ROW1);
589             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
590             p.setACL(permsU1andOwner);
591             t.put(p);
592             p = new Put(TEST_ROW1);
593             p.add(TEST_FAMILY2, TEST_Q1, 129, ZERO);
594             p.add(TEST_FAMILY2, TEST_Q2, 129, ZERO);
595             p.setACL(permsU1andOwner);
596             t.put(p);
597           }
598         }
599         return null;
600       }
601     }, USER_OWNER);
602 
603     // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both
604     // versions of the cells
605     user1.runAs(new PrivilegedExceptionAction<Void>() {
606       @Override
607       public Void run() throws Exception {
608         try (Connection connection = ConnectionFactory.createConnection(conf)) {
609           try (Table t = connection.getTable(testTable.getTableName())) {
610             Delete d = new Delete(TEST_ROW1);
611             d.deleteColumn(TEST_FAMILY1, TEST_Q1, 123);
612             d.deleteColumn(TEST_FAMILY1, TEST_Q2);
613             d.deleteFamilyVersion(TEST_FAMILY2, 125);
614             t.delete(d);
615           }
616         }
617         return null;
618       }
619     });
620 
621     verifyUserDeniedForDeleteExactVersion(user2, TEST_ROW1, TEST_Q1, TEST_Q2);
622     verifyUserDeniedForDeleteExactVersion(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2);
623   }
624 
625   private void verifyUserDeniedForDeleteExactVersion(final User user, final byte[] row,
626       final byte[] q1, final byte[] q2) throws IOException, InterruptedException {
627     user.runAs(new PrivilegedExceptionAction<Void>() {
628       @Override
629       public Void run() throws Exception {
630         try (Connection connection = ConnectionFactory.createConnection(conf)) {
631           try (Table t = connection.getTable(testTable.getTableName())) {
632             Delete d = new Delete(row, 127);
633             d.addColumns(TEST_FAMILY1, q1);
634             d.addColumns(TEST_FAMILY1, q2);
635             d.addFamily(TEST_FAMILY2, 129);
636             t.delete(d);
637             fail(user.getShortName() + " can not do the delete");
638           } catch (Exception e) {
639 
640           }
641         }
642         return null;
643       }
644     });
645   }
646 
647   @Test
648   public void testCellPermissionsForIncrementWithMultipleVersions() throws Exception {
649     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
650     final byte[] TEST_Q1 = Bytes.toBytes("q1");
651     final byte[] TEST_Q2 = Bytes.toBytes("q2");
652     final byte[] ZERO = Bytes.toBytes(0L);
653 
654     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
655     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
656 
657     verifyAllowed(new AccessTestAction() {
658       @Override
659       public Object run() throws Exception {
660         try (Connection connection = ConnectionFactory.createConnection(conf)) {
661           try (Table t = connection.getTable(testTable.getTableName())) {
662             Map<String, Permission> permsU1andOwner =
663                 prepareCellPermissions(
664                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
665                   Action.WRITE);
666             Map<String, Permission> permsU2andGUandOwner =
667                 prepareCellPermissions(
668                   new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP),
669                       USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
670             Put p = new Put(TEST_ROW1);
671             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
672             p.setACL(permsU1andOwner);
673             t.put(p);
674             p = new Put(TEST_ROW1);
675             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
676             p.setACL(permsU2andGUandOwner);
677             t.put(p);
678 
679             p = new Put(TEST_ROW1);
680             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
681             p.setACL(permsU2andGUandOwner);
682             t.put(p);
683             p = new Put(TEST_ROW1);
684             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
685             p.setACL(permsU1andOwner);
686             t.put(p);
687           }
688         }
689         return null;
690       }
691     }, USER_OWNER);
692 
693     // Increment considers the TimeRange set on it.
694     user1.runAs(new PrivilegedExceptionAction<Void>() {
695       @Override
696       public Void run() throws Exception {
697         try (Connection connection = ConnectionFactory.createConnection(conf)) {
698           try (Table t = connection.getTable(testTable.getTableName())) {
699             Increment inc = new Increment(TEST_ROW1);
700             inc.setTimeRange(0, 123);
701             inc.addColumn(TEST_FAMILY1, TEST_Q1, 2L);
702             t.increment(inc);
703             t.incrementColumnValue(TEST_ROW1, TEST_FAMILY1, TEST_Q2, 1L);
704           }
705         }
706         return null;
707       }
708     });
709 
710     verifyUserDeniedForIncrementMultipleVersions(user2, TEST_ROW1, TEST_Q2);
711     verifyUserDeniedForIncrementMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q2);
712   }
713 
714   private void verifyUserDeniedForIncrementMultipleVersions(final User user, final byte[] row,
715       final byte[] q1) throws IOException, InterruptedException {
716     user.runAs(new PrivilegedExceptionAction<Void>() {
717       @Override
718       public Void run() throws Exception {
719         try (Connection connection = ConnectionFactory.createConnection(conf)) {
720           try (Table t = connection.getTable(testTable.getTableName())) {
721             Increment inc = new Increment(row);
722             inc.setTimeRange(0, 127);
723             inc.addColumn(TEST_FAMILY1, q1, 2L);
724             t.increment(inc);
725             fail(user.getShortName() + " cannot do the increment.");
726           } catch (Exception e) {
727 
728           }
729         }
730         return null;
731       }
732     });
733   }
734 
735   @Test
736   public void testCellPermissionsForPutWithMultipleVersions() throws Exception {
737     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
738     final byte[] TEST_Q1 = Bytes.toBytes("q1");
739     final byte[] TEST_Q2 = Bytes.toBytes("q2");
740     final byte[] ZERO = Bytes.toBytes(0L);
741 
742     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
743     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
744 
745     verifyAllowed(new AccessTestAction() {
746       @Override
747       public Object run() throws Exception {
748         try (Connection connection = ConnectionFactory.createConnection(conf)) {
749           try (Table t = connection.getTable(testTable.getTableName())) {
750             Map<String, Permission> permsU1andOwner =
751                 prepareCellPermissions(
752                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
753                   Action.WRITE);
754             Map<String, Permission> permsU2andGUandOwner =
755                 prepareCellPermissions(
756                   new String[] { user1.getShortName(), AuthUtil.toGroupEntry(GROUP),
757                       USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
758             permsU2andGUandOwner.put(user2.getShortName(), new Permission(Permission.Action.READ,
759                 Permission.Action.WRITE));
760             permsU2andGUandOwner.put(USER_OWNER.getShortName(),
761                 new Permission(Permission.Action.READ, Permission.Action.WRITE));
762             Put p = new Put(TEST_ROW1);
763             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
764             p.setACL(permsU1andOwner);
765             t.put(p);
766             p = new Put(TEST_ROW1);
767             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
768             p.setACL(permsU2andGUandOwner);
769             t.put(p);
770 
771             p = new Put(TEST_ROW1);
772             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
773             p.setACL(permsU2andGUandOwner);
774             t.put(p);
775             p = new Put(TEST_ROW1);
776             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
777             p.setACL(permsU1andOwner);
778             t.put(p);
779           }
780         }
781         return null;
782       }
783     }, USER_OWNER);
784 
785     // new Put with TEST_Q1 column having TS=125. This covers old cell with TS 123 and user1 is
786     // having RW permission. While TEST_Q2 is with latest TS and so it covers old cell with TS 127.
787     // User1 is having RW permission on that too.
788     user1.runAs(new PrivilegedExceptionAction<Void>() {
789       @Override
790       public Void run() throws Exception {
791         try (Connection connection = ConnectionFactory.createConnection(conf)) {
792           try (Table t = connection.getTable(testTable.getTableName())) {
793             Put p = new Put(TEST_ROW1);
794             p.add(TEST_FAMILY1, TEST_Q1, 125, ZERO);
795             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
796             p.setACL(user2.getShortName(), new Permission(Permission.Action.READ,
797                 Permission.Action.WRITE));
798             t.put(p);
799           }
800         }
801         return null;
802       }
803     });
804 
805     verifyUserDeniedForPutMultipleVersions(user2, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO);
806     verifyUserDeniedForPutMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO);
807   }
808 
809   private void verifyUserDeniedForPutMultipleVersions(final User user, final byte[] row,
810       final byte[] q1, final byte[] q2, final byte[] value) throws IOException,
811       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, Permission> permsU1andOwner =
847                 prepareCellPermissions(
848                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
849                   Action.WRITE);
850             Map<String, Permission> permsU1andU2andGUandOwner =
851                 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
852                     AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, Action.READ,
853                   Action.WRITE);
854             Map<String, Permission> permsU1_U2andGU =
855                 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
856                     AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE);
857 
858             Put p = new Put(TEST_ROW1);
859             p.add(TEST_FAMILY1, TEST_Q1, 120, ZERO);
860             p.add(TEST_FAMILY1, TEST_Q2, 120, ZERO);
861             p.add(TEST_FAMILY1, TEST_Q3, 120, ZERO);
862             p.setACL(permsU1andU2andGUandOwner);
863             t.put(p);
864 
865             p = new Put(TEST_ROW1);
866             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
867             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
868             p.add(TEST_FAMILY1, TEST_Q3, 123, ZERO);
869             p.setACL(permsU1andOwner);
870             t.put(p);
871 
872             p = new Put(TEST_ROW1);
873             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
874             p.setACL(permsU1_U2andGU);
875             t.put(p);
876 
877             p = new Put(TEST_ROW1);
878             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
879             p.setACL(user2.getShortName(), new Permission(Permission.Action.READ));
880             t.put(p);
881 
882             p = new Put(TEST_ROW1);
883             p.addColumn(TEST_FAMILY1, TEST_Q3, 127, ZERO);
884             p.setACL(AuthUtil.toGroupEntry(GROUP), new Permission(Permission.Action.READ));
885             t.put(p);
886           }
887         }
888         return null;
889       }
890     }, USER_OWNER);
891 
892     // user1 should be allowed to do the checkAndDelete. user1 having read permission on the latest
893     // version cell and write permission on all versions
894     user1.runAs(new PrivilegedExceptionAction<Void>() {
895       @Override
896       public Void run() throws Exception {
897         try (Connection connection = ConnectionFactory.createConnection(conf)) {
898           try (Table t = connection.getTable(testTable.getTableName())) {
899             Delete d = new Delete(TEST_ROW1);
900             d.deleteColumns(TEST_FAMILY1, TEST_Q1, 120);
901             t.checkAndDelete(TEST_ROW1, TEST_FAMILY1, TEST_Q1, ZERO, d);
902           }
903         }
904         return null;
905       }
906     });
907     // user2 shouldn't be allowed to do the checkAndDelete. user2 having RW permission on the latest
908     // version cell but not on cell version TS=123
909     verifyUserDeniedForCheckAndDelete(user2, TEST_ROW1, ZERO);
910 
911     // GROUP_USER shouldn't be allowed to do the checkAndDelete. GROUP_USER having RW permission on
912     // the latest
913     // version cell but not on cell version TS=123
914     verifyUserDeniedForCheckAndDelete(GROUP_USER, TEST_ROW1, ZERO);
915 
916     // user2 should be allowed to do the checkAndDelete when delete tries to delete the old version
917     // TS=120. user2 having R permission on the latest version(no W permission) cell
918     // and W permission on cell version TS=120.
919     verifyUserAllowedforCheckAndDelete(user2, TEST_ROW1, TEST_Q2, ZERO);
920 
921     // GROUP_USER should be allowed to do the checkAndDelete when delete tries to delete the old
922     // 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(GROUP_USER, TEST_ROW1, TEST_Q3, ZERO);
926   }
927 
928   private void verifyUserAllowedforCheckAndDelete(final User user, final byte[] row,
929       final byte[] q1, final byte[] value) throws IOException, InterruptedException {
930     user.runAs(new PrivilegedExceptionAction<Void>() {
931       @Override
932       public Void run() throws Exception {
933         try (Connection connection = ConnectionFactory.createConnection(conf)) {
934           try (Table t = connection.getTable(testTable.getTableName())) {
935             Delete d = new Delete(row);
936             d.addColumn(TEST_FAMILY1, q1, 120);
937             t.checkAndDelete(row, TEST_FAMILY1, q1, value, d);
938           }
939         }
940         return null;
941       }
942     });
943   }
944 
945   private void verifyUserDeniedForCheckAndDelete(final User user, final byte[] row,
946       final byte[] value) throws IOException, InterruptedException {
947     user.runAs(new PrivilegedExceptionAction<Void>() {
948       @Override
949       public Void run() throws Exception {
950         try (Connection connection = ConnectionFactory.createConnection(conf)) {
951           try (Table t = connection.getTable(testTable.getTableName())) {
952             Delete d = new Delete(row);
953             d.addColumns(TEST_FAMILY1, TEST_Q1);
954             t.checkAndDelete(row, TEST_FAMILY1, TEST_Q1, value, d);
955             fail(user.getShortName() + " should not be allowed to do checkAndDelete");
956           } catch (Exception e) {
957           }
958         }
959         return null;
960       }
961     });
962   }
963 
964   @After
965   public void tearDown() throws Exception {
966     // Clean the _acl_ table
967     try {
968       TEST_UTIL.deleteTable(testTable.getTableName());
969     } catch (TableNotFoundException ex) {
970       // Test deleted the table, no problem
971       LOG.info("Test deleted table " + testTable.getTableName());
972     }
973     assertEquals(0, AccessControlLists.getTablePermissions(conf, testTable.getTableName()).size());
974   }
975 }