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