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