View Javadoc

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