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.Connection;
39  import org.apache.hadoop.hbase.client.ConnectionFactory;
40  import org.apache.hadoop.hbase.client.Delete;
41  import org.apache.hadoop.hbase.client.Get;
42  import org.apache.hadoop.hbase.client.Increment;
43  import org.apache.hadoop.hbase.client.Put;
44  import org.apache.hadoop.hbase.client.Table;
45  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
46  import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
47  import org.apache.hadoop.hbase.security.User;
48  import org.apache.hadoop.hbase.security.access.Permission.Action;
49  import org.apache.hadoop.hbase.util.Bytes;
50  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
51  import org.apache.hadoop.hbase.util.TestTableName;
52  import org.apache.hadoop.hbase.util.Threads;
53  import org.apache.log4j.Level;
54  import org.apache.log4j.Logger;
55  import org.junit.After;
56  import org.junit.AfterClass;
57  import org.junit.Before;
58  import org.junit.BeforeClass;
59  import org.junit.Rule;
60  import org.junit.Test;
61  import org.junit.experimental.categories.Category;
62  
63  @Category(MediumTests.class)
64  public class TestCellACLWithMultipleVersions extends SecureTestUtil {
65    private static final Log LOG = LogFactory.getLog(TestCellACLWithMultipleVersions.class);
66  
67    static {
68      Logger.getLogger(AccessController.class).setLevel(Level.TRACE);
69      Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE);
70      Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE);
71    }
72  
73    @Rule
74    public TestTableName TEST_TABLE = new TestTableName();
75    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
76    private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1");
77    private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2");
78    private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest");
79    private static final byte[] TEST_Q1 = Bytes.toBytes("q1");
80    private static final byte[] TEST_Q2 = Bytes.toBytes("q2");
81    private static final byte[] ZERO = Bytes.toBytes(0L);
82    private static final byte[] ONE = Bytes.toBytes(1L);
83    private static final byte[] TWO = Bytes.toBytes(2L);
84  
85    private static Configuration conf;
86  
87    private static final String GROUP = "group";
88    private static User GROUP_USER;
89    private static User USER_OWNER;
90    private static User USER_OTHER;
91    private static User USER_OTHER2;
92  
93    private static String[] usersAndGroups;
94  
95    @BeforeClass
96    public static void setupBeforeClass() throws Exception {
97      // setup configuration
98      conf = TEST_UTIL.getConfiguration();
99      // 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 = TEST_UTIL.getMiniHBaseCluster().getMaster()
109         .getMasterCoprocessorHost();
110     cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf);
111     AccessController ac = (AccessController)
112       cpHost.findCoprocessor(AccessController.class.getName());
113     cpHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
114     RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0)
115         .getRegionServerCoprocessorHost();
116     rsHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
117 
118     // Wait for the ACL table to become available
119     TEST_UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME);
120 
121     // create a set of test users
122     USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]);
123     USER_OTHER = User.createUserForTesting(conf, "other", new String[0]);
124     USER_OTHER2 = User.createUserForTesting(conf, "other2", new String[0]);
125     GROUP_USER = User.createUserForTesting(conf, "group_user", new String[] { GROUP });
126 
127     usersAndGroups = new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) };
128   }
129 
130   @AfterClass
131   public static void tearDownAfterClass() throws Exception {
132     TEST_UTIL.shutdownMiniCluster();
133   }
134 
135   @Before
136   public void setUp() throws Exception {
137     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName());
138     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY1);
139     hcd.setMaxVersions(4);
140     htd.setOwner(USER_OWNER);
141     htd.addFamily(hcd);
142     hcd = new HColumnDescriptor(TEST_FAMILY2);
143     hcd.setMaxVersions(4);
144     htd.setOwner(USER_OWNER);
145     htd.addFamily(hcd);
146     // Create the test table (owner added to the _acl_ table)
147     TEST_UTIL.createTable(htd, new byte[][] { Bytes.toBytes("s") });
148     TEST_UTIL.waitUntilAllRegionsAssigned(TEST_TABLE.getTableName());
149     LOG.info("Sleeping a second because of HBASE-12581");
150     Threads.sleep(1000);
151   }
152 
153   @Test
154   public void testCellPermissionwithVersions() throws Exception {
155     // store two sets of values, one store with a cell level ACL, and one
156     // without
157     final Map<String, Permission> writePerms = prepareCellPermissions(usersAndGroups, Action.WRITE);
158     final Map<String, Permission> readPerms = prepareCellPermissions(usersAndGroups, Action.READ);
159     verifyAllowed(new AccessTestAction() {
160       @Override
161       public Object run() throws Exception {
162         try(Connection conn = ConnectionFactory.createConnection(conf);
163             Table t = conn.getTable(TEST_TABLE.getTableName())) {
164           Put p;
165           // with ro ACL
166           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
167           p.setACL(writePerms);
168           t.put(p);
169           // with ro ACL
170           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
171           p.setACL(readPerms);
172           t.put(p);
173           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
174           p.setACL(writePerms);
175           t.put(p);
176           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
177           p.setACL(readPerms);
178           t.put(p);
179           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, 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.setMaxVersions(10);
194         try(Connection conn = ConnectionFactory.createConnection(conf);
195             Table t = conn.getTable(TEST_TABLE.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.setMaxVersions(10);
206         try(Connection conn = ConnectionFactory.createConnection(conf);
207             Table t = conn.getTable(TEST_TABLE.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 conn = ConnectionFactory.createConnection(conf);
223             Table t = conn.getTable(TEST_TABLE.getTableName())) {
224           Put p;
225           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
226           p.setACL(writePerms);
227           t.put(p);
228           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
229           p.setACL(readPerms);
230           t.put(p);
231           p = new Put(TEST_ROW).add(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<String, Permission>(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(TEST_TABLE.getTableName())) {
270             // with rw ACL for "user1"
271             Put p = new Put(TEST_ROW1);
272             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
273             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
274             p.setACL(user1.getShortName(), new Permission(Permission.Action.READ,
275                 Permission.Action.WRITE));
276             t.put(p);
277             // with rw ACL for "user1"
278             p = new Put(TEST_ROW2);
279             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
280             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
281             p.setACL(user1.getShortName(), new Permission(Permission.Action.READ,
282                 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(TEST_TABLE.getTableName())) {
295             // with rw ACL for "user1", "user2" and "@group"
296             Put p = new Put(TEST_ROW1);
297             p.add(TEST_FAMILY1, TEST_Q1, ZERO);
298             p.add(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.add(TEST_FAMILY1, TEST_Q1, ZERO);
307             p.add(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(TEST_TABLE.getTableName())) {
323             Delete d = new Delete(TEST_ROW1);
324             d.deleteColumns(TEST_FAMILY1, TEST_Q1);
325             d.deleteColumns(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(TEST_TABLE.getTableName())) {
346             Delete d = new Delete(TEST_ROW2);
347             d.deleteFamily(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(TEST_TABLE.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 
378   @Test
379   public void testDeleteWithFutureTimestamp() throws Exception {
380     // Store two values, one in the future
381 
382     verifyAllowed(new AccessTestAction() {
383       @Override
384       public Object run() throws Exception {
385         try (Connection connection = ConnectionFactory.createConnection(conf)) {
386           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
387             // Store a read write ACL without a timestamp, server will use current time
388             Put p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q2, ONE);
389             Map<String, Permission> readAndWritePerms =
390                 prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE);
391             p.setACL(readAndWritePerms);
392             t.put(p);
393             p = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q2, ONE);
394             p.setACL(readAndWritePerms);
395             t.put(p);
396             LOG.info("Stored at current time");
397             // Store read only ACL at a future time
398             p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1,
399                 EnvironmentEdgeManager.currentTime() + 1000000, ZERO);
400             p.setACL(prepareCellPermissions(new String[]{ USER_OTHER.getShortName(),
401                 AuthUtil.toGroupEntry(GROUP)}, 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(TEST_TABLE.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(TEST_TABLE.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 
439     // Issue a DELETE for the family, should succeed because the future ACL is
440     // not considered
441     AccessTestAction deleteFamily1 = getDeleteFamilyAction(TEST_FAMILY1);
442     AccessTestAction deleteFamily2 = getDeleteFamilyAction(TEST_FAMILY2);
443 
444     verifyAllowed(deleteFamily1, USER_OTHER);
445     verifyAllowed(deleteFamily2, GROUP_USER);
446 
447     // The future put should still exist
448 
449     verifyAllowed(getQ1, USER_OWNER, USER_OTHER,GROUP_USER);
450 
451     // The other put should be covered by the tombstone
452 
453     verifyIfNull(getQ2, USER_OTHER, GROUP_USER);
454   }
455 
456   private AccessTestAction getDeleteFamilyAction(final byte[] fam) {
457     AccessTestAction deleteFamilyAction = new AccessTestAction() {
458       @Override
459       public Object run() throws Exception {
460         Delete delete = new Delete(TEST_ROW).addFamily(fam);
461         try (Connection connection = ConnectionFactory.createConnection(conf)) {
462           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
463             t.delete(delete);
464           }
465         }
466         return null;
467       }
468     };
469     return deleteFamilyAction;
470   }
471 
472   @Test
473   public void testCellPermissionsWithDeleteWithUserTs() throws Exception {
474     USER_OWNER.runAs(new AccessTestAction() {
475       @Override
476       public Object run() throws Exception {
477         try (Connection connection = ConnectionFactory.createConnection(conf)) {
478           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
479             // This version (TS = 123) with rw ACL for USER_OTHER and USER_OTHER2
480             Put p = new Put(TEST_ROW);
481             p.add(TEST_FAMILY1, TEST_Q1, 123L, ZERO);
482             p.add(TEST_FAMILY1, TEST_Q2, 123L, ZERO);
483             p.setACL(prepareCellPermissions(
484               new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP),
485                   USER_OTHER2.getShortName() }, Permission.Action.READ, Permission.Action.WRITE));
486             t.put(p);
487 
488             // This version (TS = 125) with rw ACL for USER_OTHER
489             p = new Put(TEST_ROW);
490             p.add(TEST_FAMILY1, TEST_Q1, 125L, ONE);
491             p.add(TEST_FAMILY1, TEST_Q2, 125L, ONE);
492             p.setACL(prepareCellPermissions(
493               new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) },
494               Action.READ, Action.WRITE));
495             t.put(p);
496 
497             // This version (TS = 127) with rw ACL for USER_OTHER
498             p = new Put(TEST_ROW);
499             p.add(TEST_FAMILY1, TEST_Q1, 127L, TWO);
500             p.add(TEST_FAMILY1, TEST_Q2, 127L, TWO);
501             p.setACL(prepareCellPermissions(
502               new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) },
503               Action.READ, Action.WRITE));
504             t.put(p);
505 
506             return null;
507           }
508         }
509       }
510     });
511 
512     // USER_OTHER2 should be allowed to delete the column f1:q1 versions older than TS 124L
513     USER_OTHER2.runAs(new AccessTestAction() {
514       @Override
515       public Object run() throws Exception {
516         try (Connection connection = ConnectionFactory.createConnection(conf)) {
517           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
518             Delete d = new Delete(TEST_ROW, 124L);
519             d.deleteColumns(TEST_FAMILY1, TEST_Q1);
520             t.delete(d);
521           }
522         }
523         return null;
524       }
525     });
526 
527     // USER_OTHER2 should be allowed to delete the column f1:q2 versions older than TS 124L
528     USER_OTHER2.runAs(new AccessTestAction() {
529       @Override
530       public Object run() throws Exception {
531         try (Connection connection = ConnectionFactory.createConnection(conf)) {
532           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
533             Delete d = new Delete(TEST_ROW);
534             d.deleteColumns(TEST_FAMILY1, TEST_Q2, 124L);
535             t.delete(d);
536           }
537         }
538         return null;
539       }
540     });
541   }
542 
543   @Test
544   public void testCellPermissionsWithDeleteExactVersion() throws Exception {
545     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
546     final byte[] TEST_Q1 = Bytes.toBytes("q1");
547     final byte[] TEST_Q2 = Bytes.toBytes("q2");
548     final byte[] ZERO = Bytes.toBytes(0L);
549 
550     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
551     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
552 
553     verifyAllowed(new AccessTestAction() {
554       @Override
555       public Object run() throws Exception {
556         try (Connection connection = ConnectionFactory.createConnection(conf)) {
557           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
558             Map<String, Permission> permsU1andOwner =
559                 prepareCellPermissions(
560                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
561                   Action.WRITE);
562             Map<String, Permission> permsU2andGUandOwner =
563                 prepareCellPermissions(
564                   new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP),
565                       USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
566             Put p = new Put(TEST_ROW1);
567             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
568             p.setACL(permsU1andOwner);
569             t.put(p);
570             p = new Put(TEST_ROW1);
571             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
572             p.setACL(permsU2andGUandOwner);
573             t.put(p);
574             p = new Put(TEST_ROW1);
575             p.add(TEST_FAMILY2, TEST_Q1, 123, ZERO);
576             p.add(TEST_FAMILY2, TEST_Q2, 123, ZERO);
577             p.setACL(permsU2andGUandOwner);
578             t.put(p);
579 
580             p = new Put(TEST_ROW1);
581             p.add(TEST_FAMILY2, TEST_Q1, 125, ZERO);
582             p.add(TEST_FAMILY2, TEST_Q2, 125, ZERO);
583             p.setACL(permsU1andOwner);
584             t.put(p);
585 
586             p = new Put(TEST_ROW1);
587             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
588             p.setACL(permsU2andGUandOwner);
589             t.put(p);
590             p = new Put(TEST_ROW1);
591             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
592             p.setACL(permsU1andOwner);
593             t.put(p);
594             p = new Put(TEST_ROW1);
595             p.add(TEST_FAMILY2, TEST_Q1, 129, ZERO);
596             p.add(TEST_FAMILY2, TEST_Q2, 129, ZERO);
597             p.setACL(permsU1andOwner);
598             t.put(p);
599           }
600         }
601         return null;
602       }
603     }, USER_OWNER);
604 
605     // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both
606     // versions of the cells
607     user1.runAs(new PrivilegedExceptionAction<Void>() {
608       @Override
609       public Void run() throws Exception {
610         try (Connection connection = ConnectionFactory.createConnection(conf)) {
611           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
612             Delete d = new Delete(TEST_ROW1);
613             d.deleteColumn(TEST_FAMILY1, TEST_Q1, 123);
614             d.deleteColumn(TEST_FAMILY1, TEST_Q2);
615             d.deleteFamilyVersion(TEST_FAMILY2, 125);
616             t.delete(d);
617           }
618         }
619         return null;
620       }
621     });
622 
623     verifyUserDeniedForDeleteExactVersion(user2, TEST_ROW1, TEST_Q1, TEST_Q2);
624     verifyUserDeniedForDeleteExactVersion(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2);
625   }
626 
627   private void verifyUserDeniedForDeleteExactVersion(final User user, final byte[] row,
628       final byte[] q1, final byte[] q2) throws IOException, InterruptedException {
629     user.runAs(new PrivilegedExceptionAction<Void>() {
630       @Override
631       public Void run() throws Exception {
632         try (Connection connection = ConnectionFactory.createConnection(conf)) {
633           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
634             Delete d = new Delete(row, 127);
635             d.addColumns(TEST_FAMILY1, q1);
636             d.addColumns(TEST_FAMILY1, q2);
637             d.addFamily(TEST_FAMILY2, 129);
638             t.delete(d);
639             fail(user.getShortName() + " can not do the delete");
640           } catch (Exception e) {
641 
642           }
643         }
644         return null;
645       }
646     });
647   }
648 
649   @Test
650   public void testCellPermissionsForIncrementWithMultipleVersions() throws Exception {
651     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
652     final byte[] TEST_Q1 = Bytes.toBytes("q1");
653     final byte[] TEST_Q2 = Bytes.toBytes("q2");
654     final byte[] ZERO = Bytes.toBytes(0L);
655 
656     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
657     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
658 
659     verifyAllowed(new AccessTestAction() {
660       @Override
661       public Object run() throws Exception {
662         try (Connection connection = ConnectionFactory.createConnection(conf)) {
663           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
664             Map<String, Permission> permsU1andOwner =
665                 prepareCellPermissions(
666                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
667                   Action.WRITE);
668             Map<String, Permission> permsU2andGUandOwner =
669                 prepareCellPermissions(
670                   new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP),
671                       USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
672             Put p = new Put(TEST_ROW1);
673             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
674             p.setACL(permsU1andOwner);
675             t.put(p);
676             p = new Put(TEST_ROW1);
677             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
678             p.setACL(permsU2andGUandOwner);
679             t.put(p);
680 
681             p = new Put(TEST_ROW1);
682             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
683             p.setACL(permsU2andGUandOwner);
684             t.put(p);
685             p = new Put(TEST_ROW1);
686             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
687             p.setACL(permsU1andOwner);
688             t.put(p);
689           }
690         }
691         return null;
692       }
693     }, USER_OWNER);
694 
695     // Increment considers the TimeRange set on it.
696     user1.runAs(new PrivilegedExceptionAction<Void>() {
697       @Override
698       public Void run() throws Exception {
699         try (Connection connection = ConnectionFactory.createConnection(conf)) {
700           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
701             Increment inc = new Increment(TEST_ROW1);
702             inc.setTimeRange(0, 123);
703             inc.addColumn(TEST_FAMILY1, TEST_Q1, 2L);
704             t.increment(inc);
705             t.incrementColumnValue(TEST_ROW1, TEST_FAMILY1, TEST_Q2, 1L);
706           }
707         }
708         return null;
709       }
710     });
711 
712     verifyUserDeniedForIncrementMultipleVersions(user2, TEST_ROW1, TEST_Q2);
713     verifyUserDeniedForIncrementMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q2);
714   }
715 
716   private void verifyUserDeniedForIncrementMultipleVersions(final User user, final byte[] row,
717       final byte[] q1) throws IOException, InterruptedException {
718     user.runAs(new PrivilegedExceptionAction<Void>() {
719       @Override
720       public Void run() throws Exception {
721         try (Connection connection = ConnectionFactory.createConnection(conf)) {
722           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
723             Increment inc = new Increment(row);
724             inc.setTimeRange(0, 127);
725             inc.addColumn(TEST_FAMILY1, q1, 2L);
726             t.increment(inc);
727             fail(user.getShortName() + " cannot do the increment.");
728           } catch (Exception e) {
729 
730           }
731         }
732         return null;
733       }
734     });
735   }
736 
737   @Test
738   public void testCellPermissionsForPutWithMultipleVersions() throws Exception {
739     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
740     final byte[] TEST_Q1 = Bytes.toBytes("q1");
741     final byte[] TEST_Q2 = Bytes.toBytes("q2");
742     final byte[] ZERO = Bytes.toBytes(0L);
743 
744     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
745     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
746 
747     verifyAllowed(new AccessTestAction() {
748       @Override
749       public Object run() throws Exception {
750         try (Connection connection = ConnectionFactory.createConnection(conf)) {
751           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
752             Map<String, Permission> permsU1andOwner =
753                 prepareCellPermissions(
754                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
755                   Action.WRITE);
756             Map<String, Permission> permsU2andGUandOwner =
757                 prepareCellPermissions(
758                   new String[] { user1.getShortName(), AuthUtil.toGroupEntry(GROUP),
759                       USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
760             permsU2andGUandOwner.put(user2.getShortName(), new Permission(Permission.Action.READ,
761                 Permission.Action.WRITE));
762             permsU2andGUandOwner.put(USER_OWNER.getShortName(), new Permission(Permission.Action.READ,
763                 Permission.Action.WRITE));
764             Put p = new Put(TEST_ROW1);
765             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
766             p.setACL(permsU1andOwner);
767             t.put(p);
768             p = new Put(TEST_ROW1);
769             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
770             p.setACL(permsU2andGUandOwner);
771             t.put(p);
772 
773             p = new Put(TEST_ROW1);
774             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
775             p.setACL(permsU2andGUandOwner);
776             t.put(p);
777             p = new Put(TEST_ROW1);
778             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
779             p.setACL(permsU1andOwner);
780             t.put(p);
781           }
782         }
783         return null;
784       }
785     }, USER_OWNER);
786 
787     // new Put with TEST_Q1 column having TS=125. This covers old cell with TS 123 and user1 is
788     // having RW permission. While TEST_Q2 is with latest TS and so it covers old cell with TS 127.
789     // User1 is having RW permission on that too.
790     user1.runAs(new PrivilegedExceptionAction<Void>() {
791       @Override
792       public Void run() throws Exception {
793         try (Connection connection = ConnectionFactory.createConnection(conf)) {
794           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
795             Put p = new Put(TEST_ROW1);
796             p.add(TEST_FAMILY1, TEST_Q1, 125, ZERO);
797             p.add(TEST_FAMILY1, TEST_Q2, ZERO);
798             p.setACL(user2.getShortName(), new Permission(Permission.Action.READ,
799                 Permission.Action.WRITE));
800             t.put(p);
801           }
802         }
803         return null;
804       }
805     });
806 
807     verifyUserDeniedForPutMultipleVersions(user2, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO);
808     verifyUserDeniedForPutMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO);
809   }
810 
811   private void verifyUserDeniedForPutMultipleVersions(final User user, final byte[] row,
812       final byte[] q1, final byte[] q2, final byte[] value) throws IOException,
813       InterruptedException {
814     user.runAs(new PrivilegedExceptionAction<Void>() {
815       @Override
816       public Void run() throws Exception {
817         try (Connection connection = ConnectionFactory.createConnection(conf)) {
818           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
819             Put p = new Put(row);
820             // column Q1 covers version at 123 fr which user2 do not have permission
821             p.addColumn(TEST_FAMILY1, q1, 124, value);
822             p.addColumn(TEST_FAMILY1, q2, value);
823             t.put(p);
824             fail(user.getShortName() + " cannot do the put.");
825           } catch (Exception e) {
826 
827           }
828         }
829         return null;
830       }
831     });
832   }
833 
834   @Test
835   public void testCellPermissionsForCheckAndDelete() throws Exception {
836     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
837     final byte[] TEST_Q3 = Bytes.toBytes("q3");
838     final byte[] ZERO = Bytes.toBytes(0L);
839 
840     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
841     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
842 
843     verifyAllowed(new AccessTestAction() {
844       @Override
845       public Object run() throws Exception {
846         try (Connection connection = ConnectionFactory.createConnection(conf)) {
847           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
848             Map<String, Permission> permsU1andOwner =
849                 prepareCellPermissions(
850                   new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
851                   Action.WRITE);
852             Map<String, Permission> permsU1andU2andGUandOwner =
853                 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
854                     AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, Action.READ,
855                   Action.WRITE);
856             Map<String, Permission> permsU1_U2andGU =
857                 prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
858                     AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE);
859 
860             Put p = new Put(TEST_ROW1);
861             p.add(TEST_FAMILY1, TEST_Q1, 120, ZERO);
862             p.add(TEST_FAMILY1, TEST_Q2, 120, ZERO);
863             p.add(TEST_FAMILY1, TEST_Q3, 120, ZERO);
864             p.setACL(permsU1andU2andGUandOwner);
865             t.put(p);
866 
867             p = new Put(TEST_ROW1);
868             p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
869             p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
870             p.add(TEST_FAMILY1, TEST_Q3, 123, ZERO);
871             p.setACL(permsU1andOwner);
872             t.put(p);
873 
874             p = new Put(TEST_ROW1);
875             p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
876             p.setACL(permsU1_U2andGU);
877             t.put(p);
878 
879             p = new Put(TEST_ROW1);
880             p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
881             p.setACL(user2.getShortName(), new Permission(Permission.Action.READ));
882             t.put(p);
883 
884             p = new Put(TEST_ROW1);
885             p.addColumn(TEST_FAMILY1, TEST_Q3, 127, ZERO);
886             p.setACL(AuthUtil.toGroupEntry(GROUP), new Permission(Permission.Action.READ));
887             t.put(p);
888           }
889         }
890         return null;
891       }
892     }, USER_OWNER);
893 
894     // user1 should be allowed to do the checkAndDelete. user1 having read permission on the latest
895     // version cell and write permission on all versions
896     user1.runAs(new PrivilegedExceptionAction<Void>() {
897       @Override
898       public Void run() throws Exception {
899         try (Connection connection = ConnectionFactory.createConnection(conf)) {
900           try (Table t = connection.getTable(TEST_TABLE.getTableName())) {
901             Delete d = new Delete(TEST_ROW1);
902             d.deleteColumns(TEST_FAMILY1, TEST_Q1, 120);
903             t.checkAndDelete(TEST_ROW1, TEST_FAMILY1, TEST_Q1, ZERO, 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(TEST_TABLE.getTableName())) {
937             Delete d = new Delete(row);
938             d.addColumn(TEST_FAMILY1, q1, 120);
939             t.checkAndDelete(row, TEST_FAMILY1, q1, value, 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(TEST_TABLE.getTableName())) {
954             Delete d = new Delete(row);
955             d.addColumns(TEST_FAMILY1, TEST_Q1);
956             t.checkAndDelete(row, TEST_FAMILY1, TEST_Q1, value, 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(TEST_TABLE.getTableName());
971     } catch (TableNotFoundException ex) {
972       // Test deleted the table, no problem
973       LOG.info("Test deleted table " + TEST_TABLE.getTableName());
974     }
975     assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size());
976   }
977 }