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.TableNotFoundException;
37  import org.apache.hadoop.hbase.client.Delete;
38  import org.apache.hadoop.hbase.client.Get;
39  import org.apache.hadoop.hbase.client.HBaseAdmin;
40  import org.apache.hadoop.hbase.client.HTable;
41  import org.apache.hadoop.hbase.client.Increment;
42  import org.apache.hadoop.hbase.client.Put;
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.TestTableName;
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 TestTableName TEST_TABLE = new TestTableName();
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()
107         .getMaster().getCoprocessorHost();
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         .getCoprocessorHost();
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.getName());
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     // Create the test table (owner added to the _acl_ table)
136     HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
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     admin.createTable(htd, new byte[][] { Bytes.toBytes("s") });
147     TEST_UTIL.waitTableEnabled(TEST_TABLE.getTableName().getName());
148   }
149 
150   @Test
151   public void testCellPermissionwithVersions() throws Exception {
152     // store two sets of values, one store with a cell level ACL, and one
153     // without
154     final Map<String, Permission> writePerms = prepareCellPermissions(usersAndGroups, Action.WRITE);
155     final Map<String, Permission> readPerms = prepareCellPermissions(usersAndGroups, Action.READ);
156     verifyAllowed(new AccessTestAction() {
157       @Override
158       public Object run() throws Exception {
159         HTable t = new HTable(conf, TEST_TABLE.getTableName());
160         try {
161           Put p;
162           // with ro ACL
163           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
164           p.setACL(writePerms);
165           t.put(p);
166           // with ro ACL
167           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
168           p.setACL(readPerms);
169           t.put(p);
170           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
171           p.setACL(writePerms);
172           t.put(p);
173           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
174           p.setACL(readPerms);
175           t.put(p);
176           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
177           p.setACL(writePerms);
178           t.put(p);
179         } finally {
180           t.close();
181         }
182         return null;
183       }
184     }, USER_OWNER);
185 
186     /* ---- Gets ---- */
187 
188     AccessTestAction getQ1 = new AccessTestAction() {
189       @Override
190       public Object run() throws Exception {
191         Get get = new Get(TEST_ROW);
192         get.setMaxVersions(10);
193         HTable t = new HTable(conf, TEST_TABLE.getTableName());
194         try {
195           return t.get(get).listCells();
196         } finally {
197           t.close();
198         }
199       }
200     };
201 
202     AccessTestAction get2 = new AccessTestAction() {
203       @Override
204       public Object run() throws Exception {
205         Get get = new Get(TEST_ROW);
206         get.setMaxVersions(10);
207         HTable t = new HTable(conf, TEST_TABLE.getTableName());
208         try {
209           return t.get(get).listCells();
210         } finally {
211           t.close();
212         }
213       }
214     };
215     // Confirm special read access set at cell level
216 
217     verifyAllowed(GROUP_USER, getQ1, 2);
218     verifyAllowed(USER_OTHER, getQ1, 2);
219 
220     // store two sets of values, one store with a cell level ACL, and one
221     // without
222     verifyAllowed(new AccessTestAction() {
223       @Override
224       public Object run() throws Exception {
225         HTable t = new HTable(conf, TEST_TABLE.getTableName());
226         try {
227           Put p;
228           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
229           p.setACL(writePerms);
230           t.put(p);
231           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
232           p.setACL(readPerms);
233           t.put(p);
234           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
235           p.setACL(writePerms);
236           t.put(p);
237         } finally {
238           t.close();
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         HTable t = new HTable(conf, TEST_TABLE.getTableName());
274         try {
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         } finally {
290           t.close();
291         }
292         return null;
293       }
294     }, USER_OWNER);
295 
296     verifyAllowed(new AccessTestAction() {
297       @Override
298       public Object run() throws Exception {
299         HTable t = new HTable(conf, TEST_TABLE.getTableName());
300         try {
301           // with rw ACL for "user1", "user2" and "@group"
302           Put p = new Put(TEST_ROW1);
303           p.add(TEST_FAMILY1, TEST_Q1, ZERO);
304           p.add(TEST_FAMILY1, TEST_Q2, ZERO);
305           Map<String, Permission> perms =
306               prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
307                   AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE);
308           p.setACL(perms);
309           t.put(p);
310           // with rw ACL for "user1" and "user2"
311           p = new Put(TEST_ROW2);
312           p.add(TEST_FAMILY1, TEST_Q1, ZERO);
313           p.add(TEST_FAMILY1, TEST_Q2, ZERO);
314           p.setACL(perms);
315           t.put(p);
316         } finally {
317           t.close();
318         }
319         return null;
320       }
321     }, user1);
322 
323     // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both
324     // versions of the cells
325     user1.runAs(new PrivilegedExceptionAction<Void>() {
326       @Override
327       public Void run() throws Exception {
328         HTable t = new HTable(conf, TEST_TABLE.getTableName());
329         try {
330           Delete d = new Delete(TEST_ROW1);
331           d.deleteColumns(TEST_FAMILY1, TEST_Q1);
332           d.deleteColumns(TEST_FAMILY1, TEST_Q2);
333           t.delete(d);
334         } finally {
335           t.close();
336         }
337         return null;
338       }
339     });
340     // user2 should not be allowed to delete TEST_ROW2 as he is having write permission only on one
341     // version of the cells.
342     verifyUserDeniedForDeleteMultipleVersions(user2, TEST_ROW2, TEST_Q1, TEST_Q2);
343 
344     // GROUP_USER should not be allowed to delete TEST_ROW2 as he is having write permission only on
345     // one version of the cells.
346     verifyUserDeniedForDeleteMultipleVersions(GROUP_USER, TEST_ROW2, TEST_Q1, TEST_Q2);
347 
348     // user1 should be allowed to delete the cf. (All data under cf for a row)
349     user1.runAs(new PrivilegedExceptionAction<Void>() {
350       @Override
351       public Void run() throws Exception {
352         HTable t = new HTable(conf, TEST_TABLE.getTableName());
353         try {
354           Delete d = new Delete(TEST_ROW2);
355           d.deleteFamily(TEST_FAMILY1);
356           t.delete(d);
357         } finally {
358           t.close();
359         }
360         return null;
361       }
362     });
363   }
364 
365   private void verifyUserDeniedForDeleteMultipleVersions(final User user, final byte[] row,
366       final byte[] q1, final byte[] q2) throws IOException, InterruptedException {
367     user.runAs(new PrivilegedExceptionAction<Void>() {
368       @Override
369       public Void run() throws Exception {
370         HTable t = new HTable(conf, TEST_TABLE.getTableName());
371         try {
372           Delete d = new Delete(row);
373           d.deleteColumns(TEST_FAMILY1, q1);
374           d.deleteColumns(TEST_FAMILY1, q2);
375           t.delete(d);
376           fail(user.getShortName() + " should not be allowed to delete the row");
377         } catch (Exception e) {
378 
379         } finally {
380           t.close();
381         }
382         return null;
383       }
384     });
385   }
386 
387   @Test
388   public void testDeleteWithFutureTimestamp() throws Exception {
389     // Store two values, one in the future
390 
391     verifyAllowed(new AccessTestAction() {
392       @Override
393       public Object run() throws Exception {
394         HTable t = new HTable(conf, TEST_TABLE.getTableName());
395         try {
396           // Store read only ACL at a future time
397           Put p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1,
398             EnvironmentEdgeManager.currentTimeMillis() + 1000000,
399             ZERO);
400           Map<String, Permission> readAndWritePerms =
401               prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE);
402           p.setACL(prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE));
403           t.put(p);
404           // Store a read write ACL without a timestamp, server will use current time
405           p = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q2, ONE);
406           p.setACL(readAndWritePerms);
407           t.put(p);
408 
409           p = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q2, ONE);
410           p.setACL(readAndWritePerms);
411           t.put(p);
412 
413         } finally {
414           t.close();
415         }
416         return null;
417       }
418     }, USER_OWNER);
419 
420     // Confirm stores are visible
421 
422     AccessTestAction getQ1 = new AccessTestAction() {
423       @Override
424       public Object run() throws Exception {
425         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q1);
426         HTable t = new HTable(conf, TEST_TABLE.getTableName());
427         try {
428           return t.get(get).listCells();
429         } finally {
430           t.close();
431         }
432       }
433     };
434 
435     AccessTestAction getQ2 = new AccessTestAction() {
436       @Override
437       public Object run() throws Exception {
438         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY1, TEST_Q2);
439         HTable t = new HTable(conf, TEST_TABLE.getTableName());
440         try {
441           return t.get(get).listCells();
442         } finally {
443           t.close();
444         }
445       }
446     };
447 
448     verifyAllowed(getQ1, USER_OWNER, USER_OTHER, GROUP_USER);
449     verifyAllowed(getQ2, USER_OWNER, USER_OTHER, GROUP_USER);
450 
451 
452     // Issue a DELETE for the family, should succeed because the future ACL is
453     // not considered
454 
455     AccessTestAction deleteFamily1 = getDeleteFamilyAction(TEST_FAMILY1);
456     AccessTestAction deleteFamily2 = getDeleteFamilyAction(TEST_FAMILY2);
457 
458     verifyAllowed(deleteFamily1, USER_OTHER);
459     verifyAllowed(deleteFamily2, GROUP_USER);
460 
461     // The future put should still exist
462 
463     verifyAllowed(getQ1, USER_OWNER, USER_OTHER, GROUP_USER);
464 
465     // The other put should be covered by the tombstone
466 
467     verifyIfNull(getQ2, USER_OTHER, GROUP_USER);
468   }
469 
470   private AccessTestAction getDeleteFamilyAction(final byte[] fam) {
471     AccessTestAction deleteFamily = new AccessTestAction() {
472       @Override
473       public Object run() throws Exception {
474         Delete delete = new Delete(TEST_ROW).deleteFamily(fam);
475         HTable t = new HTable(conf, TEST_TABLE.getTableName());
476         try {
477           t.delete(delete);
478         } finally {
479           t.close();
480         }
481         return null;
482       }
483     };
484     return deleteFamily;
485   }
486 
487   @Test
488   public void testCellPermissionsWithDeleteWithUserTs() throws Exception {
489     USER_OWNER.runAs(new AccessTestAction() {
490       @Override
491       public Object run() throws Exception {
492         HTable t = new HTable(conf, TEST_TABLE.getTableName());
493         try {
494           // This version (TS = 123) with rw ACL for USER_OTHER and USER_OTHER2
495           Put p = new Put(TEST_ROW);
496           p.add(TEST_FAMILY1, TEST_Q1, 123L, ZERO);
497           p.add(TEST_FAMILY1, TEST_Q2, 123L, ZERO);
498           p.setACL(prepareCellPermissions(
499             new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP),
500                 USER_OTHER2.getShortName() }, Permission.Action.READ, Permission.Action.WRITE));
501           t.put(p);
502 
503           // This version (TS = 125) with rw ACL for USER_OTHER
504           p = new Put(TEST_ROW);
505           p.add(TEST_FAMILY1, TEST_Q1, 125L, ONE);
506           p.add(TEST_FAMILY1, TEST_Q2, 125L, ONE);
507           p.setACL(prepareCellPermissions(
508             new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) },
509             Action.READ, Action.WRITE));
510           t.put(p);
511 
512           // This version (TS = 127) with rw ACL for USER_OTHER
513           p = new Put(TEST_ROW);
514           p.add(TEST_FAMILY1, TEST_Q1, 127L, TWO);
515           p.add(TEST_FAMILY1, TEST_Q2, 127L, TWO);
516           p.setACL(prepareCellPermissions(
517             new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) },
518             Action.READ, Action.WRITE));
519           t.put(p);
520 
521           return null;
522         } finally {
523           t.close();
524         }
525       }
526     });
527 
528     // USER_OTHER2 should be allowed to delete the column f1:q1 versions older than TS 124L
529     USER_OTHER2.runAs(new AccessTestAction() {
530       @Override
531       public Object run() throws Exception {
532         HTable t = new HTable(conf, TEST_TABLE.getTableName());
533         try {
534           Delete d = new Delete(TEST_ROW, 124L);
535           d.deleteColumns(TEST_FAMILY1, TEST_Q1);
536           t.delete(d);
537         } finally {
538           t.close();
539         }
540         return null;
541       }
542     });
543 
544     // USER_OTHER2 should be allowed to delete the column f1:q2 versions older than TS 124L
545     USER_OTHER2.runAs(new AccessTestAction() {
546       @Override
547       public Object run() throws Exception {
548         HTable t = new HTable(conf, TEST_TABLE.getTableName());
549         try {
550           Delete d = new Delete(TEST_ROW);
551           d.deleteColumns(TEST_FAMILY1, TEST_Q2, 124L);
552           t.delete(d);
553         } finally {
554           t.close();
555         }
556         return null;
557       }
558     });
559   }
560 
561   @Test
562   public void testCellPermissionsWithDeleteExactVersion() throws Exception {
563     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
564     final byte[] TEST_Q1 = Bytes.toBytes("q1");
565     final byte[] TEST_Q2 = Bytes.toBytes("q2");
566     final byte[] ZERO = Bytes.toBytes(0L);
567 
568     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
569     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
570 
571     verifyAllowed(new AccessTestAction() {
572       @Override
573       public Object run() throws Exception {
574         HTable t = new HTable(conf, TEST_TABLE.getTableName());
575         try {
576           Map<String, Permission> permsU1andOwner =
577               prepareCellPermissions(
578                 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
579                 Action.WRITE);
580           Map<String, Permission> permsU2andGUandOwner =
581               prepareCellPermissions(
582                 new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP),
583                     USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
584           Put p = new Put(TEST_ROW1);
585           p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
586           p.setACL(permsU1andOwner);
587           t.put(p);
588           p = new Put(TEST_ROW1);
589           p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
590           p.setACL(permsU2andGUandOwner);
591           t.put(p);
592           p = new Put(TEST_ROW1);
593           p.add(TEST_FAMILY2, TEST_Q1, 123, ZERO);
594           p.add(TEST_FAMILY2, TEST_Q2, 123, ZERO);
595           p.setACL(permsU2andGUandOwner);
596           t.put(p);
597 
598           p = new Put(TEST_ROW1);
599           p.add(TEST_FAMILY2, TEST_Q1, 125, ZERO);
600           p.add(TEST_FAMILY2, TEST_Q2, 125, ZERO);
601           p.setACL(permsU1andOwner);
602           t.put(p);
603 
604           p = new Put(TEST_ROW1);
605           p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
606           p.setACL(permsU2andGUandOwner);
607           t.put(p);
608           p = new Put(TEST_ROW1);
609           p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
610           p.setACL(permsU1andOwner);
611           t.put(p);
612           p = new Put(TEST_ROW1);
613           p.add(TEST_FAMILY2, TEST_Q1, 129, ZERO);
614           p.add(TEST_FAMILY2, TEST_Q2, 129, ZERO);
615           p.setACL(permsU1andOwner);
616           t.put(p);
617         } finally {
618           t.close();
619         }
620         return null;
621       }
622     }, USER_OWNER);
623 
624     // user1 should be allowed to delete TEST_ROW1 as he is having write permission on both
625     // versions of the cells
626     user1.runAs(new PrivilegedExceptionAction<Void>() {
627       @Override
628       public Void run() throws Exception {
629         HTable t = new HTable(conf, TEST_TABLE.getTableName());
630         try {
631           Delete d = new Delete(TEST_ROW1);
632           d.deleteColumn(TEST_FAMILY1, TEST_Q1, 123);
633           d.deleteColumn(TEST_FAMILY1, TEST_Q2);
634           d.deleteFamilyVersion(TEST_FAMILY2, 125);
635           t.delete(d);
636         } finally {
637           t.close();
638         }
639         return null;
640       }
641     });
642 
643     verifyUserDeniedForDeleteExactVersion(user2, TEST_ROW1, TEST_Q1, TEST_Q2);
644     verifyUserDeniedForDeleteExactVersion(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2);
645   }
646 
647   private void verifyUserDeniedForDeleteExactVersion(final User user, final byte[] row,
648       final byte[] q1, final byte[] q2) throws IOException, InterruptedException {
649     user.runAs(new PrivilegedExceptionAction<Void>() {
650       @Override
651       public Void run() throws Exception {
652         HTable t = new HTable(conf, TEST_TABLE.getTableName());
653         try {
654           Delete d = new Delete(row, 127);
655           d.deleteColumns(TEST_FAMILY1, q1);
656           d.deleteColumns(TEST_FAMILY1, q2);
657           d.deleteFamily(TEST_FAMILY2, 129);
658           t.delete(d);
659           fail(user.getShortName() + " can not do the delete");
660         } catch (Exception e) {
661 
662         } finally {
663           t.close();
664         }
665         return null;
666       }
667     });
668   }
669 
670   @Test
671   public void testCellPermissionsForIncrementWithMultipleVersions() throws Exception {
672     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
673     final byte[] TEST_Q1 = Bytes.toBytes("q1");
674     final byte[] TEST_Q2 = Bytes.toBytes("q2");
675     final byte[] ZERO = Bytes.toBytes(0L);
676 
677     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
678     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
679 
680     verifyAllowed(new AccessTestAction() {
681       @Override
682       public Object run() throws Exception {
683         HTable t = new HTable(conf, TEST_TABLE.getTableName());
684         try {
685           Map<String, Permission> permsU1andOwner =
686               prepareCellPermissions(
687                 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
688                 Action.WRITE);
689           Map<String, Permission> permsU2andGUandOwner =
690               prepareCellPermissions(
691                 new String[] { user2.getShortName(), AuthUtil.toGroupEntry(GROUP),
692                     USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
693           Put p = new Put(TEST_ROW1);
694           p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
695           p.setACL(permsU1andOwner);
696           t.put(p);
697           p = new Put(TEST_ROW1);
698           p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
699           p.setACL(permsU2andGUandOwner);
700           t.put(p);
701 
702           p = new Put(TEST_ROW1);
703           p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
704           p.setACL(permsU2andGUandOwner);
705           t.put(p);
706           p = new Put(TEST_ROW1);
707           p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
708           p.setACL(permsU1andOwner);
709           t.put(p);
710         } finally {
711           t.close();
712         }
713         return null;
714       }
715     }, USER_OWNER);
716 
717     // Increment considers the TimeRange set on it.
718     user1.runAs(new PrivilegedExceptionAction<Void>() {
719       @Override
720       public Void run() throws Exception {
721         HTable t = new HTable(conf, TEST_TABLE.getTableName());
722         try {
723           Increment inc = new Increment(TEST_ROW1);
724           inc.setTimeRange(0, 123);
725           inc.addColumn(TEST_FAMILY1, TEST_Q1, 2L);
726           t.increment(inc);
727           t.incrementColumnValue(TEST_ROW1, TEST_FAMILY1, TEST_Q2, 1L);
728         } finally {
729           t.close();
730         }
731         return null;
732       }
733     });
734 
735     verifyUserDeniedForIncrementMultipleVersions(user2, TEST_ROW1, TEST_Q2);
736     verifyUserDeniedForIncrementMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q2);
737   }
738 
739   private void verifyUserDeniedForIncrementMultipleVersions(final User user,
740       final byte[] row, final byte[] q1) throws IOException, InterruptedException {
741     user.runAs(new PrivilegedExceptionAction<Void>() {
742       @Override
743       public Void run() throws Exception {
744         HTable t = new HTable(conf, TEST_TABLE.getTableName());
745         try {
746           Increment inc = new Increment(row);
747           inc.setTimeRange(0, 127);
748           inc.addColumn(TEST_FAMILY1, q1, 2L);
749           t.increment(inc);
750           fail(user.getShortName() + " cannot do the increment.");
751         } catch (Exception e) {
752 
753         } finally {
754           t.close();
755         }
756         return null;
757       }
758     });
759   }
760 
761   @Test
762   public void testCellPermissionsForPutWithMultipleVersions() throws Exception {
763     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
764     final byte[] TEST_Q1 = Bytes.toBytes("q1");
765     final byte[] TEST_Q2 = Bytes.toBytes("q2");
766     final byte[] ZERO = Bytes.toBytes(0L);
767 
768     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
769     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
770 
771     verifyAllowed(new AccessTestAction() {
772       @Override
773       public Object run() throws Exception {
774         HTable t = new HTable(conf, TEST_TABLE.getTableName());
775         try {
776           Map<String, Permission> permsU1andOwner =
777               prepareCellPermissions(
778                 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
779                 Action.WRITE);
780           Map<String, Permission> permsU2andGUandOwner =
781               prepareCellPermissions(
782                 new String[] { user1.getShortName(), AuthUtil.toGroupEntry(GROUP),
783                     USER_OWNER.getShortName() }, Action.READ, Action.WRITE);
784           permsU2andGUandOwner.put(user2.getShortName(), new Permission(Permission.Action.READ,
785               Permission.Action.WRITE));
786           permsU2andGUandOwner.put(USER_OWNER.getShortName(), new Permission(
787               Permission.Action.READ, Permission.Action.WRITE));
788           Put p = new Put(TEST_ROW1);
789           p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
790           p.setACL(permsU1andOwner);
791           t.put(p);
792           p = new Put(TEST_ROW1);
793           p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
794           p.setACL(permsU2andGUandOwner);
795           t.put(p);
796 
797           p = new Put(TEST_ROW1);
798           p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
799           p.setACL(permsU2andGUandOwner);
800           t.put(p);
801           p = new Put(TEST_ROW1);
802           p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
803           p.setACL(permsU1andOwner);
804           t.put(p);
805         } finally {
806           t.close();
807         }
808         return null;
809       }
810     }, USER_OWNER);
811 
812     // new Put with TEST_Q1 column having TS=125. This covers old cell with TS 123 and user1 is
813     // having RW permission. While TEST_Q2 is with latest TS and so it covers old cell with TS 127.
814     // User1 is having RW permission on that too.
815     user1.runAs(new PrivilegedExceptionAction<Void>() {
816       @Override
817       public Void run() throws Exception {
818         HTable t = new HTable(conf, TEST_TABLE.getTableName());
819         try {
820           Put p = new Put(TEST_ROW1);
821           p.add(TEST_FAMILY1, TEST_Q1, 125, ZERO);
822           p.add(TEST_FAMILY1, TEST_Q2, ZERO);
823           p.setACL(user2.getShortName(), new Permission(Permission.Action.READ,
824               Permission.Action.WRITE));
825           t.put(p);
826         } finally {
827           t.close();
828         }
829         return null;
830       }
831     });
832 
833     // Should be denied.
834     verifyUserDeniedForPutMultipleVersions(user2, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO);
835     verifyUserDeniedForPutMultipleVersions(GROUP_USER, TEST_ROW1, TEST_Q1, TEST_Q2, ZERO);
836   }
837 
838   private void verifyUserDeniedForPutMultipleVersions(final User user, final byte[] row,
839       final byte[] q1, final byte[] q2, final byte[] val) throws IOException,
840       InterruptedException {
841     user.runAs(new PrivilegedExceptionAction<Void>() {
842       @Override
843       public Void run() throws Exception {
844         HTable t = new HTable(conf, TEST_TABLE.getTableName());
845         try {
846           Put p = new Put(row);
847           // column Q1 covers version at 123 fr which user2 do not have permission
848           p.add(TEST_FAMILY1, q1, 124, val);
849           p.add(TEST_FAMILY1, q2, val);
850           t.put(p);
851           fail(user.getShortName() + " cannot do the put.");
852         } catch (Exception e) {
853 
854         } finally {
855           t.close();
856         }
857         return null;
858       }
859     });
860   }
861 
862   @Test
863   public void testCellPermissionsForCheckAndDelete() throws Exception {
864     final byte[] TEST_ROW1 = Bytes.toBytes("r1");
865     final byte[] TEST_Q3 = Bytes.toBytes("q3");
866     final byte[] ZERO = Bytes.toBytes(0L);
867 
868     final User user1 = User.createUserForTesting(conf, "user1", new String[0]);
869     final User user2 = User.createUserForTesting(conf, "user2", new String[0]);
870 
871     verifyAllowed(new AccessTestAction() {
872       @Override
873       public Object run() throws Exception {
874         HTable t = new HTable(conf, TEST_TABLE.getTableName());
875         try {
876           Map<String, Permission> permsU1andOwner =
877               prepareCellPermissions(
878                 new String[] { user1.getShortName(), USER_OWNER.getShortName() }, Action.READ,
879                 Action.WRITE);
880           Map<String, Permission> permsU1andU2andGUandOwner =
881               prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
882                   AuthUtil.toGroupEntry(GROUP), USER_OWNER.getShortName() }, Action.READ,
883                 Action.WRITE);
884           Map<String, Permission> permsU1andU2andGU =
885               prepareCellPermissions(new String[] { user1.getShortName(), user2.getShortName(),
886                   AuthUtil.toGroupEntry(GROUP) }, Action.READ, Action.WRITE);
887 
888           Put p = new Put(TEST_ROW1);
889           p.add(TEST_FAMILY1, TEST_Q1, 120, ZERO);
890           p.add(TEST_FAMILY1, TEST_Q2, 120, ZERO);
891           p.add(TEST_FAMILY1, TEST_Q3, 120, ZERO);
892           p.setACL(permsU1andU2andGUandOwner);
893           t.put(p);
894 
895           p = new Put(TEST_ROW1);
896           p.add(TEST_FAMILY1, TEST_Q1, 123, ZERO);
897           p.add(TEST_FAMILY1, TEST_Q2, 123, ZERO);
898           p.add(TEST_FAMILY1, TEST_Q3, 123, ZERO);
899           p.setACL(permsU1andOwner);
900           t.put(p);
901 
902           p = new Put(TEST_ROW1);
903           p.add(TEST_FAMILY1, TEST_Q1, 127, ZERO);
904           p.setACL(permsU1andU2andGU);
905           t.put(p);
906 
907           p = new Put(TEST_ROW1);
908           p.add(TEST_FAMILY1, TEST_Q2, 127, ZERO);
909           p.setACL(user2.getShortName(), new Permission(Permission.Action.READ));
910           t.put(p);
911 
912           p = new Put(TEST_ROW1);
913           p.add(TEST_FAMILY1, TEST_Q3, 127, ZERO);
914           p.setACL(AuthUtil.toGroupEntry(GROUP), new Permission(Permission.Action.READ));
915           t.put(p);
916         } finally {
917           t.close();
918         }
919         return null;
920       }
921     }, USER_OWNER);
922 
923     // user1 should be allowed to do the checkAndDelete. user1 having read permission on the latest
924     // version cell and write permission on all versions
925     user1.runAs(new PrivilegedExceptionAction<Void>() {
926       @Override
927       public Void run() throws Exception {
928         HTable t = new HTable(conf, TEST_TABLE.getTableName());
929         try {
930           Delete d = new Delete(TEST_ROW1);
931           d.deleteColumns(TEST_FAMILY1, TEST_Q1, 120);
932           t.checkAndDelete(TEST_ROW1, TEST_FAMILY1, TEST_Q1, ZERO, d);
933         } finally {
934           t.close();
935         }
936         return null;
937       }
938     });
939     // user2 shouldn't be allowed to do the checkAndDelete. user2 having RW permission on the latest
940     // version cell but not on cell version TS=123
941     verifyUserDeniedForCheckAndDelete(user2, TEST_ROW1, ZERO);
942 
943     // GROUP_USER shouldn't be allowed to do the checkAndDelete. GROUP_USER having RW permission on
944     // the latest
945     // version cell but not on cell version TS=123
946     verifyUserDeniedForCheckAndDelete(GROUP_USER, TEST_ROW1, ZERO);
947 
948     // user2 should be allowed to do the checkAndDelete when delete tries to delete the old version
949     // TS=120. user2 having R permission on the latest version(no W permission) cell
950     // and W permission on cell version TS=120.
951     verifyUserAllowedforCheckAndDelete(user2, TEST_ROW1, TEST_Q2, ZERO);
952 
953     // GROUP_USER should be allowed to do the checkAndDelete when delete tries to delete the old
954     // version
955     // TS=120. user2 having R permission on the latest version(no W permission) cell
956     // and W permission on cell version TS=120.
957     verifyUserAllowedforCheckAndDelete(GROUP_USER, TEST_ROW1, TEST_Q3, ZERO);
958   }
959 
960   private void verifyUserAllowedforCheckAndDelete(final User user, final byte[] row,
961       final byte[] q1, final byte[] value) throws IOException, InterruptedException {
962     user.runAs(new PrivilegedExceptionAction<Void>() {
963       @Override
964       public Void run() throws Exception {
965         HTable t = new HTable(conf, TEST_TABLE.getTableName());
966         try {
967           Delete d = new Delete(row);
968           d.deleteColumn(TEST_FAMILY1, q1, 120);
969           t.checkAndDelete(row, TEST_FAMILY1, q1, value, d);
970         } finally {
971           t.close();
972         }
973         return null;
974       }
975     });
976   }
977 
978   private void verifyUserDeniedForCheckAndDelete(final User user, final byte[] row,
979       final byte[] value) throws IOException, InterruptedException {
980     user.runAs(new PrivilegedExceptionAction<Void>() {
981       @Override
982       public Void run() throws Exception {
983         HTable t = new HTable(conf, TEST_TABLE.getTableName());
984         try {
985           Delete d = new Delete(row);
986           d.deleteColumns(TEST_FAMILY1, TEST_Q1);
987           t.checkAndDelete(row, TEST_FAMILY1, TEST_Q1, value, d);
988           fail(user.getShortName() + " should not be allowed to do checkAndDelete");
989         } catch (Exception e) {
990         } finally {
991           t.close();
992         }
993         return null;
994       }
995     });
996   }
997 
998   @After
999   public void tearDown() throws Exception {
1000     // Clean the _acl_ table
1001     try {
1002       TEST_UTIL.deleteTable(TEST_TABLE.getTableName());
1003     } catch (TableNotFoundException ex) {
1004       // Test deleted the table, no problem
1005       LOG.info("Test deleted table " + TEST_TABLE.getTableName());
1006     }
1007     assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size());
1008   }
1009 }