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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.security.access;
20  
21  import java.io.IOException;
22  import java.security.PrivilegedExceptionAction;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.Set;
30  import java.util.TreeMap;
31  import java.util.TreeSet;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.hbase.Cell;
37  import org.apache.hadoop.hbase.CellScanner;
38  import org.apache.hadoop.hbase.CellUtil;
39  import org.apache.hadoop.hbase.CompoundConfiguration;
40  import org.apache.hadoop.hbase.CoprocessorEnvironment;
41  import org.apache.hadoop.hbase.DoNotRetryIOException;
42  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
43  import org.apache.hadoop.hbase.HColumnDescriptor;
44  import org.apache.hadoop.hbase.HConstants;
45  import org.apache.hadoop.hbase.HRegionInfo;
46  import org.apache.hadoop.hbase.HTableDescriptor;
47  import org.apache.hadoop.hbase.KeyValue;
48  import org.apache.hadoop.hbase.KeyValue.Type;
49  import org.apache.hadoop.hbase.MetaTableAccessor;
50  import org.apache.hadoop.hbase.NamespaceDescriptor;
51  import org.apache.hadoop.hbase.ProcedureInfo;
52  import org.apache.hadoop.hbase.ServerName;
53  import org.apache.hadoop.hbase.TableName;
54  import org.apache.hadoop.hbase.Tag;
55  import org.apache.hadoop.hbase.TagRewriteCell;
56  import org.apache.hadoop.hbase.classification.InterfaceAudience;
57  import org.apache.hadoop.hbase.client.Admin;
58  import org.apache.hadoop.hbase.client.Append;
59  import org.apache.hadoop.hbase.client.Delete;
60  import org.apache.hadoop.hbase.client.Durability;
61  import org.apache.hadoop.hbase.client.Get;
62  import org.apache.hadoop.hbase.client.Increment;
63  import org.apache.hadoop.hbase.client.Mutation;
64  import org.apache.hadoop.hbase.client.Put;
65  import org.apache.hadoop.hbase.client.Query;
66  import org.apache.hadoop.hbase.client.Result;
67  import org.apache.hadoop.hbase.client.Scan;
68  import org.apache.hadoop.hbase.client.Table;
69  import org.apache.hadoop.hbase.coprocessor.BaseMasterAndRegionObserver;
70  import org.apache.hadoop.hbase.coprocessor.BulkLoadObserver;
71  import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
72  import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
73  import org.apache.hadoop.hbase.coprocessor.EndpointObserver;
74  import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
75  import org.apache.hadoop.hbase.coprocessor.ObserverContext;
76  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
77  import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
78  import org.apache.hadoop.hbase.coprocessor.RegionServerObserver;
79  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
80  import org.apache.hadoop.hbase.filter.CompareFilter;
81  import org.apache.hadoop.hbase.filter.Filter;
82  import org.apache.hadoop.hbase.filter.FilterList;
83  import org.apache.hadoop.hbase.io.hfile.HFile;
84  import org.apache.hadoop.hbase.ipc.RpcServer;
85  import org.apache.hadoop.hbase.master.MasterServices;
86  import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
87  import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
88  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
89  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
90  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
91  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
92  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
93  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
94  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
95  import org.apache.hadoop.hbase.protobuf.generated.SecureBulkLoadProtos.CleanupBulkLoadRequest;
96  import org.apache.hadoop.hbase.protobuf.generated.SecureBulkLoadProtos.PrepareBulkLoadRequest;
97  import org.apache.hadoop.hbase.regionserver.InternalScanner;
98  import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
99  import org.apache.hadoop.hbase.regionserver.Region;
100 import org.apache.hadoop.hbase.regionserver.RegionScanner;
101 import org.apache.hadoop.hbase.regionserver.ScanType;
102 import org.apache.hadoop.hbase.regionserver.ScannerContext;
103 import org.apache.hadoop.hbase.regionserver.Store;
104 import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
105 import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
106 import org.apache.hadoop.hbase.security.AccessDeniedException;
107 import org.apache.hadoop.hbase.security.Superusers;
108 import org.apache.hadoop.hbase.security.User;
109 import org.apache.hadoop.hbase.security.UserProvider;
110 import org.apache.hadoop.hbase.security.access.Permission.Action;
111 import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
112 import org.apache.hadoop.hbase.util.ByteRange;
113 import org.apache.hadoop.hbase.util.Bytes;
114 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
115 import org.apache.hadoop.hbase.util.Pair;
116 import org.apache.hadoop.hbase.util.SimpleMutableByteRange;
117 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
118 
119 import com.google.common.collect.ArrayListMultimap;
120 import com.google.common.collect.ImmutableSet;
121 import com.google.common.collect.ListMultimap;
122 import com.google.common.collect.Lists;
123 import com.google.common.collect.MapMaker;
124 import com.google.common.collect.Maps;
125 import com.google.common.collect.Sets;
126 import com.google.protobuf.Message;
127 import com.google.protobuf.RpcCallback;
128 import com.google.protobuf.RpcController;
129 import com.google.protobuf.Service;
130 
131 /**
132  * Provides basic authorization checks for data access and administrative
133  * operations.
134  *
135  * <p>
136  * {@code AccessController} performs authorization checks for HBase operations
137  * based on:
138  * </p>
139  * <ul>
140  *   <li>the identity of the user performing the operation</li>
141  *   <li>the scope over which the operation is performed, in increasing
142  *   specificity: global, table, column family, or qualifier</li>
143  *   <li>the type of action being performed (as mapped to
144  *   {@link Permission.Action} values)</li>
145  * </ul>
146  * <p>
147  * If the authorization check fails, an {@link AccessDeniedException}
148  * will be thrown for the operation.
149  * </p>
150  *
151  * <p>
152  * To perform authorization checks, {@code AccessController} relies on the
153  * RpcServerEngine being loaded to provide
154  * the user identities for remote requests.
155  * </p>
156  *
157  * <p>
158  * The access control lists used for authorization can be manipulated via the
159  * exposed {@link AccessControlService} Interface implementation, and the associated
160  * {@code grant}, {@code revoke}, and {@code user_permission} HBase shell
161  * commands.
162  * </p>
163  */
164 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
165 public class AccessController extends BaseMasterAndRegionObserver
166     implements RegionServerObserver,
167       AccessControlService.Interface, CoprocessorService, EndpointObserver, BulkLoadObserver {
168 
169   private static final Log LOG = LogFactory.getLog(AccessController.class);
170 
171   private static final Log AUDITLOG =
172     LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
173   private static final String CHECK_COVERING_PERM = "check_covering_perm";
174   private static final String TAG_CHECK_PASSED = "tag_check_passed";
175   private static final byte[] TRUE = Bytes.toBytes(true);
176 
177   private AccessChecker accessChecker;
178 
179   /** flags if we are running on a region of the _acl_ table */
180   private boolean aclRegion = false;
181 
182   /** defined only for Endpoint implementation, so it can have way to
183    access region services */
184   private RegionCoprocessorEnvironment regionEnv;
185 
186   /** Mapping of scanner instances to the user who created them */
187   private Map<InternalScanner,String> scannerOwners =
188       new MapMaker().weakKeys().makeMap();
189 
190   private Map<TableName, List<UserPermission>> tableAcls;
191 
192   /** Provider for mapping principal names to Users */
193   private UserProvider userProvider;
194 
195   /** if we are active, usually false, only true if "hbase.security.authorization"
196    has been set to true in site configuration */
197   private boolean authorizationEnabled;
198 
199   /** if we are able to support cell ACLs */
200   private boolean cellFeaturesEnabled;
201 
202   /** if we should check EXEC permissions */
203   private boolean shouldCheckExecPermission;
204 
205   /** if we should terminate access checks early as soon as table or CF grants
206     allow access; pre-0.98 compatible behavior */
207   private boolean compatibleEarlyTermination;
208 
209   /** if we have been successfully initialized */
210   private volatile boolean initialized = false;
211 
212   /** if the ACL table is available, only relevant in the master */
213   private volatile boolean aclTabAvailable = false;
214 
215   public static boolean isAuthorizationSupported(Configuration conf) {
216     return AccessChecker.isAuthorizationSupported(conf);
217   }
218 
219   public static boolean isCellAuthorizationSupported(Configuration conf) {
220     return AccessChecker.isAuthorizationSupported(conf) &&
221         (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS);
222   }
223 
224   public Region getRegion() {
225     return regionEnv != null ? regionEnv.getRegion() : null;
226   }
227 
228   public TableAuthManager getAuthManager() {
229     return accessChecker.getAuthManager();
230   }
231 
232   private void initialize(RegionCoprocessorEnvironment e) throws IOException {
233     final Region region = e.getRegion();
234     Configuration conf = e.getConfiguration();
235     Map<byte[], ListMultimap<String,TablePermission>> tables =
236         AccessControlLists.loadAll(region);
237     // For each table, write out the table's permissions to the respective
238     // znode for that table.
239     for (Map.Entry<byte[], ListMultimap<String,TablePermission>> t:
240       tables.entrySet()) {
241       byte[] entry = t.getKey();
242       ListMultimap<String,TablePermission> perms = t.getValue();
243       byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
244       getAuthManager().getZKPermissionWatcher().writeToZookeeper(entry, serialized);
245     }
246     initialized = true;
247   }
248 
249   /**
250    * Writes all table ACLs for the tables in the given Map up into ZooKeeper
251    * znodes.  This is called to synchronize ACL changes following {@code _acl_}
252    * table updates.
253    */
254   private void updateACL(RegionCoprocessorEnvironment e,
255       final Map<byte[], List<Cell>> familyMap) {
256     Set<byte[]> entries =
257         new TreeSet<byte[]>(Bytes.BYTES_RAWCOMPARATOR);
258     for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) {
259       List<Cell> cells = f.getValue();
260       for (Cell cell: cells) {
261         if (Bytes.equals(cell.getFamilyArray(), cell.getFamilyOffset(),
262             cell.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
263             AccessControlLists.ACL_LIST_FAMILY.length)) {
264           entries.add(CellUtil.cloneRow(cell));
265         }
266       }
267     }
268     ZKPermissionWatcher zkw = getAuthManager().getZKPermissionWatcher();
269     Configuration conf = regionEnv.getConfiguration();
270     for (byte[] entry: entries) {
271       try {
272         try (Table t = regionEnv.getTable(AccessControlLists.ACL_TABLE_NAME)) {
273           ListMultimap<String,TablePermission> perms =
274               AccessControlLists.getPermissions(conf, entry, t);
275           byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
276           zkw.writeToZookeeper(entry, serialized);
277         }
278       } catch (IOException ex) {
279         LOG.error("Failed updating permissions mirror for '" + Bytes.toString(entry) + "'",
280             ex);
281       }
282     }
283   }
284 
285   /**
286    * Check the current user for authorization to perform a specific action
287    * against the given set of row data.
288    *
289    * <p>Note: Ordering of the authorization checks
290    * has been carefully optimized to short-circuit the most common requests
291    * and minimize the amount of processing required.</p>
292    *
293    * @param permRequest the action being requested
294    * @param e the coprocessor environment
295    * @param families the map of column families to qualifiers present in
296    * the request
297    * @return an authorization result
298    */
299   AuthResult permissionGranted(String request, User user, Action permRequest,
300       RegionCoprocessorEnvironment e,
301       Map<byte [], ? extends Collection<?>> families) {
302     HRegionInfo hri = e.getRegion().getRegionInfo();
303     TableName tableName = hri.getTable();
304 
305     // 1. All users need read access to hbase:meta table.
306     // this is a very common operation, so deal with it quickly.
307     if (hri.isMetaRegion()) {
308       if (permRequest == Action.READ) {
309         return AuthResult.allow(request, "All users allowed", user,
310           permRequest, tableName, families);
311       }
312     }
313 
314     if (user == null) {
315       return AuthResult.deny(request, "No user associated with request!", null,
316         permRequest, tableName, families);
317     }
318 
319     // 2. check for the table-level, if successful we can short-circuit
320     if (getAuthManager().authorize(user, tableName, (byte[])null, permRequest)) {
321       return AuthResult.allow(request, "Table permission granted", user,
322         permRequest, tableName, families);
323     }
324 
325     // 3. check permissions against the requested families
326     if (families != null && families.size() > 0) {
327       // all families must pass
328       for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
329         // a) check for family level access
330         if (getAuthManager().authorize(user, tableName, family.getKey(),
331             permRequest)) {
332           continue;  // family-level permission overrides per-qualifier
333         }
334 
335         // b) qualifier level access can still succeed
336         if ((family.getValue() != null) && (family.getValue().size() > 0)) {
337           if (family.getValue() instanceof Set) {
338             // for each qualifier of the family
339             Set<byte[]> familySet = (Set<byte[]>)family.getValue();
340             for (byte[] qualifier : familySet) {
341               if (!getAuthManager().authorize(user, tableName, family.getKey(),
342                                          qualifier, permRequest)) {
343                 return AuthResult.deny(request, "Failed qualifier check", user,
344                     permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
345               }
346             }
347           } else if (family.getValue() instanceof List) { // List<KeyValue>
348             List<KeyValue> kvList = (List<KeyValue>)family.getValue();
349             for (KeyValue kv : kvList) {
350               if (!getAuthManager().authorize(user, tableName, family.getKey(),
351                       kv.getQualifier(), permRequest)) {
352                 return AuthResult.deny(request, "Failed qualifier check", user,
353                     permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
354               }
355             }
356           }
357         } else {
358           // no qualifiers and family-level check already failed
359           return AuthResult.deny(request, "Failed family check", user, permRequest,
360               tableName, makeFamilyMap(family.getKey(), null));
361         }
362       }
363 
364       // all family checks passed
365       return AuthResult.allow(request, "All family checks passed", user, permRequest,
366           tableName, families);
367     }
368 
369     // 4. no families to check and table level access failed
370     return AuthResult.deny(request, "No families to check and table permission failed",
371         user, permRequest, tableName, families);
372   }
373 
374   /**
375    * Check the current user for authorization to perform a specific action
376    * against the given set of row data.
377    * @param opType the operation type
378    * @param user the user
379    * @param e the coprocessor environment
380    * @param families the map of column families to qualifiers present in
381    * the request
382    * @param actions the desired actions
383    * @return an authorization result
384    */
385   AuthResult permissionGranted(OpType opType, User user, RegionCoprocessorEnvironment e,
386       Map<byte [], ? extends Collection<?>> families, Action... actions) {
387     AuthResult result = null;
388     for (Action action: actions) {
389       result = permissionGranted(opType.toString(), user, action, e, families);
390       if (!result.isAllowed()) {
391         return result;
392       }
393     }
394     return result;
395   }
396 
397   /**
398    * Returns the active user to which authorization checks should be applied.
399    * If we are in the context of an RPC call, the remote user is used,
400    * otherwise the currently logged in user is used.
401    */
402   private User getActiveUser() throws IOException {
403     User user = RpcServer.getRequestUser();
404     if (user == null) {
405       // for non-rpc handling, fallback to system user
406       user = userProvider.getCurrent();
407     }
408     return user;
409   }
410 
411   /**
412    * Authorizes that the current user has any of the given permissions for the
413    * given table, column family and column qualifier.
414    * @param tableName Table requested
415    * @param family Column family requested
416    * @param qualifier Column qualifier requested
417    * @throws IOException if obtaining the current user fails
418    * @throws AccessDeniedException if user has no authorization
419    */
420   public void requirePermission(String request, TableName tableName, byte[] family,
421       byte[] qualifier, Action... permissions) throws IOException {
422     accessChecker.requirePermission(getActiveUser(), request,
423         tableName, family, qualifier, permissions);
424   }
425 
426   /**
427    * Authorizes that the current user has any of the given permissions for the
428    * given table, column family and column qualifier.
429    * @param tableName Table requested
430    * @param family Column family param
431    * @param qualifier Column qualifier param
432    * @throws IOException if obtaining the current user fails
433    * @throws AccessDeniedException if user has no authorization
434    */
435   public void requireTablePermission(String request, TableName tableName, byte[] family,
436       byte[] qualifier, Action... permissions) throws IOException {
437     accessChecker.requireTablePermission(getActiveUser(), request,
438         tableName, family, qualifier, permissions);
439   }
440 
441   /**
442    * Authorizes that the current user has any of the given permissions to access the table.
443    *
444    * @param tableName Table requested
445    * @param permissions Actions being requested
446    * @throws IOException if obtaining the current user fails
447    * @throws AccessDeniedException if user has no authorization
448    */
449   public void requireAccess(String request, TableName tableName,
450       Action... permissions) throws IOException {
451     accessChecker.requireAccess(getActiveUser(), request, tableName, permissions);
452   }
453 
454   /**
455    * Authorizes that the current user has global privileges for the given action.
456    * @param perm The action being requested
457    * @throws IOException if obtaining the current user fails
458    * @throws AccessDeniedException if authorization is denied
459    */
460   public void requirePermission(String request, Action perm) throws IOException {
461     accessChecker.requirePermission(getActiveUser(), request, perm);
462   }
463 
464   /**
465    * Checks that the user has the given global permission. The generated
466    * audit log message will contain context information for the operation
467    * being authorized, based on the given parameters.
468    * @param perm Action being requested
469    * @param tableName Affected table name.
470    * @param familyMap Affected column families.
471    */
472   public void requireGlobalPermission(String request, Action perm, TableName tableName,
473       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
474     accessChecker.requireGlobalPermission(getActiveUser(), request, perm, tableName, familyMap);
475   }
476 
477   /**
478    * Checks that the user has the given global permission. The generated
479    * audit log message will contain context information for the operation
480    * being authorized, based on the given parameters.
481    * @param perm Action being requested
482    * @param namespace  The given namespace
483    */
484   public void requireGlobalPermission(String request, Action perm,
485       String namespace) throws IOException {
486     accessChecker.requireGlobalPermission(getActiveUser(), request, perm, namespace);
487   }
488 
489   /**
490    * Checks that the user has the given global or namespace permission.
491    * @param namespace  The given namespace
492    * @param permissions Actions being requested
493    */
494   public void requireNamespacePermission(String request, String namespace,
495       Action... permissions) throws IOException {
496     accessChecker.requireNamespacePermission(getActiveUser(), request, namespace, permissions);
497   }
498 
499   /**
500    * Checks that the user has the given global or namespace permission.
501    * @param namespace   The given namespace
502    * @param permissions Actions being requested
503    */
504   public void requireNamespacePermission(String request, String namespace, TableName tableName,
505       Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions)
506       throws IOException {
507     accessChecker.requireNamespacePermission(getActiveUser(), request, namespace,
508         tableName, familyMap, permissions);
509   }
510 
511   /**
512    * Returns <code>true</code> if the current user is allowed the given action
513    * over at least one of the column qualifiers in the given column families.
514    */
515   private boolean hasFamilyQualifierPermission(User user,
516       Action perm,
517       RegionCoprocessorEnvironment env,
518       Map<byte[], ? extends Collection<byte[]>> familyMap)
519     throws IOException {
520     HRegionInfo hri = env.getRegion().getRegionInfo();
521     TableName tableName = hri.getTable();
522 
523     if (user == null) {
524       return false;
525     }
526 
527     if (familyMap != null && familyMap.size() > 0) {
528       // at least one family must be allowed
529       for (Map.Entry<byte[], ? extends Collection<byte[]>> family :
530           familyMap.entrySet()) {
531         if (family.getValue() != null && !family.getValue().isEmpty()) {
532           for (byte[] qualifier : family.getValue()) {
533             if (getAuthManager().matchPermission(user, tableName,
534                 family.getKey(), qualifier, perm)) {
535               return true;
536             }
537           }
538         } else {
539           if (getAuthManager().matchPermission(user, tableName, family.getKey(),
540               perm)) {
541             return true;
542           }
543         }
544       }
545     } else if (LOG.isDebugEnabled()) {
546       LOG.debug("Empty family map passed for permission check");
547     }
548 
549     return false;
550   }
551 
552   private enum OpType {
553     GET_CLOSEST_ROW_BEFORE("getClosestRowBefore"),
554     GET("get"),
555     EXISTS("exists"),
556     SCAN("scan"),
557     PUT("put"),
558     DELETE("delete"),
559     CHECK_AND_PUT("checkAndPut"),
560     CHECK_AND_DELETE("checkAndDelete"),
561     INCREMENT_COLUMN_VALUE("incrementColumnValue"),
562     APPEND("append"),
563     INCREMENT("increment");
564 
565     private String type;
566 
567     private OpType(String type) {
568       this.type = type;
569     }
570 
571     @Override
572     public String toString() {
573       return type;
574     }
575   }
576 
577   /**
578    * Determine if cell ACLs covered by the operation grant access. This is expensive.
579    * @return false if cell ACLs failed to grant access, true otherwise
580    * @throws IOException
581    */
582   private boolean checkCoveringPermission(OpType request, RegionCoprocessorEnvironment e,
583       byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, Action... actions)
584       throws IOException {
585     if (!cellFeaturesEnabled) {
586       return false;
587     }
588     long cellGrants = 0;
589     User user = getActiveUser();
590     long latestCellTs = 0;
591     Get get = new Get(row);
592     // Only in case of Put/Delete op, consider TS within cell (if set for individual cells).
593     // When every cell, within a Mutation, can be linked with diff TS we can not rely on only one
594     // version. We have to get every cell version and check its TS against the TS asked for in
595     // Mutation and skip those Cells which is outside this Mutation TS.In case of Put, we have to
596     // consider only one such passing cell. In case of Delete we have to consider all the cell
597     // versions under this passing version. When Delete Mutation contains columns which are a
598     // version delete just consider only one version for those column cells.
599     boolean considerCellTs  = (request == OpType.PUT || request == OpType.DELETE);
600     if (considerCellTs) {
601       get.setMaxVersions();
602     } else {
603       get.setMaxVersions(1);
604     }
605     boolean diffCellTsFromOpTs = false;
606     for (Map.Entry<byte[], ? extends Collection<?>> entry: familyMap.entrySet()) {
607       byte[] col = entry.getKey();
608       // TODO: HBASE-7114 could possibly unify the collection type in family
609       // maps so we would not need to do this
610       if (entry.getValue() instanceof Set) {
611         Set<byte[]> set = (Set<byte[]>)entry.getValue();
612         if (set == null || set.isEmpty()) {
613           get.addFamily(col);
614         } else {
615           for (byte[] qual: set) {
616             get.addColumn(col, qual);
617           }
618         }
619       } else if (entry.getValue() instanceof List) {
620         List<Cell> list = (List<Cell>)entry.getValue();
621         if (list == null || list.isEmpty()) {
622           get.addFamily(col);
623         } else {
624           // In case of family delete, a Cell will be added into the list with Qualifier as null.
625           for (Cell cell : list) {
626             if (cell.getQualifierLength() == 0
627                 && (cell.getTypeByte() == Type.DeleteFamily.getCode()
628                 || cell.getTypeByte() == Type.DeleteFamilyVersion.getCode())) {
629               get.addFamily(col);
630             } else {
631               get.addColumn(col, CellUtil.cloneQualifier(cell));
632             }
633             if (considerCellTs) {
634               long cellTs = cell.getTimestamp();
635               latestCellTs = Math.max(latestCellTs, cellTs);
636               diffCellTsFromOpTs = diffCellTsFromOpTs || (opTs != cellTs);
637             }
638           }
639         }
640       } else if (entry.getValue() == null) {
641         get.addFamily(col);
642       } else {
643         throw new RuntimeException("Unhandled collection type " +
644           entry.getValue().getClass().getName());
645       }
646     }
647     // We want to avoid looking into the future. So, if the cells of the
648     // operation specify a timestamp, or the operation itself specifies a
649     // timestamp, then we use the maximum ts found. Otherwise, we bound
650     // the Get to the current server time. We add 1 to the timerange since
651     // the upper bound of a timerange is exclusive yet we need to examine
652     // any cells found there inclusively.
653     long latestTs = Math.max(opTs, latestCellTs);
654     if (latestTs == 0 || latestTs == HConstants.LATEST_TIMESTAMP) {
655       latestTs = EnvironmentEdgeManager.currentTime();
656     }
657     get.setTimeRange(0, latestTs + 1);
658     // In case of Put operation we set to read all versions. This was done to consider the case
659     // where columns are added with TS other than the Mutation TS. But normally this wont be the
660     // case with Put. There no need to get all versions but get latest version only.
661     if (!diffCellTsFromOpTs && request == OpType.PUT) {
662       get.setMaxVersions(1);
663     }
664     if (LOG.isTraceEnabled()) {
665       LOG.trace("Scanning for cells with " + get);
666     }
667     // This Map is identical to familyMap. The key is a BR rather than byte[].
668     // It will be easy to do gets over this new Map as we can create get keys over the Cell cf by
669     // new SimpleByteRange(cell.familyArray, cell.familyOffset, cell.familyLen)
670     Map<ByteRange, List<Cell>> familyMap1 = new HashMap<ByteRange, List<Cell>>();
671     for (Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) {
672       if (entry.getValue() instanceof List) {
673         familyMap1.put(new SimpleMutableByteRange(entry.getKey()), (List<Cell>) entry.getValue());
674       }
675     }
676     RegionScanner scanner = getRegion(e).getScanner(new Scan(get));
677     List<Cell> cells = Lists.newArrayList();
678     Cell prevCell = null;
679     ByteRange curFam = new SimpleMutableByteRange();
680     boolean curColAllVersions = (request == OpType.DELETE);
681     long curColCheckTs = opTs;
682     boolean foundColumn = false;
683     try {
684       boolean more = false;
685       ScannerContext scannerContext = ScannerContext.newBuilder().setBatchLimit(1).build();
686 
687       do {
688         cells.clear();
689         // scan with limit as 1 to hold down memory use on wide rows
690         more = scanner.next(cells, scannerContext);
691         for (Cell cell: cells) {
692           if (LOG.isTraceEnabled()) {
693             LOG.trace("Found cell " + cell);
694           }
695           boolean colChange = prevCell == null || !CellUtil.matchingColumn(prevCell, cell);
696           if (colChange) foundColumn = false;
697           prevCell = cell;
698           if (!curColAllVersions && foundColumn) {
699             continue;
700           }
701           if (colChange && considerCellTs) {
702             curFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
703             List<Cell> cols = familyMap1.get(curFam);
704             for (Cell col : cols) {
705               // null/empty qualifier is used to denote a Family delete. The TS and delete type
706               // associated with this is applicable for all columns within the family. That is
707               // why the below (col.getQualifierLength() == 0) check.
708               if ((col.getQualifierLength() == 0 && request == OpType.DELETE)
709                   || CellUtil.matchingQualifier(cell, col)) {
710                 byte type = col.getTypeByte();
711                 if (considerCellTs) {
712                   curColCheckTs = col.getTimestamp();
713                 }
714                 // For a Delete op we pass allVersions as true. When a Delete Mutation contains
715                 // a version delete for a column no need to check all the covering cells within
716                 // that column. Check all versions when Type is DeleteColumn or DeleteFamily
717                 // One version delete types are Delete/DeleteFamilyVersion
718                 curColAllVersions = (KeyValue.Type.DeleteColumn.getCode() == type)
719                     || (KeyValue.Type.DeleteFamily.getCode() == type);
720                 break;
721               }
722             }
723           }
724           if (cell.getTimestamp() > curColCheckTs) {
725             // Just ignore this cell. This is not a covering cell.
726             continue;
727           }
728           foundColumn = true;
729           for (Action action: actions) {
730             // Are there permissions for this user for the cell?
731             if (!getAuthManager().authorize(user, getTableName(e), cell, action)) {
732               // We can stop if the cell ACL denies access
733               return false;
734             }
735           }
736           cellGrants++;
737         }
738       } while (more);
739     } catch (AccessDeniedException ex) {
740       throw ex;
741     } catch (IOException ex) {
742       LOG.error("Exception while getting cells to calculate covering permission", ex);
743     } finally {
744       scanner.close();
745     }
746     // We should not authorize unless we have found one or more cell ACLs that
747     // grant access. This code is used to check for additional permissions
748     // after no table or CF grants are found.
749     return cellGrants > 0;
750   }
751 
752   private static void addCellPermissions(final byte[] perms, Map<byte[], List<Cell>> familyMap) {
753     // Iterate over the entries in the familyMap, replacing the cells therein
754     // with new cells including the ACL data
755     for (Map.Entry<byte[], List<Cell>> e: familyMap.entrySet()) {
756       List<Cell> newCells = Lists.newArrayList();
757       for (Cell cell: e.getValue()) {
758         // Prepend the supplied perms in a new ACL tag to an update list of tags for the cell
759         List<Tag> tags = Lists.newArrayList(new Tag(AccessControlLists.ACL_TAG_TYPE, perms));
760         if (cell.getTagsLength() > 0) {
761           Iterator<Tag> tagIterator = CellUtil.tagsIterator(cell.getTagsArray(),
762             cell.getTagsOffset(), cell.getTagsLength());
763           while (tagIterator.hasNext()) {
764             tags.add(tagIterator.next());
765           }
766         }
767         newCells.add(new TagRewriteCell(cell, Tag.fromList(tags)));
768       }
769       // This is supposed to be safe, won't CME
770       e.setValue(newCells);
771     }
772   }
773 
774   // Checks whether incoming cells contain any tag with type as ACL_TAG_TYPE. This tag
775   // type is reserved and should not be explicitly set by user.
776   private void checkForReservedTagPresence(User user, Mutation m) throws IOException {
777     // No need to check if we're not going to throw
778     if (!authorizationEnabled) {
779       m.setAttribute(TAG_CHECK_PASSED, TRUE);
780       return;
781     }
782     // Superusers are allowed to store cells unconditionally.
783     if (Superusers.isSuperUser(user)) {
784       m.setAttribute(TAG_CHECK_PASSED, TRUE);
785       return;
786     }
787     // We already checked (prePut vs preBatchMutation)
788     if (m.getAttribute(TAG_CHECK_PASSED) != null) {
789       return;
790     }
791     for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
792       Cell cell = cellScanner.current();
793       if (cell.getTagsLength() > 0) {
794         Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
795           cell.getTagsLength());
796         while (tagsItr.hasNext()) {
797           if (tagsItr.next().getType() == AccessControlLists.ACL_TAG_TYPE) {
798             throw new AccessDeniedException("Mutation contains cell with reserved type tag");
799           }
800         }
801       }
802     }
803     m.setAttribute(TAG_CHECK_PASSED, TRUE);
804   }
805 
806   /* ---- MasterObserver implementation ---- */
807   @Override
808   public void start(CoprocessorEnvironment env) throws IOException {
809     CompoundConfiguration conf = new CompoundConfiguration();
810     conf.add(env.getConfiguration());
811 
812     authorizationEnabled = isAuthorizationSupported(conf);
813     if (!authorizationEnabled) {
814       LOG.warn("The AccessController has been loaded with authorization checks disabled.");
815     }
816 
817     shouldCheckExecPermission = conf.getBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY,
818       AccessControlConstants.DEFAULT_EXEC_PERMISSION_CHECKS);
819 
820     cellFeaturesEnabled = (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS);
821     if (!cellFeaturesEnabled) {
822       LOG.info("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
823           + " is required to persist cell ACLs. Consider setting " + HFile.FORMAT_VERSION_KEY
824           + " accordingly.");
825     }
826 
827     ZooKeeperWatcher zk = null;
828     if (env instanceof MasterCoprocessorEnvironment) {
829       // if running on HMaster
830       MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
831       zk = mEnv.getMasterServices().getZooKeeper();
832     } else if (env instanceof RegionServerCoprocessorEnvironment) {
833       RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
834       zk = rsEnv.getRegionServerServices().getZooKeeper();
835     } else if (env instanceof RegionCoprocessorEnvironment) {
836       // if running at region
837       regionEnv = (RegionCoprocessorEnvironment) env;
838       conf.addStringMap(regionEnv.getRegion().getTableDesc().getConfiguration());
839       zk = regionEnv.getRegionServerServices().getZooKeeper();
840       compatibleEarlyTermination = conf.getBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT,
841         AccessControlConstants.DEFAULT_ATTRIBUTE_EARLY_OUT);
842     }
843 
844     // set the user-provider.
845     this.userProvider = UserProvider.instantiate(env.getConfiguration());
846     accessChecker = new AccessChecker(env.getConfiguration(), zk);
847     tableAcls = new MapMaker().weakValues().makeMap();
848   }
849 
850   @Override
851   public void stop(CoprocessorEnvironment env) {
852     if (getAuthManager()!= null) {
853       TableAuthManager.release(getAuthManager());
854     }
855   }
856 
857   @Override
858   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
859       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
860     Set<byte[]> families = desc.getFamiliesKeys();
861     Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
862     for (byte[] family: families) {
863       familyMap.put(family, null);
864     }
865     requireNamespacePermission("createTable", desc.getTableName().getNamespaceAsString(),
866         desc.getTableName(), familyMap, Action.CREATE);
867   }
868 
869   @Override
870   public void postCreateTableHandler(final ObserverContext<MasterCoprocessorEnvironment> c,
871       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
872     // When AC is used, it should be configured as the 1st CP.
873     // In Master, the table operations like create, are handled by a Thread pool but the max size
874     // for this pool is 1. So if multiple CPs create tables on startup, these creations will happen
875     // sequentially only.
876     // Related code in HMaster#startServiceThreads
877     // {code}
878     //   // We depend on there being only one instance of this executor running
879     //   // at a time. To do concurrency, would need fencing of enable/disable of
880     //   // tables.
881     //   this.service.startExecutorService(ExecutorType.MASTER_TABLE_OPERATIONS, 1);
882     // {code}
883     // In future if we change this pool to have more threads, then there is a chance for thread,
884     // creating acl table, getting delayed and by that time another table creation got over and
885     // this hook is getting called. In such a case, we will need a wait logic here which will
886     // wait till the acl table is created.
887     if (AccessControlLists.isAclTable(desc)) {
888       this.aclTabAvailable = true;
889     } else if (!(TableName.NAMESPACE_TABLE_NAME.equals(desc.getTableName()))) {
890       if (!aclTabAvailable) {
891         LOG.warn("Not adding owner permission for table " + desc.getTableName() + ". "
892             + AccessControlLists.ACL_TABLE_NAME + " is not yet created. "
893             + getClass().getSimpleName() + " should be configured as the first Coprocessor");
894       } else {
895         String owner = desc.getOwnerString();
896         // default the table owner to current user, if not specified.
897         if (owner == null)
898           owner = getActiveUser().getShortName();
899         final UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
900             desc.getTableName(), null, Action.values());
901         // switch to the real hbase master user for doing the RPC on the ACL table
902         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
903           @Override
904           public Void run() throws Exception {
905             AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(),
906                 userperm, c.getEnvironment().getTable(AccessControlLists.ACL_TABLE_NAME));
907             return null;
908           }
909         });
910       }
911     }
912   }
913 
914   @Override
915   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
916       throws IOException {
917     requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
918   }
919 
920   @Override
921   public void postDeleteTable(final ObserverContext<MasterCoprocessorEnvironment> c,
922       final TableName tableName) throws IOException {
923     final Configuration conf = c.getEnvironment().getConfiguration();
924     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
925       @Override
926       public Void run() throws Exception {
927         AccessControlLists.removeTablePermissions(conf, tableName,
928             c.getEnvironment().getTable(AccessControlLists.ACL_TABLE_NAME));
929         return null;
930       }
931     });
932     getAuthManager().getZKPermissionWatcher().deleteTableACLNode(tableName);
933   }
934 
935   @Override
936   public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c,
937       final TableName tableName) throws IOException {
938     requirePermission("truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE);
939 
940     final Configuration conf = c.getEnvironment().getConfiguration();
941     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
942       @Override
943       public Void run() throws Exception {
944         List<UserPermission> acls = AccessControlLists.getUserTablePermissions(conf, tableName);
945         if (acls != null) {
946           tableAcls.put(tableName, acls);
947         }
948         return null;
949       }
950     });
951   }
952 
953   @Override
954   public void postTruncateTable(final ObserverContext<MasterCoprocessorEnvironment> ctx,
955       final TableName tableName) throws IOException {
956     final Configuration conf = ctx.getEnvironment().getConfiguration();
957     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
958       @Override
959       public Void run() throws Exception {
960         List<UserPermission> perms = tableAcls.get(tableName);
961         if (perms != null) {
962           for (UserPermission perm : perms) {
963             AccessControlLists.addUserPermission(conf, perm,
964                 ctx.getEnvironment().getTable(AccessControlLists.ACL_TABLE_NAME));
965           }
966         }
967         tableAcls.remove(tableName);
968         return null;
969       }
970     });
971   }
972 
973   @Override
974   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
975       HTableDescriptor htd) throws IOException {
976     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
977   }
978 
979   @Override
980   public void postModifyTable(final ObserverContext<MasterCoprocessorEnvironment> c,
981       TableName tableName, final HTableDescriptor htd) throws IOException {
982     final Configuration conf = c.getEnvironment().getConfiguration();
983     // default the table owner to current user, if not specified.
984     final String owner = (htd.getOwnerString() != null) ? htd.getOwnerString() :
985       getActiveUser().getShortName();
986     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
987       @Override
988       public Void run() throws Exception {
989         UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
990             htd.getTableName(), null, Action.values());
991         AccessControlLists.addUserPermission(conf, userperm,
992             c.getEnvironment().getTable(AccessControlLists.ACL_TABLE_NAME));
993         return null;
994       }
995     });
996   }
997 
998   @Override
999   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1000       HColumnDescriptor column) throws IOException {
1001     requireTablePermission("addColumn", tableName, column.getName(), null, Action.ADMIN,
1002         Action.CREATE);
1003   }
1004 
1005   @Override
1006   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1007       HColumnDescriptor descriptor) throws IOException {
1008     requirePermission("modifyColumn", tableName, descriptor.getName(), null, Action.ADMIN,
1009         Action.CREATE);
1010   }
1011 
1012   @Override
1013   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1014       byte[] col) throws IOException {
1015     requirePermission("deleteColumn", tableName, col, null, Action.ADMIN, Action.CREATE);
1016   }
1017 
1018   @Override
1019   public void postDeleteColumn(final ObserverContext<MasterCoprocessorEnvironment> c,
1020       final TableName tableName, final byte[] col) throws IOException {
1021     final Configuration conf = c.getEnvironment().getConfiguration();
1022     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1023       @Override
1024       public Void run() throws Exception {
1025         AccessControlLists.removeTablePermissions(conf, tableName, col,
1026             c.getEnvironment().getTable(AccessControlLists.ACL_TABLE_NAME));
1027         return null;
1028       }
1029     });
1030   }
1031 
1032   @Override
1033   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1034       throws IOException {
1035     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1036   }
1037 
1038   @Override
1039   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1040       throws IOException {
1041     if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
1042       // We have to unconditionally disallow disable of the ACL table when we are installed,
1043       // even if not enforcing authorizations. We are still allowing grants and revocations,
1044       // checking permissions and logging audit messages, etc. If the ACL table is not
1045       // available we will fail random actions all over the place.
1046       throw new AccessDeniedException("Not allowed to disable "
1047           + AccessControlLists.ACL_TABLE_NAME + " table with AccessController installed");
1048     }
1049     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1050   }
1051 
1052   @Override
1053   public void preAbortProcedure(
1054       ObserverContext<MasterCoprocessorEnvironment> ctx,
1055       final ProcedureExecutor<MasterProcedureEnv> procEnv,
1056       final long procId) throws IOException {
1057     if (!procEnv.isProcedureOwner(procId, getActiveUser())) {
1058       // If the user is not the procedure owner, then we should further probe whether
1059       // he can abort the procedure.
1060       requirePermission("abortProcedure", Action.ADMIN);
1061     }
1062   }
1063 
1064   @Override
1065   public void postAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx)
1066       throws IOException {
1067     // There is nothing to do at this time after the procedure abort request was sent.
1068   }
1069 
1070   @Override
1071   public void preListProcedures(ObserverContext<MasterCoprocessorEnvironment> ctx)
1072       throws IOException {
1073     // We are delegating the authorization check to postListProcedures as we don't have
1074     // any concrete set of procedures to work with
1075   }
1076 
1077   @Override
1078   public void postListProcedures(
1079       ObserverContext<MasterCoprocessorEnvironment> ctx,
1080       List<ProcedureInfo> procInfoList) throws IOException {
1081     if (procInfoList.isEmpty()) {
1082       return;
1083     }
1084 
1085     // Retains only those which passes authorization checks, as the checks weren't done as part
1086     // of preListProcedures.
1087     Iterator<ProcedureInfo> itr = procInfoList.iterator();
1088     User user = getActiveUser();
1089     while (itr.hasNext()) {
1090       ProcedureInfo procInfo = itr.next();
1091       try {
1092         if (!ProcedureInfo.isProcedureOwner(procInfo, user)) {
1093           // If the user is not the procedure owner, then we should further probe whether
1094           // he can see the procedure.
1095           requirePermission("listProcedures", Action.ADMIN);
1096         }
1097       } catch (AccessDeniedException e) {
1098         itr.remove();
1099       }
1100     }
1101   }
1102 
1103   @Override
1104   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
1105       ServerName srcServer, ServerName destServer) throws IOException {
1106     requirePermission("move", region.getTable(), null, null, Action.ADMIN);
1107   }
1108 
1109   @Override
1110   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
1111       throws IOException {
1112     requirePermission("assign", regionInfo.getTable(), null, null, Action.ADMIN);
1113   }
1114 
1115   @Override
1116   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
1117       boolean force) throws IOException {
1118     requirePermission("unassign", regionInfo.getTable(), null, null, Action.ADMIN);
1119   }
1120 
1121   @Override
1122   public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
1123       HRegionInfo regionInfo) throws IOException {
1124     requirePermission("regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
1125   }
1126 
1127   @Override
1128   public boolean preSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1129       final boolean newValue, final Admin.MasterSwitchType switchType) throws IOException {
1130     requirePermission("setSplitOrMergeEnabled", Action.ADMIN);
1131     return false;
1132   }
1133 
1134   @Override
1135   public void postSetSplitOrMergeEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1136       final boolean newValue, final Admin.MasterSwitchType switchType) throws IOException {
1137   }
1138 
1139   @Override
1140   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
1141       throws IOException {
1142     requirePermission("balance", Action.ADMIN);
1143   }
1144 
1145   @Override
1146   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
1147       boolean newValue) throws IOException {
1148     requirePermission("balanceSwitch", Action.ADMIN);
1149     return newValue;
1150   }
1151 
1152   @Override
1153   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
1154       throws IOException {
1155     requirePermission("shutdown", Action.ADMIN);
1156   }
1157 
1158   @Override
1159   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
1160       throws IOException {
1161     requirePermission("stopMaster", Action.ADMIN);
1162   }
1163 
1164   @Override
1165   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
1166       throws IOException {
1167     if (!MetaTableAccessor.tableExists(ctx.getEnvironment().getMasterServices()
1168       .getConnection(), AccessControlLists.ACL_TABLE_NAME)) {
1169       // initialize the ACL storage table
1170       AccessControlLists.createACLTable(ctx.getEnvironment().getMasterServices());
1171     } else {
1172       aclTabAvailable = true;
1173     }
1174   }
1175 
1176   @Override
1177   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1178       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1179       throws IOException {
1180     requirePermission("snapshot " + snapshot.getName(), hTableDescriptor.getTableName(), null, null,
1181       Permission.Action.ADMIN);
1182   }
1183 
1184   @Override
1185   public void preListSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
1186       final SnapshotDescription snapshot) throws IOException {
1187     User user = getActiveUser();
1188     if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) {
1189       // list it, if user is the owner of snapshot
1190       AuthResult result = AuthResult.allow("listSnapshot " + snapshot.getName(),
1191           "Snapshot owner check allowed", user, null, null, null);
1192       accessChecker.logResult(result);
1193     } else {
1194       requirePermission("listSnapshot " + snapshot.getName(), Action.ADMIN);
1195     }
1196   }
1197 
1198   @Override
1199   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1200       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1201       throws IOException {
1202     requirePermission("cloneSnapshot " + snapshot.getName(), Action.ADMIN);
1203   }
1204 
1205   @Override
1206   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1207       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1208       throws IOException {
1209     if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, getActiveUser())) {
1210       requirePermission("restoreSnapshot " + snapshot.getName(), hTableDescriptor.getTableName(),
1211         null, null, Permission.Action.ADMIN);
1212     } else {
1213       requirePermission("restoreSnapshot " + snapshot.getName(), Action.ADMIN);
1214     }
1215   }
1216 
1217   @Override
1218   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1219       final SnapshotDescription snapshot) throws IOException {
1220     User user = getActiveUser();
1221     if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, user)) {
1222       // Snapshot owner is allowed to delete the snapshot
1223       AuthResult result = AuthResult.allow("deleteSnapshot " + snapshot.getName(),
1224           "Snapshot owner check allowed", user, null, null, null);
1225       accessChecker.logResult(result);
1226     } else {
1227       requirePermission("deleteSnapshot " + snapshot.getName(), Action.ADMIN);
1228     }
1229   }
1230 
1231   @Override
1232   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1233       NamespaceDescriptor ns) throws IOException {
1234     requireGlobalPermission("createNamespace", Action.ADMIN, ns.getName());
1235   }
1236 
1237   @Override
1238   public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1239       throws IOException {
1240     requireGlobalPermission("deleteNamespace", Action.ADMIN, namespace);
1241   }
1242 
1243   @Override
1244   public void postDeleteNamespace(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1245       final String namespace) throws IOException {
1246     final Configuration conf = ctx.getEnvironment().getConfiguration();
1247     User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1248       @Override
1249       public Void run() throws Exception {
1250         AccessControlLists.removeNamespacePermissions(conf, namespace,
1251             ctx.getEnvironment().getTable(AccessControlLists.ACL_TABLE_NAME));
1252         return null;
1253       }
1254     });
1255     getAuthManager().getZKPermissionWatcher().deleteNamespaceACLNode(namespace);
1256     LOG.info(namespace + " entry deleted in " + AccessControlLists.ACL_TABLE_NAME + " table.");
1257   }
1258 
1259   @Override
1260   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1261       NamespaceDescriptor ns) throws IOException {
1262     // We require only global permission so that
1263     // a user with NS admin cannot altering namespace configurations. i.e. namespace quota
1264     requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName());
1265   }
1266 
1267   @Override
1268   public void preGetNamespaceDescriptor(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1269       throws IOException {
1270     requireNamespacePermission("getNamespaceDescriptor", namespace, Action.ADMIN);
1271   }
1272 
1273   @Override
1274   public void postListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1275       List<NamespaceDescriptor> descriptors) throws IOException {
1276     // Retains only those which passes authorization checks, as the checks weren't done as part
1277     // of preGetTableDescriptors.
1278     Iterator<NamespaceDescriptor> itr = descriptors.iterator();
1279     while (itr.hasNext()) {
1280       NamespaceDescriptor desc = itr.next();
1281       try {
1282         requireNamespacePermission("listNamespaces", desc.getName(), Action.ADMIN);
1283       } catch (AccessDeniedException e) {
1284         itr.remove();
1285       }
1286     }
1287   }
1288 
1289   @Override
1290   public void preTableFlush(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1291       final TableName tableName) throws IOException {
1292     requirePermission("flushTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1293   }
1294 
1295   /* ---- RegionObserver implementation ---- */
1296 
1297   @Override
1298   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
1299       throws IOException {
1300     RegionCoprocessorEnvironment env = e.getEnvironment();
1301     final Region region = env.getRegion();
1302     if (region == null) {
1303       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
1304     } else {
1305       HRegionInfo regionInfo = region.getRegionInfo();
1306       if (regionInfo.getTable().isSystemTable()) {
1307         checkSystemOrSuperUser();
1308       } else {
1309         requirePermission("preOpen", Action.ADMIN);
1310       }
1311     }
1312   }
1313 
1314   @Override
1315   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
1316     RegionCoprocessorEnvironment env = c.getEnvironment();
1317     final Region region = env.getRegion();
1318     if (region == null) {
1319       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
1320       return;
1321     }
1322     if (AccessControlLists.isAclRegion(region)) {
1323       aclRegion = true;
1324       // When this region is under recovering state, initialize will be handled by postLogReplay
1325       if (!region.isRecovering()) {
1326         try {
1327           initialize(env);
1328         } catch (IOException ex) {
1329           // if we can't obtain permissions, it's better to fail
1330           // than perform checks incorrectly
1331           throw new RuntimeException("Failed to initialize permissions cache", ex);
1332         }
1333       }
1334     } else {
1335       initialized = true;
1336     }
1337   }
1338 
1339   @Override
1340   public void postLogReplay(ObserverContext<RegionCoprocessorEnvironment> c) {
1341     if (aclRegion) {
1342       try {
1343         initialize(c.getEnvironment());
1344       } catch (IOException ex) {
1345         // if we can't obtain permissions, it's better to fail
1346         // than perform checks incorrectly
1347         throw new RuntimeException("Failed to initialize permissions cache", ex);
1348       }
1349     }
1350   }
1351 
1352   @Override
1353   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1354     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1355         Action.CREATE);
1356   }
1357 
1358   @Override
1359   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1360     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1361   }
1362 
1363   @Override
1364   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
1365       byte[] splitRow) throws IOException {
1366     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1367   }
1368 
1369   @Override
1370   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
1371       final Store store, final InternalScanner scanner, final ScanType scanType)
1372           throws IOException {
1373     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1374         Action.CREATE);
1375     return scanner;
1376   }
1377 
1378   @Override
1379   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
1380       final byte [] row, final byte [] family, final Result result)
1381       throws IOException {
1382     assert family != null;
1383     RegionCoprocessorEnvironment env = c.getEnvironment();
1384     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, null);
1385     User user = getActiveUser();
1386     AuthResult authResult = permissionGranted(OpType.GET_CLOSEST_ROW_BEFORE, user, env, families,
1387       Action.READ);
1388     if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1389       authResult.setAllowed(checkCoveringPermission(OpType.GET_CLOSEST_ROW_BEFORE, env, row,
1390         families, HConstants.LATEST_TIMESTAMP, Action.READ));
1391       authResult.setReason("Covering cell set");
1392     }
1393     accessChecker.logResult(authResult);
1394     if (authorizationEnabled && !authResult.isAllowed()) {
1395       throw new AccessDeniedException("Insufficient permissions " +
1396         authResult.toContextString());
1397     }
1398   }
1399 
1400   private void internalPreRead(final ObserverContext<RegionCoprocessorEnvironment> c,
1401       final Query query, OpType opType) throws IOException {
1402     Filter filter = query.getFilter();
1403     // Don't wrap an AccessControlFilter
1404     if (filter != null && filter instanceof AccessControlFilter) {
1405       return;
1406     }
1407     User user = getActiveUser();
1408     RegionCoprocessorEnvironment env = c.getEnvironment();
1409     Map<byte[],? extends Collection<byte[]>> families = null;
1410     switch (opType) {
1411     case GET:
1412     case EXISTS:
1413       families = ((Get)query).getFamilyMap();
1414       break;
1415     case SCAN:
1416       families = ((Scan)query).getFamilyMap();
1417       break;
1418     default:
1419       throw new RuntimeException("Unhandled operation " + opType);
1420     }
1421     AuthResult authResult = permissionGranted(opType, user, env, families, Action.READ);
1422     Region region = getRegion(env);
1423     TableName table = getTableName(region);
1424     Map<ByteRange, Integer> cfVsMaxVersions = Maps.newHashMap();
1425     for (HColumnDescriptor hcd : region.getTableDesc().getFamilies()) {
1426       cfVsMaxVersions.put(new SimpleMutableByteRange(hcd.getName()), hcd.getMaxVersions());
1427     }
1428     if (!authResult.isAllowed()) {
1429       if (!cellFeaturesEnabled || compatibleEarlyTermination) {
1430         // Old behavior: Scan with only qualifier checks if we have partial
1431         // permission. Backwards compatible behavior is to throw an
1432         // AccessDeniedException immediately if there are no grants for table
1433         // or CF or CF+qual. Only proceed with an injected filter if there are
1434         // grants for qualifiers. Otherwise we will fall through below and log
1435         // the result and throw an ADE. We may end up checking qualifier
1436         // grants three times (permissionGranted above, here, and in the
1437         // filter) but that's the price of backwards compatibility.
1438         if (hasFamilyQualifierPermission(user, Action.READ, env, families)) {
1439           authResult.setAllowed(true);
1440           authResult.setReason("Access allowed with filter");
1441           // Only wrap the filter if we are enforcing authorizations
1442           if (authorizationEnabled) {
1443             Filter ourFilter = new AccessControlFilter(getAuthManager(), user, table,
1444               AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY,
1445               cfVsMaxVersions);
1446             // wrap any existing filter
1447             if (filter != null) {
1448               ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1449                 Lists.newArrayList(ourFilter, filter));
1450             }
1451             switch (opType) {
1452               case GET:
1453               case EXISTS:
1454                 ((Get)query).setFilter(ourFilter);
1455                 break;
1456               case SCAN:
1457                 ((Scan)query).setFilter(ourFilter);
1458                 break;
1459               default:
1460                 throw new RuntimeException("Unhandled operation " + opType);
1461             }
1462           }
1463         }
1464       } else {
1465         // New behavior: Any access we might be granted is more fine-grained
1466         // than whole table or CF. Simply inject a filter and return what is
1467         // allowed. We will not throw an AccessDeniedException. This is a
1468         // behavioral change since 0.96.
1469         authResult.setAllowed(true);
1470         authResult.setReason("Access allowed with filter");
1471         // Only wrap the filter if we are enforcing authorizations
1472         if (authorizationEnabled) {
1473           Filter ourFilter = new AccessControlFilter(getAuthManager(), user, table,
1474             AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions);
1475           // wrap any existing filter
1476           if (filter != null) {
1477             ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1478               Lists.newArrayList(ourFilter, filter));
1479           }
1480           switch (opType) {
1481             case GET:
1482             case EXISTS:
1483               ((Get)query).setFilter(ourFilter);
1484               break;
1485             case SCAN:
1486               ((Scan)query).setFilter(ourFilter);
1487               break;
1488             default:
1489               throw new RuntimeException("Unhandled operation " + opType);
1490           }
1491         }
1492       }
1493     }
1494 
1495     accessChecker.logResult(authResult);
1496     if (authorizationEnabled && !authResult.isAllowed()) {
1497       throw new AccessDeniedException("Insufficient permissions for user '"
1498           + (user != null ? user.getShortName() : "null")
1499           + "' (table=" + table + ", action=READ)");
1500     }
1501   }
1502 
1503   @Override
1504   public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> c,
1505       final Get get, final List<Cell> result) throws IOException {
1506     internalPreRead(c, get, OpType.GET);
1507   }
1508 
1509   @Override
1510   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
1511       final Get get, final boolean exists) throws IOException {
1512     internalPreRead(c, get, OpType.EXISTS);
1513     return exists;
1514   }
1515 
1516   @Override
1517   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
1518       final Put put, final WALEdit edit, final Durability durability)
1519       throws IOException {
1520     User user = getActiveUser();
1521     checkForReservedTagPresence(user, put);
1522 
1523     // Require WRITE permission to the table, CF, or top visible value, if any.
1524     // NOTE: We don't need to check the permissions for any earlier Puts
1525     // because we treat the ACLs in each Put as timestamped like any other
1526     // HBase value. A new ACL in a new Put applies to that Put. It doesn't
1527     // change the ACL of any previous Put. This allows simple evolution of
1528     // security policy over time without requiring expensive updates.
1529     RegionCoprocessorEnvironment env = c.getEnvironment();
1530     Map<byte[],? extends Collection<Cell>> families = put.getFamilyCellMap();
1531     AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE);
1532     accessChecker.logResult(authResult);
1533     if (!authResult.isAllowed()) {
1534       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1535         put.setAttribute(CHECK_COVERING_PERM, TRUE);
1536       } else if (authorizationEnabled) {
1537         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1538       }
1539     }
1540 
1541     // Add cell ACLs from the operation to the cells themselves
1542     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1543     if (bytes != null) {
1544       if (cellFeaturesEnabled) {
1545         addCellPermissions(bytes, put.getFamilyCellMap());
1546       } else {
1547         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1548       }
1549     }
1550   }
1551 
1552   @Override
1553   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1554       final Put put, final WALEdit edit, final Durability durability) {
1555     if (aclRegion) {
1556       updateACL(c.getEnvironment(), put.getFamilyCellMap());
1557     }
1558   }
1559 
1560   @Override
1561   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1562       final Delete delete, final WALEdit edit, final Durability durability)
1563       throws IOException {
1564     // An ACL on a delete is useless, we shouldn't allow it
1565     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1566       throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString());
1567     }
1568     // Require WRITE permissions on all cells covered by the delete. Unlike
1569     // for Puts we need to check all visible prior versions, because a major
1570     // compaction could remove them. If the user doesn't have permission to
1571     // overwrite any of the visible versions ('visible' defined as not covered
1572     // by a tombstone already) then we have to disallow this operation.
1573     RegionCoprocessorEnvironment env = c.getEnvironment();
1574     Map<byte[],? extends Collection<Cell>> families = delete.getFamilyCellMap();
1575     User user = getActiveUser();
1576     AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE);
1577     accessChecker.logResult(authResult);
1578     if (!authResult.isAllowed()) {
1579       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1580         delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1581       } else if (authorizationEnabled) {
1582         throw new AccessDeniedException("Insufficient permissions " +
1583           authResult.toContextString());
1584       }
1585     }
1586   }
1587 
1588   @Override
1589   public void preBatchMutate(ObserverContext<RegionCoprocessorEnvironment> c,
1590       MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1591     if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1592       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1593       for (int i = 0; i < miniBatchOp.size(); i++) {
1594         Mutation m = miniBatchOp.getOperation(i);
1595         if (m.getAttribute(CHECK_COVERING_PERM) != null) {
1596           // We have a failure with table, cf and q perm checks and now giving a chance for cell
1597           // perm check
1598           OpType opType;
1599           if (m instanceof Put) {
1600             checkForReservedTagPresence(getActiveUser(), m);
1601             opType = OpType.PUT;
1602           } else {
1603             opType = OpType.DELETE;
1604           }
1605           AuthResult authResult = null;
1606           if (checkCoveringPermission(opType, c.getEnvironment(), m.getRow(),
1607             m.getFamilyCellMap(), m.getTimeStamp(), Action.WRITE)) {
1608             authResult = AuthResult.allow(opType.toString(), "Covering cell set",
1609               getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1610           } else {
1611             authResult = AuthResult.deny(opType.toString(), "Covering cell set",
1612               getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1613           }
1614           accessChecker.logResult(authResult);
1615           if (authorizationEnabled && !authResult.isAllowed()) {
1616             throw new AccessDeniedException("Insufficient permissions "
1617               + authResult.toContextString());
1618           }
1619         }
1620       }
1621     }
1622   }
1623 
1624   @Override
1625   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1626       final Delete delete, final WALEdit edit, final Durability durability)
1627       throws IOException {
1628     if (aclRegion) {
1629       updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1630     }
1631   }
1632 
1633   @Override
1634   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1635       final byte [] row, final byte [] family, final byte [] qualifier,
1636       final CompareFilter.CompareOp compareOp,
1637       final ByteArrayComparable comparator, final Put put,
1638       final boolean result) throws IOException {
1639     User user = getActiveUser();
1640     checkForReservedTagPresence(user, put);
1641 
1642     // Require READ and WRITE permissions on the table, CF, and KV to update
1643     RegionCoprocessorEnvironment env = c.getEnvironment();
1644     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1645     AuthResult authResult = permissionGranted(OpType.CHECK_AND_PUT, user, env, families,
1646       Action.READ, Action.WRITE);
1647     accessChecker.logResult(authResult);
1648     if (!authResult.isAllowed()) {
1649       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1650         put.setAttribute(CHECK_COVERING_PERM, TRUE);
1651       } else if (authorizationEnabled) {
1652         throw new AccessDeniedException("Insufficient permissions " +
1653           authResult.toContextString());
1654       }
1655     }
1656 
1657     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1658     if (bytes != null) {
1659       if (cellFeaturesEnabled) {
1660         addCellPermissions(bytes, put.getFamilyCellMap());
1661       } else {
1662         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1663       }
1664     }
1665     return result;
1666   }
1667 
1668   @Override
1669   public boolean preCheckAndPutAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1670       final byte[] row, final byte[] family, final byte[] qualifier,
1671       final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Put put,
1672       final boolean result) throws IOException {
1673     if (put.getAttribute(CHECK_COVERING_PERM) != null) {
1674       // We had failure with table, cf and q perm checks and now giving a chance for cell
1675       // perm check
1676       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1677       Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1678       AuthResult authResult = null;
1679       if (checkCoveringPermission(OpType.CHECK_AND_PUT, c.getEnvironment(), row, families,
1680           HConstants.LATEST_TIMESTAMP, Action.READ)) {
1681         authResult = AuthResult.allow(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1682             getActiveUser(), Action.READ, table, families);
1683       } else {
1684         authResult = AuthResult.deny(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1685             getActiveUser(), Action.READ, table, families);
1686       }
1687       accessChecker.logResult(authResult);
1688       if (authorizationEnabled && !authResult.isAllowed()) {
1689         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1690       }
1691     }
1692     return result;
1693   }
1694 
1695   @Override
1696   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1697       final byte [] row, final byte [] family, final byte [] qualifier,
1698       final CompareFilter.CompareOp compareOp,
1699       final ByteArrayComparable comparator, final Delete delete,
1700       final boolean result) throws IOException {
1701     // An ACL on a delete is useless, we shouldn't allow it
1702     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1703       throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " +
1704           delete.toString());
1705     }
1706     // Require READ and WRITE permissions on the table, CF, and the KV covered
1707     // by the delete
1708     RegionCoprocessorEnvironment env = c.getEnvironment();
1709     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1710     User user = getActiveUser();
1711     AuthResult authResult = permissionGranted(OpType.CHECK_AND_DELETE, user, env, families,
1712         Action.READ, Action.WRITE);
1713     accessChecker.logResult(authResult);
1714     if (!authResult.isAllowed()) {
1715       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1716         delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1717       } else if (authorizationEnabled) {
1718         throw new AccessDeniedException("Insufficient permissions " +
1719           authResult.toContextString());
1720       }
1721     }
1722     return result;
1723   }
1724 
1725   @Override
1726   public boolean preCheckAndDeleteAfterRowLock(
1727       final ObserverContext<RegionCoprocessorEnvironment> c, final byte[] row, final byte[] family,
1728       final byte[] qualifier, final CompareFilter.CompareOp compareOp,
1729       final ByteArrayComparable comparator, final Delete delete, final boolean result)
1730       throws IOException {
1731     if (delete.getAttribute(CHECK_COVERING_PERM) != null) {
1732       // We had failure with table, cf and q perm checks and now giving a chance for cell
1733       // perm check
1734       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1735       Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1736       AuthResult authResult = null;
1737       if (checkCoveringPermission(OpType.CHECK_AND_DELETE, c.getEnvironment(), row, families,
1738           HConstants.LATEST_TIMESTAMP, Action.READ)) {
1739         authResult = AuthResult.allow(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1740             getActiveUser(), Action.READ, table, families);
1741       } else {
1742         authResult = AuthResult.deny(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1743             getActiveUser(), Action.READ, table, families);
1744       }
1745       accessChecker.logResult(authResult);
1746       if (authorizationEnabled && !authResult.isAllowed()) {
1747         throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1748       }
1749     }
1750     return result;
1751   }
1752 
1753   @Override
1754   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
1755       final byte [] row, final byte [] family, final byte [] qualifier,
1756       final long amount, final boolean writeToWAL)
1757       throws IOException {
1758     // Require WRITE permission to the table, CF, and the KV to be replaced by the
1759     // incremented value
1760     RegionCoprocessorEnvironment env = c.getEnvironment();
1761     Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1762     User user = getActiveUser();
1763     AuthResult authResult = permissionGranted(OpType.INCREMENT_COLUMN_VALUE, user, env, families,
1764         Action.WRITE);
1765     if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1766       authResult.setAllowed(checkCoveringPermission(OpType.INCREMENT_COLUMN_VALUE, env, row,
1767         families, HConstants.LATEST_TIMESTAMP, Action.WRITE));
1768       authResult.setReason("Covering cell set");
1769     }
1770     accessChecker.logResult(authResult);
1771     if (authorizationEnabled && !authResult.isAllowed()) {
1772       throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1773     }
1774     return -1;
1775   }
1776 
1777   @Override
1778   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
1779       throws IOException {
1780     User user = getActiveUser();
1781     checkForReservedTagPresence(user, append);
1782 
1783     // Require WRITE permission to the table, CF, and the KV to be appended
1784     RegionCoprocessorEnvironment env = c.getEnvironment();
1785     Map<byte[],? extends Collection<Cell>> families = append.getFamilyCellMap();
1786     AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE);
1787     accessChecker.logResult(authResult);
1788     if (!authResult.isAllowed()) {
1789       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1790         append.setAttribute(CHECK_COVERING_PERM, TRUE);
1791       } else if (authorizationEnabled)  {
1792         throw new AccessDeniedException("Insufficient permissions " +
1793           authResult.toContextString());
1794       }
1795     }
1796 
1797     byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1798     if (bytes != null) {
1799       if (cellFeaturesEnabled) {
1800         addCellPermissions(bytes, append.getFamilyCellMap());
1801       } else {
1802         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1803       }
1804     }
1805 
1806     return null;
1807   }
1808 
1809   @Override
1810   public Result preAppendAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1811       final Append append) throws IOException {
1812     if (append.getAttribute(CHECK_COVERING_PERM) != null) {
1813       // We had failure with table, cf and q perm checks and now giving a chance for cell
1814       // perm check
1815       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1816       AuthResult authResult = null;
1817       if (checkCoveringPermission(OpType.APPEND, c.getEnvironment(), append.getRow(),
1818           append.getFamilyCellMap(), HConstants.LATEST_TIMESTAMP, Action.WRITE)) {
1819         authResult = AuthResult.allow(OpType.APPEND.toString(), "Covering cell set",
1820             getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1821       } else {
1822         authResult = AuthResult.deny(OpType.APPEND.toString(), "Covering cell set",
1823             getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1824       }
1825       accessChecker.logResult(authResult);
1826       if (authorizationEnabled && !authResult.isAllowed()) {
1827         throw new AccessDeniedException("Insufficient permissions " +
1828           authResult.toContextString());
1829       }
1830     }
1831     return null;
1832   }
1833 
1834   @Override
1835   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
1836       final Increment increment)
1837       throws IOException {
1838     User user = getActiveUser();
1839     checkForReservedTagPresence(user, increment);
1840 
1841     // Require WRITE permission to the table, CF, and the KV to be replaced by
1842     // the incremented value
1843     RegionCoprocessorEnvironment env = c.getEnvironment();
1844     Map<byte[],? extends Collection<Cell>> families = increment.getFamilyCellMap();
1845     AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families,
1846       Action.WRITE);
1847     accessChecker.logResult(authResult);
1848     if (!authResult.isAllowed()) {
1849       if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1850         increment.setAttribute(CHECK_COVERING_PERM, TRUE);
1851       } else if (authorizationEnabled) {
1852         throw new AccessDeniedException("Insufficient permissions " +
1853           authResult.toContextString());
1854       }
1855     }
1856 
1857     byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1858     if (bytes != null) {
1859       if (cellFeaturesEnabled) {
1860         addCellPermissions(bytes, increment.getFamilyCellMap());
1861       } else {
1862         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1863       }
1864     }
1865 
1866     return null;
1867   }
1868 
1869   @Override
1870   public Result preIncrementAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1871       final Increment increment) throws IOException {
1872     if (increment.getAttribute(CHECK_COVERING_PERM) != null) {
1873       // We had failure with table, cf and q perm checks and now giving a chance for cell
1874       // perm check
1875       TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1876       AuthResult authResult = null;
1877       if (checkCoveringPermission(OpType.INCREMENT, c.getEnvironment(), increment.getRow(),
1878           increment.getFamilyCellMap(), increment.getTimeRange().getMax(), Action.WRITE)) {
1879         authResult = AuthResult.allow(OpType.INCREMENT.toString(), "Covering cell set",
1880             getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1881       } else {
1882         authResult = AuthResult.deny(OpType.INCREMENT.toString(), "Covering cell set",
1883             getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1884       }
1885       accessChecker.logResult(authResult);
1886       if (authorizationEnabled && !authResult.isAllowed()) {
1887         throw new AccessDeniedException("Insufficient permissions " +
1888           authResult.toContextString());
1889       }
1890     }
1891     return null;
1892   }
1893 
1894   @Override
1895   public Cell postMutationBeforeWAL(ObserverContext<RegionCoprocessorEnvironment> ctx,
1896       MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException {
1897     // If the HFile version is insufficient to persist tags, we won't have any
1898     // work to do here
1899     if (!cellFeaturesEnabled) {
1900       return newCell;
1901     }
1902 
1903     // Collect any ACLs from the old cell
1904     List<Tag> tags = Lists.newArrayList();
1905     ListMultimap<String,Permission> perms = ArrayListMultimap.create();
1906     if (oldCell != null) {
1907       // Save an object allocation where we can
1908       if (oldCell.getTagsLength() > 0) {
1909         Iterator<Tag> tagIterator = CellUtil.tagsIterator(oldCell.getTagsArray(),
1910           oldCell.getTagsOffset(), oldCell.getTagsLength());
1911         while (tagIterator.hasNext()) {
1912           Tag tag = tagIterator.next();
1913           if (tag.getType() != AccessControlLists.ACL_TAG_TYPE) {
1914             // Not an ACL tag, just carry it through
1915             if (LOG.isTraceEnabled()) {
1916               LOG.trace("Carrying forward tag from " + oldCell + ": type " + tag.getType() +
1917                 " length " + tag.getTagLength());
1918             }
1919             tags.add(tag);
1920           } else {
1921             // Merge the perms from the older ACL into the current permission set
1922             // TODO: The efficiency of this can be improved. Don't build just to unpack
1923             // again, use the builder
1924             AccessControlProtos.UsersAndPermissions.Builder builder =
1925               AccessControlProtos.UsersAndPermissions.newBuilder();
1926             ProtobufUtil.mergeFrom(builder, tag.getBuffer(), tag.getTagOffset(), tag.getTagLength());
1927             ListMultimap<String,Permission> kvPerms =
1928               ProtobufUtil.toUsersAndPermissions(builder.build());
1929             perms.putAll(kvPerms);
1930           }
1931         }
1932       }
1933     }
1934 
1935     // Do we have an ACL on the operation?
1936     byte[] aclBytes = mutation.getACL();
1937     if (aclBytes != null) {
1938       // Yes, use it
1939       tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, aclBytes));
1940     } else {
1941       // No, use what we carried forward
1942       if (perms != null) {
1943         // TODO: If we collected ACLs from more than one tag we may have a
1944         // List<Permission> of size > 1, this can be collapsed into a single
1945         // Permission
1946         if (LOG.isTraceEnabled()) {
1947           LOG.trace("Carrying forward ACLs from " + oldCell + ": " + perms);
1948         }
1949         tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE,
1950             ProtobufUtil.toUsersAndPermissions(perms).toByteArray()));
1951       }
1952     }
1953 
1954     // If we have no tags to add, just return
1955     if (tags.isEmpty()) {
1956       return newCell;
1957     }
1958 
1959     Cell rewriteCell = new TagRewriteCell(newCell, Tag.fromList(tags));
1960     return rewriteCell;
1961   }
1962 
1963   @Override
1964   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1965       final Scan scan, final RegionScanner s) throws IOException {
1966     internalPreRead(c, scan, OpType.SCAN);
1967     return s;
1968   }
1969 
1970   @Override
1971   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1972       final Scan scan, final RegionScanner s) throws IOException {
1973     User user = getActiveUser();
1974     if (user != null && user.getShortName() != null) {
1975       // store reference to scanner owner for later checks
1976       scannerOwners.put(s, user.getShortName());
1977     }
1978     return s;
1979   }
1980 
1981   @Override
1982   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
1983       final InternalScanner s, final List<Result> result,
1984       final int limit, final boolean hasNext) throws IOException {
1985     requireScannerOwner(s);
1986     return hasNext;
1987   }
1988 
1989   @Override
1990   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1991       final InternalScanner s) throws IOException {
1992     requireScannerOwner(s);
1993   }
1994 
1995   @Override
1996   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1997       final InternalScanner s) throws IOException {
1998     // clean up any associated owner mapping
1999     scannerOwners.remove(s);
2000   }
2001 
2002   /**
2003    * Verify, when servicing an RPC, that the caller is the scanner owner.
2004    * If so, we assume that access control is correctly enforced based on
2005    * the checks performed in preScannerOpen()
2006    */
2007   private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
2008     if (!RpcServer.isInRpcCallContext())
2009       return;
2010     String requestUserName = RpcServer.getRequestUserName();
2011     String owner = scannerOwners.get(s);
2012     if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) {
2013       throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
2014     }
2015   }
2016 
2017   /**
2018    * Verifies user has CREATE privileges on
2019    * the Column Families involved in the bulkLoadHFile
2020    * request. Specific Column Write privileges are presently
2021    * ignored.
2022    */
2023   @Override
2024   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
2025       List<Pair<byte[], String>> familyPaths) throws IOException {
2026     for(Pair<byte[],String> el : familyPaths) {
2027       requirePermission("preBulkLoadHFile",
2028           ctx.getEnvironment().getRegion().getTableDesc().getTableName(),
2029           el.getFirst(),
2030           null,
2031           Action.CREATE);
2032     }
2033   }
2034 
2035   /**
2036    * Authorization check for
2037    * SecureBulkLoadProtocol.prepareBulkLoad()
2038    * @param ctx the context
2039    * @param request the request
2040    * @throws IOException
2041    */
2042   @Override
2043   public void prePrepareBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2044                                  PrepareBulkLoadRequest request) throws IOException {
2045     requireAccess("prePareBulkLoad",
2046         ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2047   }
2048 
2049   /**
2050    * Authorization security check for
2051    * SecureBulkLoadProtocol.cleanupBulkLoad()
2052    * @param ctx the context
2053    * @param request the request
2054    * @throws IOException
2055    */
2056   @Override
2057   public void preCleanupBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2058                                  CleanupBulkLoadRequest request) throws IOException {
2059     requireAccess("preCleanupBulkLoad",
2060         ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2061   }
2062 
2063   /* ---- EndpointObserver implementation ---- */
2064 
2065   @Override
2066   public Message preEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2067       Service service, String methodName, Message request) throws IOException {
2068     // Don't intercept calls to our own AccessControlService, we check for
2069     // appropriate permissions in the service handlers
2070     if (shouldCheckExecPermission && !(service instanceof AccessControlService)) {
2071       requirePermission("invoke(" + service.getDescriptorForType().getName() + "." +
2072         methodName + ")",
2073         getTableName(ctx.getEnvironment()), null, null,
2074         Action.EXEC);
2075     }
2076     return request;
2077   }
2078 
2079   @Override
2080   public void postEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2081       Service service, String methodName, Message request, Message.Builder responseBuilder)
2082       throws IOException { }
2083 
2084   /* ---- Protobuf AccessControlService implementation ---- */
2085 
2086   @Override
2087   public void grant(RpcController controller,
2088                     final AccessControlProtos.GrantRequest request,
2089                     RpcCallback<AccessControlProtos.GrantResponse> done) {
2090     final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2091     AccessControlProtos.GrantResponse response = null;
2092     try {
2093       // verify it's only running at .acl.
2094       if (aclRegion) {
2095         if (!initialized) {
2096           throw new CoprocessorException("AccessController not yet initialized");
2097         }
2098         if (LOG.isDebugEnabled()) {
2099           LOG.debug("Received request to grant access permission " + perm.toString());
2100         }
2101 
2102         switch(request.getUserPermission().getPermission().getType()) {
2103           case Global :
2104           case Table :
2105             requirePermission("grant", perm.getTableName(), perm.getFamily(),
2106               perm.getQualifier(), Action.ADMIN);
2107             break;
2108           case Namespace :
2109             requireNamespacePermission("grant", perm.getNamespace(), Action.ADMIN);
2110            break;
2111         }
2112 
2113         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2114           @Override
2115           public Void run() throws Exception {
2116             AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm,
2117               regionEnv.getTable(AccessControlLists.ACL_TABLE_NAME));
2118             return null;
2119           }
2120         });
2121 
2122         if (AUDITLOG.isTraceEnabled()) {
2123           // audit log should store permission changes in addition to auth results
2124           AUDITLOG.trace("Granted permission " + perm.toString());
2125         }
2126       } else {
2127         throw new CoprocessorException(AccessController.class, "This method "
2128             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2129       }
2130       response = AccessControlProtos.GrantResponse.getDefaultInstance();
2131     } catch (IOException ioe) {
2132       // pass exception back up
2133       ResponseConverter.setControllerException(controller, ioe);
2134     }
2135     done.run(response);
2136   }
2137 
2138   @Override
2139   public void revoke(RpcController controller,
2140                      AccessControlProtos.RevokeRequest request,
2141                      RpcCallback<AccessControlProtos.RevokeResponse> done) {
2142     final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2143     AccessControlProtos.RevokeResponse response = null;
2144     try {
2145       // only allowed to be called on _acl_ region
2146       if (aclRegion) {
2147         if (!initialized) {
2148           throw new CoprocessorException("AccessController not yet initialized");
2149         }
2150         if (LOG.isDebugEnabled()) {
2151           LOG.debug("Received request to revoke access permission " + perm.toString());
2152         }
2153 
2154         switch(request.getUserPermission().getPermission().getType()) {
2155           case Global :
2156           case Table :
2157             requirePermission("revoke", perm.getTableName(), perm.getFamily(),
2158               perm.getQualifier(), Action.ADMIN);
2159             break;
2160           case Namespace :
2161             requireNamespacePermission("revoke", perm.getNamespace(), Action.ADMIN);
2162             break;
2163         }
2164 
2165         User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2166           @Override
2167           public Void run() throws Exception {
2168             AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm, null);
2169             return null;
2170           }
2171         });
2172 
2173         if (AUDITLOG.isTraceEnabled()) {
2174           // audit log should record all permission changes
2175           AUDITLOG.trace("Revoked permission " + perm.toString());
2176         }
2177       } else {
2178         throw new CoprocessorException(AccessController.class, "This method "
2179             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2180       }
2181       response = AccessControlProtos.RevokeResponse.getDefaultInstance();
2182     } catch (IOException ioe) {
2183       // pass exception back up
2184       ResponseConverter.setControllerException(controller, ioe);
2185     }
2186     done.run(response);
2187   }
2188 
2189   @Override
2190   public void getUserPermissions(RpcController controller,
2191                                  AccessControlProtos.GetUserPermissionsRequest request,
2192                                  RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
2193     AccessControlProtos.GetUserPermissionsResponse response = null;
2194     try {
2195       // only allowed to be called on _acl_ region
2196       if (aclRegion) {
2197         if (!initialized) {
2198           throw new CoprocessorException("AccessController not yet initialized");
2199         }
2200         List<UserPermission> perms = null;
2201         if (request.getType() == AccessControlProtos.Permission.Type.Table) {
2202           final TableName table = request.hasTableName() ?
2203             ProtobufUtil.toTableName(request.getTableName()) : null;
2204           requirePermission("userPermissions", table, null, null, Action.ADMIN);
2205           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2206             @Override
2207             public List<UserPermission> run() throws Exception {
2208               return AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), table);
2209             }
2210           });
2211         } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
2212           final String namespace = request.getNamespaceName().toStringUtf8();
2213           requireNamespacePermission("userPermissions", namespace, Action.ADMIN);
2214           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2215             @Override
2216             public List<UserPermission> run() throws Exception {
2217               return AccessControlLists.getUserNamespacePermissions(regionEnv.getConfiguration(),
2218                 namespace);
2219             }
2220           });
2221         } else {
2222           requirePermission("userPermissions", Action.ADMIN);
2223           perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2224             @Override
2225             public List<UserPermission> run() throws Exception {
2226               return AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), null);
2227             }
2228           });
2229           // Adding superusers explicitly to the result set as AccessControlLists do not store them.
2230           // Also using acl as table name to be inline  with the results of global admin and will
2231           // help in avoiding any leakage of information about being superusers.
2232           for (String user: Superusers.getSuperUsers()) {
2233             perms.add(new UserPermission(user.getBytes(), AccessControlLists.ACL_TABLE_NAME, null,
2234                 Action.values()));
2235           }
2236         }
2237         response = ResponseConverter.buildGetUserPermissionsResponse(perms);
2238       } else {
2239         throw new CoprocessorException(AccessController.class, "This method "
2240             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2241       }
2242     } catch (IOException ioe) {
2243       // pass exception back up
2244       ResponseConverter.setControllerException(controller, ioe);
2245     }
2246     done.run(response);
2247   }
2248 
2249   @Override
2250   public void checkPermissions(RpcController controller,
2251                                AccessControlProtos.CheckPermissionsRequest request,
2252                                RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
2253     Permission[] permissions = new Permission[request.getPermissionCount()];
2254     for (int i=0; i < request.getPermissionCount(); i++) {
2255       permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
2256     }
2257     AccessControlProtos.CheckPermissionsResponse response = null;
2258     try {
2259       User user = getActiveUser();
2260       TableName tableName = regionEnv.getRegion().getTableDesc().getTableName();
2261       for (Permission permission : permissions) {
2262         if (permission instanceof TablePermission) {
2263           // Check table permissions
2264 
2265           TablePermission tperm = (TablePermission) permission;
2266           for (Action action : permission.getActions()) {
2267             if (!tperm.getTableName().equals(tableName)) {
2268               throw new CoprocessorException(AccessController.class, String.format("This method "
2269                   + "can only execute at the table specified in TablePermission. " +
2270                   "Table of the region:%s , requested table:%s", tableName,
2271                   tperm.getTableName()));
2272             }
2273 
2274             Map<byte[], Set<byte[]>> familyMap =
2275                 new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
2276             if (tperm.getFamily() != null) {
2277               if (tperm.getQualifier() != null) {
2278                 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
2279                 qualifiers.add(tperm.getQualifier());
2280                 familyMap.put(tperm.getFamily(), qualifiers);
2281               } else {
2282                 familyMap.put(tperm.getFamily(), null);
2283               }
2284             }
2285 
2286             AuthResult result = permissionGranted("checkPermissions", user, action, regionEnv,
2287               familyMap);
2288             accessChecker.logResult(result);
2289             if (!result.isAllowed()) {
2290               // Even if passive we need to throw an exception here, we support checking
2291               // effective permissions, so throw unconditionally
2292               throw new AccessDeniedException("Insufficient permissions (table=" + tableName +
2293                 (familyMap.size() > 0 ? ", family: " + result.toFamilyString() : "") +
2294                 ", action=" + action.toString() + ")");
2295             }
2296           }
2297 
2298         } else {
2299           // Check global permissions
2300 
2301           for (Action action : permission.getActions()) {
2302             AuthResult result;
2303             if (getAuthManager().authorize(user, action)) {
2304               result = AuthResult.allow("checkPermissions", "Global action allowed", user,
2305                 action, null, null);
2306             } else {
2307               result = AuthResult.deny("checkPermissions", "Global action denied", user, action,
2308                 null, null);
2309             }
2310             accessChecker.logResult(result);
2311             if (!result.isAllowed()) {
2312               // Even if passive we need to throw an exception here, we support checking
2313               // effective permissions, so throw unconditionally
2314               throw new AccessDeniedException("Insufficient permissions (action=" +
2315                 action.toString() + ")");
2316             }
2317           }
2318         }
2319       }
2320       response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
2321     } catch (IOException ioe) {
2322       ResponseConverter.setControllerException(controller, ioe);
2323     }
2324     done.run(response);
2325   }
2326 
2327   @Override
2328   public Service getService() {
2329     return AccessControlProtos.AccessControlService.newReflectiveService(this);
2330   }
2331 
2332   private Region getRegion(RegionCoprocessorEnvironment e) {
2333     return e.getRegion();
2334   }
2335 
2336   private TableName getTableName(RegionCoprocessorEnvironment e) {
2337     Region region = e.getRegion();
2338     if (region != null) {
2339       return getTableName(region);
2340     }
2341     return null;
2342   }
2343 
2344   private TableName getTableName(Region region) {
2345     HRegionInfo regionInfo = region.getRegionInfo();
2346     if (regionInfo != null) {
2347       return regionInfo.getTable();
2348     }
2349     return null;
2350   }
2351 
2352   @Override
2353   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
2354       throws IOException {
2355     requirePermission("preClose", Action.ADMIN);
2356   }
2357 
2358   private void checkSystemOrSuperUser() throws IOException {
2359     // No need to check if we're not going to throw
2360     if (!authorizationEnabled) {
2361       return;
2362     }
2363     User activeUser = getActiveUser();
2364     if (!Superusers.isSuperUser(activeUser)) {
2365       throw new AccessDeniedException("User '" + (activeUser != null ?
2366         activeUser.getShortName() : "null") + "' is not system or super user.");
2367     }
2368   }
2369 
2370   @Override
2371   public void preStopRegionServer(
2372       ObserverContext<RegionServerCoprocessorEnvironment> env)
2373       throws IOException {
2374     requirePermission("preStopRegionServer", Action.ADMIN);
2375   }
2376 
2377   private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
2378       byte[] qualifier) {
2379     if (family == null) {
2380       return null;
2381     }
2382 
2383     Map<byte[], Collection<byte[]>> familyMap = new TreeMap<byte[], Collection<byte[]>>(Bytes.BYTES_COMPARATOR);
2384     familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
2385     return familyMap;
2386   }
2387 
2388   @Override
2389   public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2390        List<TableName> tableNamesList, List<HTableDescriptor> descriptors,
2391        String regex) throws IOException {
2392     // We are delegating the authorization check to postGetTableDescriptors as we don't have
2393     // any concrete set of table names when a regex is present or the full list is requested.
2394     if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) {
2395       // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the
2396       // request can be granted.
2397       MasterServices masterServices = ctx.getEnvironment().getMasterServices();
2398       for (TableName tableName: tableNamesList) {
2399         // Skip checks for a table that does not exist
2400         if (masterServices.getTableDescriptors().get(tableName) == null) {
2401           continue;
2402         }
2403         requirePermission("getTableDescriptors", tableName, null, null,
2404             Action.ADMIN, Action.CREATE);
2405       }
2406     }
2407   }
2408 
2409   @Override
2410   public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2411       List<TableName> tableNamesList, List<HTableDescriptor> descriptors,
2412       String regex) throws IOException {
2413     // Skipping as checks in this case are already done by preGetTableDescriptors.
2414     if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) {
2415       return;
2416     }
2417 
2418     // Retains only those which passes authorization checks, as the checks weren't done as part
2419     // of preGetTableDescriptors.
2420     Iterator<HTableDescriptor> itr = descriptors.iterator();
2421     while (itr.hasNext()) {
2422       HTableDescriptor htd = itr.next();
2423       try {
2424         requirePermission("getTableDescriptors", htd.getTableName(), null, null,
2425             Action.ADMIN, Action.CREATE);
2426       } catch (AccessDeniedException e) {
2427         itr.remove();
2428       }
2429     }
2430   }
2431 
2432   @Override
2433   public void postGetTableNames(ObserverContext<MasterCoprocessorEnvironment> ctx,
2434       List<HTableDescriptor> descriptors, String regex) throws IOException {
2435     // Retains only those which passes authorization checks.
2436     Iterator<HTableDescriptor> itr = descriptors.iterator();
2437     while (itr.hasNext()) {
2438       HTableDescriptor htd = itr.next();
2439       try {
2440         requireAccess("getTableNames", htd.getTableName(), Action.values());
2441       } catch (AccessDeniedException e) {
2442         itr.remove();
2443       }
2444     }
2445   }
2446 
2447   @Override
2448   public void preDispatchMerge(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2449       HRegionInfo regionA, HRegionInfo regionB) throws IOException {
2450     requirePermission("mergeRegions", regionA.getTable(), null, null,
2451       Action.ADMIN);
2452   }
2453 
2454   @Override
2455   public void preMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx, Region regionA,
2456       Region regionB) throws IOException {
2457     requirePermission("mergeRegions", regionA.getTableDesc().getTableName(), null, null,
2458       Action.ADMIN);
2459   }
2460 
2461   @Override
2462   public void postMerge(ObserverContext<RegionServerCoprocessorEnvironment> c, Region regionA,
2463       Region regionB, Region mergedRegion) throws IOException { }
2464 
2465   @Override
2466   public void preMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2467       Region regionA, Region regionB, List<Mutation> metaEntries) throws IOException { }
2468 
2469   @Override
2470   public void postMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2471       Region regionA, Region regionB, Region mergedRegion) throws IOException { }
2472 
2473   @Override
2474   public void preRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2475       Region regionA, Region regionB) throws IOException { }
2476 
2477   @Override
2478   public void postRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2479       Region regionA, Region regionB) throws IOException { }
2480 
2481   @Override
2482   public void preRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2483       throws IOException {
2484     requirePermission("preRollLogWriterRequest", Permission.Action.ADMIN);
2485   }
2486 
2487   @Override
2488   public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2489       throws IOException { }
2490 
2491   @Override
2492   public ReplicationEndpoint postCreateReplicationEndPoint(
2493       ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) {
2494     return endpoint;
2495   }
2496 
2497   @Override
2498   public void preReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2499       List<WALEntry> entries, CellScanner cells) throws IOException {
2500     requirePermission("replicateLogEntries", Action.WRITE);
2501   }
2502 
2503   @Override
2504   public void postReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2505       List<WALEntry> entries, CellScanner cells) throws IOException {
2506   }
2507 
2508   @Override
2509   public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2510       final String userName, final Quotas quotas) throws IOException {
2511     requirePermission("setUserQuota", Action.ADMIN);
2512   }
2513 
2514   @Override
2515   public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2516       final String userName, final TableName tableName, final Quotas quotas) throws IOException {
2517     requirePermission("setUserTableQuota", tableName, null, null, Action.ADMIN);
2518   }
2519 
2520   @Override
2521   public void preSetUserQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2522       final String userName, final String namespace, final Quotas quotas) throws IOException {
2523     requirePermission("setUserNamespaceQuota", Action.ADMIN);
2524   }
2525 
2526   @Override
2527   public void preSetTableQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2528       final TableName tableName, final Quotas quotas) throws IOException {
2529     requirePermission("setTableQuota", tableName, null, null, Action.ADMIN);
2530   }
2531 
2532   @Override
2533   public void preSetNamespaceQuota(final ObserverContext<MasterCoprocessorEnvironment> ctx,
2534       final String namespace, final Quotas quotas) throws IOException {
2535     requirePermission("setNamespaceQuota", Action.ADMIN);
2536   }
2537 }