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.util.Collection;
20  import java.util.Collections;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.TreeMap;
26  import java.util.TreeSet;
27  
28  import com.google.protobuf.Message;
29  import com.google.protobuf.RpcCallback;
30  import com.google.protobuf.RpcController;
31  import com.google.protobuf.Service;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.hbase.Cell;
37  import org.apache.hadoop.hbase.CellUtil;
38  import org.apache.hadoop.hbase.CoprocessorEnvironment;
39  import org.apache.hadoop.hbase.DoNotRetryIOException;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.KeyValue.Type;
42  import org.apache.hadoop.hbase.TableName;
43  import org.apache.hadoop.hbase.HColumnDescriptor;
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.KeyValueUtil;
48  import org.apache.hadoop.hbase.NamespaceDescriptor;
49  import org.apache.hadoop.hbase.ServerName;
50  import org.apache.hadoop.hbase.TableNotDisabledException;
51  import org.apache.hadoop.hbase.TableNotFoundException;
52  import org.apache.hadoop.hbase.Tag;
53  import org.apache.hadoop.hbase.client.Append;
54  import org.apache.hadoop.hbase.client.Delete;
55  import org.apache.hadoop.hbase.client.Get;
56  import org.apache.hadoop.hbase.client.Increment;
57  import org.apache.hadoop.hbase.client.Mutation;
58  import org.apache.hadoop.hbase.client.Put;
59  import org.apache.hadoop.hbase.client.Query;
60  import org.apache.hadoop.hbase.client.Result;
61  import org.apache.hadoop.hbase.client.Scan;
62  import org.apache.hadoop.hbase.client.Durability;
63  import org.apache.hadoop.hbase.coprocessor.*;
64  import org.apache.hadoop.hbase.filter.CompareFilter;
65  import org.apache.hadoop.hbase.filter.Filter;
66  import org.apache.hadoop.hbase.filter.FilterList;
67  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
68  import org.apache.hadoop.hbase.io.hfile.HFile;
69  import org.apache.hadoop.hbase.ipc.RequestContext;
70  import org.apache.hadoop.hbase.master.MasterServices;
71  import org.apache.hadoop.hbase.master.RegionPlan;
72  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
73  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
74  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
75  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
76  import org.apache.hadoop.hbase.regionserver.HRegion;
77  import org.apache.hadoop.hbase.regionserver.InternalScanner;
78  import org.apache.hadoop.hbase.regionserver.RegionScanner;
79  import org.apache.hadoop.hbase.regionserver.Store;
80  import org.apache.hadoop.hbase.regionserver.ScanType;
81  import org.apache.hadoop.hbase.regionserver.StoreFile;
82  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
83  import org.apache.hadoop.hbase.security.AccessDeniedException;
84  import org.apache.hadoop.hbase.security.User;
85  import org.apache.hadoop.hbase.security.UserProvider;
86  import org.apache.hadoop.hbase.security.access.Permission.Action;
87  import org.apache.hadoop.hbase.util.Bytes;
88  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
89  import org.apache.hadoop.hbase.util.Pair;
90  
91  import com.google.common.collect.ArrayListMultimap;
92  import com.google.common.collect.ImmutableSet;
93  import com.google.common.collect.ListMultimap;
94  import com.google.common.collect.Lists;
95  import com.google.common.collect.MapMaker;
96  import com.google.common.collect.Sets;
97  
98  import static org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
99  
100 /**
101  * Provides basic authorization checks for data access and administrative
102  * operations.
103  *
104  * <p>
105  * {@code AccessController} performs authorization checks for HBase operations
106  * based on:
107  * <ul>
108  *   <li>the identity of the user performing the operation</li>
109  *   <li>the scope over which the operation is performed, in increasing
110  *   specificity: global, table, column family, or qualifier</li>
111  *   <li>the type of action being performed (as mapped to
112  *   {@link Permission.Action} values)</li>
113  * </ul>
114  * If the authorization check fails, an {@link AccessDeniedException}
115  * will be thrown for the operation.
116  * </p>
117  *
118  * <p>
119  * To perform authorization checks, {@code AccessController} relies on the
120  * {@link org.apache.hadoop.hbase.ipc.RpcServerEngine} being loaded to provide
121  * the user identities for remote requests.
122  * </p>
123  *
124  * <p>
125  * The access control lists used for authorization can be manipulated via the
126  * exposed {@link AccessControlService} Interface implementation, and the associated
127  * {@code grant}, {@code revoke}, and {@code user_permission} HBase shell
128  * commands.
129  * </p>
130  */
131 public class AccessController extends BaseRegionObserver
132     implements MasterObserver, RegionServerObserver,
133       AccessControlService.Interface, CoprocessorService, EndpointObserver {
134 
135   public static final Log LOG = LogFactory.getLog(AccessController.class);
136 
137   private static final Log AUDITLOG =
138     LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
139 
140   static final String EXEC_PERMISSION_CHECKS_KEY = "hbase.security.exec.permission.checks";
141   static final boolean DEFAULT_EXEC_PERMISSION_CHECKS = false;
142 
143   TableAuthManager authManager = null;
144 
145   // flags if we are running on a region of the _acl_ table
146   boolean aclRegion = false;
147 
148   // defined only for Endpoint implementation, so it can have way to
149   // access region services.
150   private RegionCoprocessorEnvironment regionEnv;
151 
152   /** Mapping of scanner instances to the user who created them */
153   private Map<InternalScanner,String> scannerOwners =
154       new MapMaker().weakKeys().makeMap();
155 
156   private UserProvider userProvider;
157 
158   // flags if we are able to support cell ACLs
159   boolean canPersistCellACLs;
160 
161   // flags if we should check EXEC permissions
162   boolean shouldCheckExecPermissions;
163 
164   private volatile boolean initialized = false;
165 
166   public HRegion getRegion() {
167     return regionEnv != null ? regionEnv.getRegion() : null;
168   }
169 
170   public TableAuthManager getAuthManager() {
171     return authManager;
172   }
173 
174   void initialize(RegionCoprocessorEnvironment e) throws IOException {
175     final HRegion region = e.getRegion();
176     Map<byte[], ListMultimap<String,TablePermission>> tables =
177         AccessControlLists.loadAll(region);
178     // For each table, write out the table's permissions to the respective
179     // znode for that table.
180     for (Map.Entry<byte[], ListMultimap<String,TablePermission>> t:
181       tables.entrySet()) {
182       byte[] entry = t.getKey();
183       ListMultimap<String,TablePermission> perms = t.getValue();
184       byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, e.getConfiguration());
185       this.authManager.getZKPermissionWatcher().writeToZookeeper(entry, serialized);
186     }
187     shouldCheckExecPermissions = e.getConfiguration().getBoolean(EXEC_PERMISSION_CHECKS_KEY,
188       DEFAULT_EXEC_PERMISSION_CHECKS);
189     initialized = true;
190   }
191 
192   /**
193    * Writes all table ACLs for the tables in the given Map up into ZooKeeper
194    * znodes.  This is called to synchronize ACL changes following {@code _acl_}
195    * table updates.
196    */
197   void updateACL(RegionCoprocessorEnvironment e,
198       final Map<byte[], List<Cell>> familyMap) {
199     Set<byte[]> entries =
200         new TreeSet<byte[]>(Bytes.BYTES_RAWCOMPARATOR);
201     for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) {
202       List<Cell> cells = f.getValue();
203       for (Cell cell: cells) {
204         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
205         if (Bytes.equals(kv.getBuffer(), kv.getFamilyOffset(),
206             kv.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
207             AccessControlLists.ACL_LIST_FAMILY.length)) {
208           entries.add(kv.getRow());
209         }
210       }
211     }
212     ZKPermissionWatcher zkw = this.authManager.getZKPermissionWatcher();
213     Configuration conf = regionEnv.getConfiguration();
214     for (byte[] entry: entries) {
215       try {
216         ListMultimap<String,TablePermission> perms =
217           AccessControlLists.getPermissions(conf, entry);
218         byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
219         zkw.writeToZookeeper(entry, serialized);
220       } catch (IOException ex) {
221         LOG.error("Failed updating permissions mirror for '" + Bytes.toString(entry) + "'",
222             ex);
223       }
224     }
225   }
226 
227   /**
228    * Check the current user for authorization to perform a specific action
229    * against the given set of row data.
230    *
231    * <p>Note: Ordering of the authorization checks
232    * has been carefully optimized to short-circuit the most common requests
233    * and minimize the amount of processing required.</p>
234    *
235    * @param permRequest the action being requested
236    * @param e the coprocessor environment
237    * @param families the map of column families to qualifiers present in
238    * the request
239    * @return an authorization result
240    */
241   AuthResult permissionGranted(String request, User user, Permission.Action permRequest,
242       RegionCoprocessorEnvironment e,
243       Map<byte [], ? extends Collection<?>> families) {
244     HRegionInfo hri = e.getRegion().getRegionInfo();
245     TableName tableName = hri.getTable();
246 
247     // 1. All users need read access to hbase:meta table.
248     // this is a very common operation, so deal with it quickly.
249     if (hri.isMetaRegion()) {
250       if (permRequest == Permission.Action.READ) {
251         return AuthResult.allow(request, "All users allowed", user,
252           permRequest, tableName, families);
253       }
254     }
255 
256     if (user == null) {
257       return AuthResult.deny(request, "No user associated with request!", null,
258         permRequest, tableName, families);
259     }
260 
261     // Users with CREATE/ADMIN rights need to modify hbase:meta and _acl_ table
262     // e.g. When a new table is created a new entry in hbase:meta is added,
263     // so the user need to be allowed to write on it.
264     // e.g. When a table is removed an entry is removed from hbase:meta and _acl_
265     // and the user need to be allowed to write on both tables.
266     if (permRequest == Permission.Action.WRITE &&
267        (hri.isMetaRegion() ||
268         Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) &&
269        (authManager.authorize(user, Permission.Action.CREATE) ||
270         authManager.authorize(user, Permission.Action.ADMIN)))
271     {
272        return AuthResult.allow(request, "Table permission granted", user,
273         permRequest, tableName, families);
274     }
275 
276     // 2. check for the table-level, if successful we can short-circuit
277     if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
278       return AuthResult.allow(request, "Table permission granted", user,
279         permRequest, tableName, families);
280     }
281 
282     // 3. check permissions against the requested families
283     if (families != null && families.size() > 0) {
284       // all families must pass
285       for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
286         // a) check for family level access
287         if (authManager.authorize(user, tableName, family.getKey(),
288             permRequest)) {
289           continue;  // family-level permission overrides per-qualifier
290         }
291 
292         // b) qualifier level access can still succeed
293         if ((family.getValue() != null) && (family.getValue().size() > 0)) {
294           if (family.getValue() instanceof Set) {
295             // for each qualifier of the family
296             Set<byte[]> familySet = (Set<byte[]>)family.getValue();
297             for (byte[] qualifier : familySet) {
298               if (!authManager.authorize(user, tableName, family.getKey(),
299                                          qualifier, permRequest)) {
300                 return AuthResult.deny(request, "Failed qualifier check", user,
301                     permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
302               }
303             }
304           } else if (family.getValue() instanceof List) { // List<KeyValue>
305             List<KeyValue> kvList = (List<KeyValue>)family.getValue();
306             for (KeyValue kv : kvList) {
307               if (!authManager.authorize(user, tableName, family.getKey(),
308                       kv.getQualifier(), permRequest)) {
309                 return AuthResult.deny(request, "Failed qualifier check", user,
310                     permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
311               }
312             }
313           }
314         } else {
315           // no qualifiers and family-level check already failed
316           return AuthResult.deny(request, "Failed family check", user, permRequest,
317               tableName, makeFamilyMap(family.getKey(), null));
318         }
319       }
320 
321       // all family checks passed
322       return AuthResult.allow(request, "All family checks passed", user, permRequest,
323           tableName, families);
324     }
325 
326     // 4. no families to check and table level access failed
327     return AuthResult.deny(request, "No families to check and table permission failed",
328         user, permRequest, tableName, families);
329   }
330 
331   private void logResult(AuthResult result) {
332     if (AUDITLOG.isTraceEnabled()) {
333       RequestContext ctx = RequestContext.get();
334       InetAddress remoteAddr = null;
335       if (ctx != null) {
336         remoteAddr = ctx.getRemoteAddress();
337       }
338       AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
339           " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
340           "; reason: " + result.getReason() +
341           "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
342           "; request: " + result.getRequest() +
343           "; context: " + result.toContextString());
344     }
345   }
346 
347   /**
348    * Returns the active user to which authorization checks should be applied.
349    * If we are in the context of an RPC call, the remote user is used,
350    * otherwise the currently logged in user is used.
351    */
352   private User getActiveUser() throws IOException {
353     User user = RequestContext.getRequestUser();
354     if (!RequestContext.isInRequestContext()) {
355       // for non-rpc handling, fallback to system user
356       user = userProvider.getCurrent();
357     }
358     return user;
359   }
360 
361   /**
362    * Authorizes that the current user has any of the given permissions for the
363    * given table, column family and column qualifier.
364    * @param tableName Table requested
365    * @param family Column family requested
366    * @param qualifier Column qualifier requested
367    * @throws IOException if obtaining the current user fails
368    * @throws AccessDeniedException if user has no authorization
369    */
370   private void requirePermission(String request, TableName tableName, byte[] family, byte[] qualifier,
371       Action... permissions) throws IOException {
372     User user = getActiveUser();
373     AuthResult result = null;
374 
375     for (Action permission : permissions) {
376       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
377         result = AuthResult.allow(request, "Table permission granted", user,
378                                   permission, tableName, family, qualifier);
379         break;
380       } else {
381         // rest of the world
382         result = AuthResult.deny(request, "Insufficient permissions", user,
383                                  permission, tableName, family, qualifier);
384       }
385     }
386     logResult(result);
387     if (!result.isAllowed()) {
388       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
389     }
390   }
391 
392   /**
393    * Authorizes that the current user has global privileges for the given action.
394    * @param perm The action being requested
395    * @throws IOException if obtaining the current user fails
396    * @throws AccessDeniedException if authorization is denied
397    */
398   private void requirePermission(String request, Permission.Action perm) throws IOException {
399     requireGlobalPermission(request, perm, null, null);
400   }
401 
402   /**
403    * Authorizes that the current user has permission to perform the given
404    * action on the set of table column families.
405    * @param perm Action that is required
406    * @param env The current coprocessor environment
407    * @param families The map of column families-qualifiers.
408    * @throws AccessDeniedException if the authorization check failed
409    */
410   private void requirePermission(String request, Permission.Action perm,
411         RegionCoprocessorEnvironment env,
412         Map<byte[], ? extends Collection<?>> families)
413       throws IOException {
414     User user = getActiveUser();
415     AuthResult result = permissionGranted(request, user, perm, env, families);
416     logResult(result);
417 
418     if (!result.isAllowed()) {
419       throw new AccessDeniedException("Insufficient permissions (table=" +
420         env.getRegion().getTableDesc().getTableName()+
421         ((families != null && families.size() > 0) ? ", family: " +
422         result.toFamilyString() : "") + ", action=" +
423         perm.toString() + ")");
424     }
425   }
426 
427   /**
428    * Checks that the user has the given global permission. The generated
429    * audit log message will contain context information for the operation
430    * being authorized, based on the given parameters.
431    * @param perm Action being requested
432    * @param tableName Affected table name.
433    * @param familyMap Affected column families.
434    */
435   private void requireGlobalPermission(String request, Permission.Action perm, TableName tableName,
436       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
437     User user = getActiveUser();
438     if (authManager.authorize(user, perm)) {
439       logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap));
440     } else {
441       logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap));
442       throw new AccessDeniedException("Insufficient permissions for user '" +
443           (user != null ? user.getShortName() : "null") +"' (global, action=" +
444           perm.toString() + ")");
445     }
446   }
447 
448   /**
449    * Checks that the user has the given global permission. The generated
450    * audit log message will contain context information for the operation
451    * being authorized, based on the given parameters.
452    * @param perm Action being requested
453    * @param namespace
454    */
455   private void requireGlobalPermission(String request, Permission.Action perm,
456                                        String namespace) throws IOException {
457     User user = getActiveUser();
458     if (authManager.authorize(user, perm)) {
459       logResult(AuthResult.allow(request, "Global check allowed", user, perm, namespace));
460     } else {
461       logResult(AuthResult.deny(request, "Global check failed", user, perm, namespace));
462       throw new AccessDeniedException("Insufficient permissions for user '" +
463           (user != null ? user.getShortName() : "null") +"' (global, action=" +
464           perm.toString() + ")");
465     }
466   }
467 
468   private void requireCoveringPermission(String request, RegionCoprocessorEnvironment e,
469       byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long timestamp,
470       boolean allVersions, Action...actions) throws IOException {
471     User user = getActiveUser();
472 
473     // First check table or CF level permissions, if they grant access we can
474     // early out before needing to enumerate over per KV perms.
475 
476     List<Action> cellCheckActions = Lists.newArrayList();
477     // TODO: permissionGranted should support checking multiple actions or
478     // we should convert actions into a bitmap and pass that around. See
479     // HBASE-7123.
480     AuthResult results[] = new AuthResult[actions.length];
481     for (int i = 0; i < actions.length; i++) {
482       results[i] = permissionGranted(request, user, actions[i], e, familyMap);
483       if (!results[i].isAllowed()) {
484         if (LOG.isTraceEnabled()) {
485           LOG.trace("Got " + results[i] + ", added to cellCheckActions");
486         }
487         cellCheckActions.add(actions[i]);
488       }
489     }
490     // If all permissions checks passed, we can early out
491     if (cellCheckActions.isEmpty()) {
492       if (LOG.isTraceEnabled()) {
493         LOG.trace("All permissions checks passed, we can early out");
494       }
495       for (int i = 0; i < results.length; i++) {
496         logResult(results[i]);
497       }
498       return;
499     }
500 
501     // Table or CF permissions do not allow, enumerate the covered KVs. We
502     // can stop at the first which does not grant access.
503     int cellsChecked = 0;
504     if (canPersistCellACLs) {
505       Get get = new Get(row);
506       if (timestamp != HConstants.LATEST_TIMESTAMP) get.setTimeStamp(timestamp);
507       if (allVersions) {
508         get.setMaxVersions();
509       } else {
510         get.setMaxVersions(1);
511       }
512       for (Map.Entry<byte[], ? extends Collection<?>> entry: familyMap.entrySet()) {
513         byte[] col = entry.getKey();
514         // TODO: HBASE-7114 could possibly unify the collection type in family
515         // maps so we would not need to do this
516         if (entry.getValue() instanceof Set) {
517           Set<byte[]> set = (Set<byte[]>)entry.getValue();
518           if (set == null || set.isEmpty()) {
519             get.addFamily(col);
520           } else {
521             for (byte[] qual: set) {
522               get.addColumn(col, qual);
523             }
524           }
525         } else if (entry.getValue() instanceof List) {
526           List<Cell> list = (List<Cell>)entry.getValue();
527           if (list == null || list.isEmpty()) {
528             get.addFamily(col);
529           } else {
530             // In case of family delete, a Cell will be added into the list with Qualifier as null.
531             for (Cell cell : list) {
532               if (cell.getQualifierLength() == 0
533                   && (cell.getTypeByte() == Type.DeleteFamily.getCode() 
534                   || cell.getTypeByte() == Type.DeleteFamilyVersion.getCode())) {
535                 get.addFamily(col);
536               } else {
537                 get.addColumn(col, CellUtil.cloneQualifier(cell));
538               }
539             }
540           }
541         } else {
542           throw new RuntimeException("Unhandled collection type " +
543             entry.getValue().getClass().getName());
544         }
545       }
546       if (LOG.isTraceEnabled()) {
547         LOG.trace("Scanning for cells with " + get);
548       }
549       RegionScanner scanner = getRegion(e).getScanner(new Scan(get));
550       List<Cell> cells = Lists.newArrayList();
551       try {
552         boolean more = false;
553         do {
554           cells.clear();
555           // scan with limit as 1 to hold down memory use on wide rows
556           more = scanner.next(cells, 1);
557           for (Cell cell: cells) {
558             if (LOG.isTraceEnabled()) {
559               LOG.trace("Found cell " + cell);
560             }
561             for (Action action: cellCheckActions) {
562               // Are there permissions for this user for the cell?
563               if (!authManager.authorize(user, getTableName(e), cell, false, action)) {
564                 AuthResult authResult = AuthResult.deny(request, "Insufficient permissions",
565                   user, action, getTableName(e), CellUtil.cloneFamily(cell),
566                   CellUtil.cloneQualifier(cell));
567                 logResult(authResult);
568                 throw new AccessDeniedException("Insufficient permissions " +
569                   authResult.toContextString());
570               }
571             }
572             cellsChecked++;
573           }
574         } while (more);
575       } catch (AccessDeniedException ex) {
576         throw ex;
577       } catch (IOException ex) {
578         LOG.error("Exception while getting cells to calculate covering permission", ex);
579       } finally {
580         scanner.close();
581       }
582     }
583 
584     // If there were no cells to check, throw the ADE
585     if (cellsChecked < 1) {
586       if (LOG.isTraceEnabled()) {
587         LOG.trace("No cells found with scan");
588       }
589       AuthResult authResult = AuthResult.deny(request, "Insufficient permissions",
590         user, cellCheckActions.get(0), getTableName(e), familyMap);
591       logResult(authResult);
592       throw new AccessDeniedException("Insufficient permissions " +
593         authResult.toContextString());
594     }
595 
596     // Log that authentication succeeded. We need to trade off logging maybe
597     // thousands of fine grained decisions with providing detail.
598     for (byte[] family: familyMap.keySet()) {
599       for (Action action: actions) {
600         logResult(AuthResult.allow(request, "Permission granted", user, action,
601           getTableName(e), family, null));
602       }
603     }
604   }
605 
606   private void addCellPermissions(final byte[] perms, Map<byte[], List<Cell>> familyMap) {
607     // Iterate over the entries in the familyMap, replacing the cells therein
608     // with new cells including the ACL data
609     for (Map.Entry<byte[], List<Cell>> e: familyMap.entrySet()) {
610       List<Cell> newCells = Lists.newArrayList();
611       for (Cell cell: e.getValue()) {
612         List<Tag> tags = Lists.newArrayList(new Tag(AccessControlLists.ACL_TAG_TYPE, perms));
613         byte[] tagBytes = CellUtil.getTagArray(cell);
614         Iterator<Tag> tagIterator = CellUtil.tagsIterator(tagBytes, 0, tagBytes.length);
615         while (tagIterator.hasNext()) {
616           tags.add(tagIterator.next());
617         }
618         // Ensure KeyValue so we can do a scatter gather copy. This is only a win if the
619         // incoming cell type is actually KeyValue.
620         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
621         byte[] bytes = kv.getBuffer();
622         newCells.add(
623           new KeyValue(bytes, kv.getRowOffset(), kv.getRowLength(),
624             bytes, kv.getFamilyOffset(), kv.getFamilyLength(),
625             bytes, kv.getQualifierOffset(), kv.getQualifierLength(),
626             kv.getTimestamp(), KeyValue.Type.codeToType(kv.getTypeByte()),
627             bytes, kv.getValueOffset(), kv.getValueLength(),
628             tags));
629       }
630       // This is supposed to be safe, won't CME
631       e.setValue(newCells);
632     }
633   }
634 
635   private void internalPreRead(final ObserverContext<RegionCoprocessorEnvironment> c,
636       final Query query) throws IOException {
637     TableName tableName = getTableName(c.getEnvironment());
638     User activeUser = getActiveUser();
639     Filter filter = query.getFilter();
640     boolean cellFirstStrategy = query.getACLStrategy();
641     // Don't wrap an AccessControlFilter
642     if (filter != null && filter instanceof AccessControlFilter) {
643       return;
644     }
645     Filter newFilter = (filter != null)
646       ? new FilterList(FilterList.Operator.MUST_PASS_ALL,
647           Lists.newArrayList(
648             new AccessControlFilter(authManager, activeUser, tableName, cellFirstStrategy),
649             filter))
650       : new AccessControlFilter(authManager, activeUser, tableName, cellFirstStrategy);
651     query.setFilter(newFilter);
652   }
653 
654   /* ---- MasterObserver implementation ---- */
655 
656   public void start(CoprocessorEnvironment env) throws IOException {
657     canPersistCellACLs = HFile.getFormatVersion(env.getConfiguration()) >=
658       HFile.MIN_FORMAT_VERSION_WITH_TAGS;
659     if (!canPersistCellACLs) {
660       LOG.info("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
661           + " is required to persist cell ACLs. Consider setting " + HFile.FORMAT_VERSION_KEY
662           + " accordingly.");
663     }
664     ZooKeeperWatcher zk = null;
665     if (env instanceof MasterCoprocessorEnvironment) {
666       // if running on HMaster
667       MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
668       zk = mEnv.getMasterServices().getZooKeeper();      
669     } else if (env instanceof RegionServerCoprocessorEnvironment) {      
670       RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
671       zk = rsEnv.getRegionServerServices().getZooKeeper();      
672     } else if (env instanceof RegionCoprocessorEnvironment) {
673       // if running at region
674       regionEnv = (RegionCoprocessorEnvironment) env;
675       zk = regionEnv.getRegionServerServices().getZooKeeper();
676     }
677 
678     // set the user-provider.
679     this.userProvider = UserProvider.instantiate(env.getConfiguration());
680 
681     // If zk is null or IOException while obtaining auth manager,
682     // throw RuntimeException so that the coprocessor is unloaded.
683     if (zk != null) {
684       try {
685         this.authManager = TableAuthManager.get(zk, env.getConfiguration());
686       } catch (IOException ioe) {
687         throw new RuntimeException("Error obtaining TableAuthManager", ioe);
688       }
689     } else {
690       throw new RuntimeException("Error obtaining TableAuthManager, zk found null.");
691     }
692   }
693 
694   public void stop(CoprocessorEnvironment env) {
695 
696   }
697 
698   @Override
699   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
700       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
701     Set<byte[]> families = desc.getFamiliesKeys();
702     Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
703     for (byte[] family: families) {
704       familyMap.put(family, null);
705     }
706     requireGlobalPermission("createTable", Permission.Action.CREATE, desc.getTableName(), familyMap);
707   }
708 
709   @Override
710   public void preCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
711       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {}
712 
713   @Override
714   public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
715       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
716     if (!AccessControlLists.isAclTable(desc)) {
717       String owner = desc.getOwnerString();
718       // default the table owner to current user, if not specified.
719       if (owner == null) owner = getActiveUser().getShortName();
720       UserPermission userperm = new UserPermission(Bytes.toBytes(owner), desc.getTableName(), null,
721           Action.values());
722       AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
723     }
724   }
725 
726   @Override
727   public void postCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
728       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {}
729 
730   @Override
731   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
732       throws IOException {
733     requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
734   }
735 
736   @Override
737   public void preDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
738       TableName tableName) throws IOException {}
739 
740   @Override
741   public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
742       TableName tableName) throws IOException {
743     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName);
744   }
745 
746   @Override
747   public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
748       TableName tableName) throws IOException {}
749 
750   @Override
751   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
752       HTableDescriptor htd) throws IOException {
753     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
754   }
755 
756   @Override
757   public void preModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
758       TableName tableName, HTableDescriptor htd) throws IOException {}
759 
760   @Override
761   public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
762       TableName tableName, HTableDescriptor htd) throws IOException {
763     String owner = htd.getOwnerString();
764     // default the table owner to current user, if not specified.
765     if (owner == null) owner = getActiveUser().getShortName();
766     UserPermission userperm = new UserPermission(Bytes.toBytes(owner), htd.getTableName(), null,
767         Action.values());
768     AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
769   }
770 
771   @Override
772   public void postModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
773       TableName tableName, HTableDescriptor htd) throws IOException {}
774 
775 
776   @Override
777   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
778       HColumnDescriptor column) throws IOException {
779     requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
780   }
781 
782   @Override
783   public void preAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
784       TableName tableName, HColumnDescriptor column) throws IOException {}
785 
786   @Override
787   public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> c,
788       TableName tableName, HColumnDescriptor column) throws IOException {}
789 
790   @Override
791   public void postAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
792       TableName tableName, HColumnDescriptor column) throws IOException {}
793 
794   @Override
795   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
796       HColumnDescriptor descriptor) throws IOException {
797     requirePermission("modifyColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
798   }
799 
800   @Override
801   public void preModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
802       TableName tableName, HColumnDescriptor descriptor) throws IOException {}
803 
804   @Override
805   public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c,
806       TableName tableName, HColumnDescriptor descriptor) throws IOException {}
807 
808   @Override
809   public void postModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
810       TableName tableName, HColumnDescriptor descriptor) throws IOException {}
811 
812   @Override
813   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
814       byte[] col) throws IOException {
815     requirePermission("deleteColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
816   }
817 
818   @Override
819   public void preDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
820       TableName tableName, byte[] col) throws IOException {}
821 
822   @Override
823   public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
824       TableName tableName, byte[] col) throws IOException {
825     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(),
826                                               tableName, col);
827   }
828 
829   @Override
830   public void postDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> c,
831       TableName tableName, byte[] col) throws IOException {}
832 
833   @Override
834   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
835       throws IOException {
836     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
837   }
838 
839   @Override
840   public void preEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
841       TableName tableName) throws IOException {}
842 
843   @Override
844   public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> c,
845       TableName tableName) throws IOException {}
846 
847   @Override
848   public void postEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
849       TableName tableName) throws IOException {}
850 
851   @Override
852   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
853       throws IOException {
854     if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
855       throw new AccessDeniedException("Not allowed to disable "
856           + AccessControlLists.ACL_TABLE_NAME + " table.");
857     }
858     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
859   }
860 
861   @Override
862   public void preDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
863       TableName tableName) throws IOException {}
864 
865   @Override
866   public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> c,
867       TableName tableName) throws IOException {}
868 
869   @Override
870   public void postDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
871       TableName tableName) throws IOException {}
872 
873   @Override
874   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
875       ServerName srcServer, ServerName destServer) throws IOException {
876     requirePermission("move", region.getTable(), null, null, Action.ADMIN);
877   }
878 
879   @Override
880   public void postMove(ObserverContext<MasterCoprocessorEnvironment> c,
881       HRegionInfo region, ServerName srcServer, ServerName destServer)
882     throws IOException {}
883 
884   @Override
885   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
886       throws IOException {
887     requirePermission("assign", regionInfo.getTable(), null, null, Action.ADMIN);
888   }
889 
890   @Override
891   public void postAssign(ObserverContext<MasterCoprocessorEnvironment> c,
892       HRegionInfo regionInfo) throws IOException {}
893 
894   @Override
895   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
896       boolean force) throws IOException {
897     requirePermission("unassign", regionInfo.getTable(), null, null, Action.ADMIN);
898   }
899 
900   @Override
901   public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> c,
902       HRegionInfo regionInfo, boolean force) throws IOException {}
903 
904   @Override
905   public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
906       HRegionInfo regionInfo) throws IOException {
907     requirePermission("regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
908   }
909 
910   @Override
911   public void postRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
912       HRegionInfo regionInfo) throws IOException {
913   }
914 
915   @Override
916   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
917       throws IOException {
918     requirePermission("balance", Permission.Action.ADMIN);
919   }
920   @Override
921   public void postBalance(ObserverContext<MasterCoprocessorEnvironment> c, List<RegionPlan> plans)
922       throws IOException {}
923 
924   @Override
925   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
926       boolean newValue) throws IOException {
927     requirePermission("balanceSwitch", Permission.Action.ADMIN);
928     return newValue;
929   }
930 
931   @Override
932   public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
933       boolean oldValue, boolean newValue) throws IOException {}
934 
935   @Override
936   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
937       throws IOException {
938     requirePermission("shutdown", Permission.Action.ADMIN);
939   }
940 
941   @Override
942   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
943       throws IOException {
944     requirePermission("stopMaster", Permission.Action.ADMIN);
945   }
946 
947   @Override
948   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
949       throws IOException {
950     // initialize the ACL storage table
951     AccessControlLists.init(ctx.getEnvironment().getMasterServices());
952   }
953 
954   @Override
955   public void preMasterInitialization(
956       ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
957   }
958 
959   @Override
960   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
961       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
962       throws IOException {
963     requirePermission("snapshot", Permission.Action.ADMIN);
964   }
965 
966   @Override
967   public void postSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
968       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
969       throws IOException {
970   }
971 
972   @Override
973   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
974       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
975       throws IOException {
976     requirePermission("clone", Permission.Action.ADMIN);
977   }
978 
979   @Override
980   public void postCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
981       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
982       throws IOException {
983   }
984 
985   @Override
986   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
987       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
988       throws IOException {
989     requirePermission("restore", Permission.Action.ADMIN);
990   }
991 
992   @Override
993   public void postRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
994       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
995       throws IOException {
996   }
997 
998   @Override
999   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1000       final SnapshotDescription snapshot) throws IOException {
1001     requirePermission("deleteSnapshot", Permission.Action.ADMIN);
1002   }
1003 
1004   @Override
1005   public void postDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1006       final SnapshotDescription snapshot) throws IOException {
1007   }
1008 
1009   @Override
1010   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1011       NamespaceDescriptor ns) throws IOException {
1012     requireGlobalPermission("createNamespace", Action.ADMIN, ns.getName());
1013   }
1014 
1015   @Override
1016   public void postCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1017       NamespaceDescriptor ns) throws IOException {
1018   }
1019 
1020   @Override
1021   public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1022       throws IOException {
1023     requireGlobalPermission("deleteNamespace", Action.ADMIN, namespace);
1024   }
1025 
1026   @Override
1027   public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1028                                   String namespace) throws IOException {
1029     AccessControlLists.removeNamespacePermissions(ctx.getEnvironment().getConfiguration(),
1030         namespace);
1031     LOG.info(namespace + "entry deleted in "+AccessControlLists.ACL_TABLE_NAME+" table.");
1032   }
1033 
1034   @Override
1035   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1036       NamespaceDescriptor ns) throws IOException {
1037     requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName());
1038   }
1039 
1040   @Override
1041   public void postModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1042                                   NamespaceDescriptor ns) throws IOException {
1043   }
1044 
1045   /* ---- RegionObserver implementation ---- */
1046 
1047   @Override
1048   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
1049       throws IOException {
1050     RegionCoprocessorEnvironment env = e.getEnvironment();
1051     final HRegion region = env.getRegion();
1052     if (region == null) {
1053       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
1054     } else {
1055       HRegionInfo regionInfo = region.getRegionInfo();
1056       if (regionInfo.getTable().isSystemTable()) {
1057         isSystemOrSuperUser(regionEnv.getConfiguration());
1058       } else {
1059         requirePermission("preOpen", Action.ADMIN);
1060       }
1061     }
1062   }
1063 
1064   @Override
1065   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
1066     RegionCoprocessorEnvironment env = c.getEnvironment();
1067     final HRegion region = env.getRegion();
1068     if (region == null) {
1069       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
1070       return;
1071     }
1072     if (AccessControlLists.isAclRegion(region)) {
1073       aclRegion = true;
1074       // When this region is under recovering state, initialize will be handled by postLogReplay
1075       if (!region.isRecovering()) {
1076         try {
1077           initialize(env);
1078         } catch (IOException ex) {
1079           // if we can't obtain permissions, it's better to fail
1080           // than perform checks incorrectly
1081           throw new RuntimeException("Failed to initialize permissions cache", ex);
1082         }
1083       }
1084     } else {
1085       initialized = true;
1086     }
1087   }
1088 
1089   @Override
1090   public void postLogReplay(ObserverContext<RegionCoprocessorEnvironment> c) {
1091     if (aclRegion) {
1092       try {
1093         initialize(c.getEnvironment());
1094       } catch (IOException ex) {
1095         // if we can't obtain permissions, it's better to fail
1096         // than perform checks incorrectly
1097         throw new RuntimeException("Failed to initialize permissions cache", ex);
1098       }
1099     }
1100   }
1101 
1102   @Override
1103   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1104     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1105   }
1106 
1107   @Override
1108   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1109     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1110   }
1111   
1112   @Override
1113   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
1114       byte[] splitRow) throws IOException {
1115     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1116   }
1117 
1118   @Override
1119   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
1120       final Store store, final InternalScanner scanner, final ScanType scanType)
1121           throws IOException {
1122     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1123     return scanner;
1124   }
1125 
1126   @Override
1127   public void preCompactSelection(final ObserverContext<RegionCoprocessorEnvironment> e,
1128       final Store store, final List<StoreFile> candidates) throws IOException {
1129     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1130   }
1131 
1132   @Override
1133   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
1134       final byte [] row, final byte [] family, final Result result)
1135       throws IOException {
1136     assert family != null;
1137     requireCoveringPermission("getClosestRowBefore", c.getEnvironment(), row,
1138       makeFamilyMap(family, null), HConstants.LATEST_TIMESTAMP, false, Permission.Action.READ);
1139   }
1140 
1141   @Override
1142   public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> c,
1143       final Get get, final List<Cell> result) throws IOException {
1144     internalPreRead(c, get);
1145   }
1146 
1147   @Override
1148   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
1149       final Get get, final boolean exists) throws IOException {
1150     internalPreRead(c, get);
1151     return exists;
1152   }
1153 
1154   @Override
1155   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
1156       final Put put, final WALEdit edit, final Durability durability)
1157       throws IOException {
1158     // Require WRITE permission to the table, CF, or top visible value, if any.
1159     // NOTE: We don't need to check the permissions for any earlier Puts
1160     // because we treat the ACLs in each Put as timestamped like any other
1161     // HBase value. A new ACL in a new Put applies to that Put. It doesn't
1162     // change the ACL of any previous Put. This allows simple evolution of
1163     // security policy over time without requiring expensive updates.
1164     requireCoveringPermission("put", c.getEnvironment(), put.getRow(),
1165       put.getFamilyCellMap(), put.getTimeStamp(), false, Permission.Action.WRITE);
1166     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1167     if (bytes != null) {
1168       if (canPersistCellACLs) {
1169         addCellPermissions(bytes, put.getFamilyCellMap());
1170       } else {
1171         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1172       }
1173     }
1174   }
1175 
1176   @Override
1177   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1178       final Put put, final WALEdit edit, final Durability durability) {
1179     if (aclRegion) {
1180       updateACL(c.getEnvironment(), put.getFamilyCellMap());
1181     }
1182   }
1183 
1184   @Override
1185   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1186       final Delete delete, final WALEdit edit, final Durability durability)
1187       throws IOException {
1188     // An ACL on a delete is useless, we shouldn't allow it
1189     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1190       throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString());
1191     }
1192     // Require WRITE permissions on all cells covered by the delete. Unlike
1193     // for Puts we need to check all visible prior versions, because a major
1194     // compaction could remove them. If the user doesn't have permission to
1195     // overwrite any of the visible versions ('visible' defined as not covered
1196     // by a tombstone already) then we have to disallow this operation.
1197     requireCoveringPermission("delete", c.getEnvironment(), delete.getRow(),
1198       delete.getFamilyCellMap(), delete.getTimeStamp(), true, Action.WRITE);
1199   }
1200 
1201   @Override
1202   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1203       final Delete delete, final WALEdit edit, final Durability durability)
1204       throws IOException {
1205     if (aclRegion) {
1206       updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1207     }
1208   }
1209 
1210   @Override
1211   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1212       final byte [] row, final byte [] family, final byte [] qualifier,
1213       final CompareFilter.CompareOp compareOp,
1214       final ByteArrayComparable comparator, final Put put,
1215       final boolean result) throws IOException {
1216     // Require READ and WRITE permissions on the table, CF, and KV to update
1217     requireCoveringPermission("checkAndPut", c.getEnvironment(), row,
1218       makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false,
1219       Action.READ, Action.WRITE);
1220     byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1221     if (bytes != null) {
1222       if (canPersistCellACLs) {
1223         addCellPermissions(bytes, put.getFamilyCellMap());
1224       } else {
1225         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1226       }
1227     }
1228     return result;
1229   }
1230 
1231   @Override
1232   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1233       final byte [] row, final byte [] family, final byte [] qualifier,
1234       final CompareFilter.CompareOp compareOp,
1235       final ByteArrayComparable comparator, final Delete delete,
1236       final boolean result) throws IOException {
1237     // An ACL on a delete is useless, we shouldn't allow it
1238     if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1239       throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " +
1240           delete.toString());
1241     }
1242     // Require READ and WRITE permissions on the table, CF, and the KV covered
1243     // by the delete
1244     requireCoveringPermission("checkAndDelete", c.getEnvironment(), row,
1245       makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false,
1246       Action.READ, Action.WRITE);
1247     return result;
1248   }
1249 
1250   @Override
1251   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
1252       final byte [] row, final byte [] family, final byte [] qualifier,
1253       final long amount, final boolean writeToWAL)
1254       throws IOException {
1255     // Require WRITE permission to the table, CF, and the KV to be replaced by the
1256     // incremented value
1257     requireCoveringPermission("incrementColumnValue", c.getEnvironment(), row,
1258       makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false,
1259       Action.WRITE);
1260     return -1;
1261   }
1262 
1263   @Override
1264   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
1265       throws IOException {
1266     // Require WRITE permission to the table, CF, and the KV to be appended
1267     requireCoveringPermission("append", c.getEnvironment(), append.getRow(),
1268       append.getFamilyCellMap(), append.getTimeStamp(), false,
1269       Action.WRITE);
1270     byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1271     if (bytes != null) {
1272       if (canPersistCellACLs) {
1273         addCellPermissions(bytes, append.getFamilyCellMap());
1274       } else {
1275         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1276       }
1277     }
1278     return null;
1279   }
1280 
1281   @Override
1282   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
1283       final Increment increment)
1284       throws IOException {
1285     // Require WRITE permission to the table, CF, and the KV to be replaced by
1286     // the incremented value
1287     requireCoveringPermission("increment", c.getEnvironment(), increment.getRow(),
1288       increment.getFamilyCellMap(), increment.getTimeRange().getMax(), false,
1289       Action.WRITE);
1290     byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1291     if (bytes != null) {
1292       if (canPersistCellACLs) {
1293         addCellPermissions(bytes, increment.getFamilyCellMap());
1294       } else {
1295         throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1296       }
1297     }
1298     return null;
1299   }
1300 
1301   @Override
1302   public Cell postMutationBeforeWAL(ObserverContext<RegionCoprocessorEnvironment> ctx,
1303       MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException {
1304     // If the HFile version is insufficient to persist tags, we won't have any
1305     // work to do here
1306     if (!canPersistCellACLs) {
1307       return newCell;
1308     }
1309 
1310     // Collect any ACLs from the old cell
1311     List<Tag> tags = Lists.newArrayList();
1312     ListMultimap<String,Permission> perms = ArrayListMultimap.create();
1313     if (oldCell != null) {
1314       byte[] tagBytes = CellUtil.getTagArray(oldCell);
1315       Iterator<Tag> tagIterator = CellUtil.tagsIterator(tagBytes, 0, tagBytes.length);
1316       while (tagIterator.hasNext()) {
1317         Tag tag = tagIterator.next();
1318         if (tag.getType() != AccessControlLists.ACL_TAG_TYPE) {
1319           if (LOG.isTraceEnabled()) {
1320             LOG.trace("Carrying forward tag from " + oldCell + ": type " + tag.getType() +
1321               " length " + tag.getValue().length);
1322           }
1323           tags.add(tag);
1324         } else {
1325           ListMultimap<String,Permission> kvPerms = ProtobufUtil.toUsersAndPermissions(
1326             AccessControlProtos.UsersAndPermissions.newBuilder().mergeFrom(
1327               tag.getBuffer(), tag.getTagOffset(), tag.getTagLength()).build());
1328           perms.putAll(kvPerms);
1329         }
1330       }
1331     }
1332 
1333     // Do we have an ACL on the operation?
1334     byte[] aclBytes = mutation.getACL();
1335     if (aclBytes != null) {
1336       // Yes, use it
1337       tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, aclBytes));
1338     } else {
1339       // No, use what we carried forward
1340       if (perms != null) {
1341         // TODO: If we collected ACLs from more than one tag we may have a
1342         // List<Permission> of size > 1, this can be collapsed into a single
1343         // Permission
1344         if (LOG.isTraceEnabled()) {
1345           LOG.trace("Carrying forward ACLs from " + oldCell + ": " + perms);
1346         }
1347         tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE,
1348           ProtobufUtil.toUsersAndPermissions(perms).toByteArray()));
1349       }
1350     }
1351 
1352     // If we have no tags to add, just return
1353     if (tags.isEmpty()) {
1354       return newCell;
1355     }
1356 
1357     // We need to create another KV, unfortunately, because the current new KV
1358     // has no space for tags
1359     KeyValue newKv = KeyValueUtil.ensureKeyValue(newCell);
1360     byte[] bytes = newKv.getBuffer();
1361     KeyValue rewriteKv = new KeyValue(bytes, newKv.getRowOffset(), newKv.getRowLength(),
1362       bytes, newKv.getFamilyOffset(), newKv.getFamilyLength(),
1363       bytes, newKv.getQualifierOffset(), newKv.getQualifierLength(),
1364       newKv.getTimestamp(), KeyValue.Type.codeToType(newKv.getTypeByte()),
1365       bytes, newKv.getValueOffset(), newKv.getValueLength(),
1366       tags);
1367     // Preserve mvcc data
1368     rewriteKv.setMvccVersion(newKv.getMvccVersion());
1369     return rewriteKv;
1370   }
1371 
1372   @Override
1373   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1374       final Scan scan, final RegionScanner s) throws IOException {
1375     internalPreRead(c, scan);
1376     return s;
1377   }
1378 
1379   @Override
1380   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1381       final Scan scan, final RegionScanner s) throws IOException {
1382     User user = getActiveUser();
1383     if (user != null && user.getShortName() != null) {      // store reference to scanner owner for later checks
1384       scannerOwners.put(s, user.getShortName());
1385     }
1386     return s;
1387   }
1388 
1389   @Override
1390   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
1391       final InternalScanner s, final List<Result> result,
1392       final int limit, final boolean hasNext) throws IOException {
1393     requireScannerOwner(s);
1394     return hasNext;
1395   }
1396 
1397   @Override
1398   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1399       final InternalScanner s) throws IOException {
1400     requireScannerOwner(s);
1401   }
1402 
1403   @Override
1404   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1405       final InternalScanner s) throws IOException {
1406     // clean up any associated owner mapping
1407     scannerOwners.remove(s);
1408   }
1409 
1410   /**
1411    * Verify, when servicing an RPC, that the caller is the scanner owner.
1412    * If so, we assume that access control is correctly enforced based on
1413    * the checks performed in preScannerOpen()
1414    */
1415   private void requireScannerOwner(InternalScanner s)
1416       throws AccessDeniedException {
1417     if (RequestContext.isInRequestContext()) {
1418       String requestUserName = RequestContext.getRequestUserName();
1419       String owner = scannerOwners.get(s);
1420       if (owner != null && !owner.equals(requestUserName)) {
1421         throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
1422       }
1423     }
1424   }
1425 
1426   /**
1427    * Verifies user has WRITE privileges on
1428    * the Column Families involved in the bulkLoadHFile
1429    * request. Specific Column Write privileges are presently
1430    * ignored.
1431    */
1432   @Override
1433   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
1434       List<Pair<byte[], String>> familyPaths) throws IOException {
1435     for(Pair<byte[],String> el : familyPaths) {
1436       requirePermission("preBulkLoadHFile",
1437           ctx.getEnvironment().getRegion().getTableDesc().getTableName(),
1438           el.getFirst(),
1439           null,
1440           Permission.Action.WRITE);
1441     }
1442   }
1443 
1444   private AuthResult hasSomeAccess(RegionCoprocessorEnvironment e, String method, Action action) throws IOException {
1445     User requestUser = getActiveUser();
1446     TableName tableName = e.getRegion().getTableDesc().getTableName();
1447     AuthResult authResult = permissionGranted(method, requestUser,
1448         action, e, Collections.EMPTY_MAP);
1449     if (!authResult.isAllowed()) {
1450       for(UserPermission userPerm:
1451           AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), tableName)) {
1452         for(Permission.Action userAction: userPerm.getActions()) {
1453           if(userAction.equals(action)) {
1454             return AuthResult.allow(method, "Access allowed", requestUser,
1455                 action, tableName, null, null);
1456           }
1457         }
1458       }
1459     }
1460     return authResult;
1461   }
1462 
1463   /**
1464    * Authorization check for
1465    * SecureBulkLoadProtocol.prepareBulkLoad()
1466    * @param e
1467    * @throws IOException
1468    */
1469   //TODO this should end up as a coprocessor hook
1470   public void prePrepareBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1471     AuthResult authResult = hasSomeAccess(e, "prePrepareBulkLoad", Action.WRITE);
1472     logResult(authResult);
1473     if (!authResult.isAllowed()) {
1474       throw new AccessDeniedException("Insufficient permissions (table=" +
1475         e.getRegion().getTableDesc().getTableName() + ", action=WRITE)");
1476     }
1477   }
1478 
1479   /**
1480    * Authorization security check for
1481    * SecureBulkLoadProtocol.cleanupBulkLoad()
1482    * @param e
1483    * @throws IOException
1484    */
1485   //TODO this should end up as a coprocessor hook
1486   public void preCleanupBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1487     AuthResult authResult = hasSomeAccess(e, "preCleanupBulkLoad", Action.WRITE);
1488     logResult(authResult);
1489     if (!authResult.isAllowed()) {
1490       throw new AccessDeniedException("Insufficient permissions (table=" +
1491         e.getRegion().getTableDesc().getTableName() + ", action=WRITE)");
1492     }
1493   }
1494 
1495   /* ---- EndpointObserver implementation ---- */
1496 
1497   @Override
1498   public Message preEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
1499       Service service, String methodName, Message request) throws IOException {
1500     // Don't intercept calls to our own AccessControlService, we check for
1501     // appropriate permissions in the service handlers
1502     if (shouldCheckExecPermissions && !(service instanceof AccessControlService)) {
1503       requirePermission("invoke(" + service.getDescriptorForType().getName() + "." +
1504         methodName + ")",
1505         getTableName(ctx.getEnvironment()), null, null,
1506         Action.EXEC);
1507     }
1508     return request;
1509   }
1510 
1511   @Override
1512   public void postEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
1513       Service service, String methodName, Message request, Message.Builder responseBuilder)
1514       throws IOException { }
1515 
1516   /* ---- Protobuf AccessControlService implementation ---- */
1517 
1518   @Override
1519   public void grant(RpcController controller,
1520                     AccessControlProtos.GrantRequest request,
1521                     RpcCallback<AccessControlProtos.GrantResponse> done) {
1522     UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
1523     AccessControlProtos.GrantResponse response = null;
1524     try {
1525       // verify it's only running at .acl.
1526       if (aclRegion) {
1527         if (!initialized) {
1528           throw new CoprocessorException("AccessController not yet initialized");
1529         }
1530         if (LOG.isDebugEnabled()) {
1531           LOG.debug("Received request to grant access permission " + perm.toString());
1532         }
1533 
1534         switch(request.getUserPermission().getPermission().getType()) {
1535           case Global :
1536           case Table :
1537             requirePermission("grant", perm.getTableName(), perm.getFamily(),
1538                 perm.getQualifier(), Action.ADMIN);
1539             break;
1540           case Namespace :
1541             requireGlobalPermission("grant", Action.ADMIN, perm.getNamespace());
1542         }
1543 
1544         AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
1545         if (AUDITLOG.isTraceEnabled()) {
1546           // audit log should store permission changes in addition to auth results
1547           AUDITLOG.trace("Granted permission " + perm.toString());
1548         }
1549       } else {
1550         throw new CoprocessorException(AccessController.class, "This method "
1551             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
1552       }
1553       response = AccessControlProtos.GrantResponse.getDefaultInstance();
1554     } catch (IOException ioe) {
1555       // pass exception back up
1556       ResponseConverter.setControllerException(controller, ioe);
1557     }
1558     done.run(response);
1559   }
1560 
1561   @Override
1562   public void revoke(RpcController controller,
1563                      AccessControlProtos.RevokeRequest request,
1564                      RpcCallback<AccessControlProtos.RevokeResponse> done) {
1565     UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
1566     AccessControlProtos.RevokeResponse response = null;
1567     try {
1568       // only allowed to be called on _acl_ region
1569       if (aclRegion) {
1570         if (!initialized) {
1571           throw new CoprocessorException("AccessController not yet initialized");
1572         }
1573         if (LOG.isDebugEnabled()) {
1574           LOG.debug("Received request to revoke access permission " + perm.toString());
1575         }
1576 
1577         switch(request.getUserPermission().getPermission().getType()) {
1578           case Global :
1579           case Table :
1580             requirePermission("revoke", perm.getTableName(), perm.getFamily(),
1581                               perm.getQualifier(), Action.ADMIN);
1582             break;
1583           case Namespace :
1584             requireGlobalPermission("revoke", Action.ADMIN, perm.getNamespace());
1585         }
1586 
1587         AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
1588         if (AUDITLOG.isTraceEnabled()) {
1589           // audit log should record all permission changes
1590           AUDITLOG.trace("Revoked permission " + perm.toString());
1591         }
1592       } else {
1593         throw new CoprocessorException(AccessController.class, "This method "
1594             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
1595       }
1596       response = AccessControlProtos.RevokeResponse.getDefaultInstance();
1597     } catch (IOException ioe) {
1598       // pass exception back up
1599       ResponseConverter.setControllerException(controller, ioe);
1600     }
1601     done.run(response);
1602   }
1603 
1604   @Override
1605   public void getUserPermissions(RpcController controller,
1606                                  AccessControlProtos.GetUserPermissionsRequest request,
1607                                  RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
1608     AccessControlProtos.GetUserPermissionsResponse response = null;
1609     try {
1610       // only allowed to be called on _acl_ region
1611       if (aclRegion) {
1612         if (!initialized) {
1613           throw new CoprocessorException("AccessController not yet initialized");
1614         }
1615         List<UserPermission> perms = null;
1616         if(request.getType() == AccessControlProtos.Permission.Type.Table) {
1617           TableName table = null;
1618           if (request.hasTableName()) {
1619             table = ProtobufUtil.toTableName(request.getTableName());
1620           }
1621           requirePermission("userPermissions", table, null, null, Action.ADMIN);
1622 
1623           perms = AccessControlLists.getUserTablePermissions(
1624               regionEnv.getConfiguration(), table);
1625         } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
1626           perms = AccessControlLists.getUserNamespacePermissions(
1627               regionEnv.getConfiguration(), request.getNamespaceName().toStringUtf8());
1628         } else {
1629           perms = AccessControlLists.getUserPermissions(
1630               regionEnv.getConfiguration(), null);
1631         }
1632         response = ResponseConverter.buildGetUserPermissionsResponse(perms);
1633       } else {
1634         throw new CoprocessorException(AccessController.class, "This method "
1635             + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
1636       }
1637     } catch (IOException ioe) {
1638       // pass exception back up
1639       ResponseConverter.setControllerException(controller, ioe);
1640     }
1641     done.run(response);
1642   }
1643 
1644   @Override
1645   public void checkPermissions(RpcController controller,
1646                                AccessControlProtos.CheckPermissionsRequest request,
1647                                RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
1648     Permission[] permissions = new Permission[request.getPermissionCount()];
1649     for (int i=0; i < request.getPermissionCount(); i++) {
1650       permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
1651     }
1652     AccessControlProtos.CheckPermissionsResponse response = null;
1653     try {
1654       TableName tableName = regionEnv.getRegion().getTableDesc().getTableName();
1655       for (Permission permission : permissions) {
1656         if (permission instanceof TablePermission) {
1657           TablePermission tperm = (TablePermission) permission;
1658           for (Permission.Action action : permission.getActions()) {
1659             if (!tperm.getTableName().equals(tableName)) {
1660               throw new CoprocessorException(AccessController.class, String.format("This method "
1661                   + "can only execute at the table specified in TablePermission. " +
1662                   "Table of the region:%s , requested table:%s", tableName,
1663                   tperm.getTableName()));
1664             }
1665 
1666             Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
1667             if (tperm.getFamily() != null) {
1668               if (tperm.getQualifier() != null) {
1669                 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
1670                 qualifiers.add(tperm.getQualifier());
1671                 familyMap.put(tperm.getFamily(), qualifiers);
1672               } else {
1673                 familyMap.put(tperm.getFamily(), null);
1674               }
1675             }
1676 
1677             requirePermission("checkPermissions", action, regionEnv, familyMap);
1678           }
1679 
1680         } else {
1681           for (Permission.Action action : permission.getActions()) {
1682             requirePermission("checkPermissions", action);
1683           }
1684         }
1685       }
1686       response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
1687     } catch (IOException ioe) {
1688       ResponseConverter.setControllerException(controller, ioe);
1689     }
1690     done.run(response);
1691   }
1692 
1693   @Override
1694   public Service getService() {
1695     return AccessControlProtos.AccessControlService.newReflectiveService(this);
1696   }
1697 
1698   private HRegion getRegion(RegionCoprocessorEnvironment e) {
1699     return e.getRegion();
1700   }
1701 
1702   private TableName getTableName(RegionCoprocessorEnvironment e) {
1703     HRegion region = e.getRegion();
1704     TableName tableName = null;
1705 
1706     if (region != null) {
1707       HRegionInfo regionInfo = region.getRegionInfo();
1708       if (regionInfo != null) {
1709         tableName = regionInfo.getTable();
1710       }
1711     }
1712     return tableName;
1713   }
1714 
1715 
1716   @Override
1717   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
1718       throws IOException {
1719     requirePermission("preClose", Action.ADMIN);
1720   }
1721 
1722   private void isSystemOrSuperUser(Configuration conf) throws IOException {
1723     User user = userProvider.getCurrent();
1724     if (user == null) {
1725       throw new IOException("Unable to obtain the current user, " +
1726         "authorization checks for internal operations will not work correctly!");
1727     }
1728 
1729     String currentUser = user.getShortName();
1730     List<String> superusers = Lists.asList(currentUser, conf.getStrings(
1731       AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
1732 
1733     User activeUser = getActiveUser();
1734     if (!(superusers.contains(activeUser.getShortName()))) {
1735       throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null") +
1736         "is not system or super user.");
1737     }
1738   }
1739 
1740   @Override
1741   public void preStopRegionServer(
1742       ObserverContext<RegionServerCoprocessorEnvironment> env)
1743       throws IOException {
1744     requirePermission("preStopRegionServer", Permission.Action.ADMIN);
1745   }
1746 
1747   private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
1748       byte[] qualifier) {
1749     if (family == null) {
1750       return null;
1751     }
1752 
1753     Map<byte[], Collection<byte[]>> familyMap = new TreeMap<byte[], Collection<byte[]>>(Bytes.BYTES_COMPARATOR);
1754     familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
1755     return familyMap;
1756   }
1757 
1758   @Override
1759   public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1760       List<TableName> tableNamesList,
1761       List<HTableDescriptor> descriptors) throws IOException {
1762     // If the list is empty, this is a request for all table descriptors and requires GLOBAL
1763     // ADMIN privs.
1764     if (tableNamesList == null || tableNamesList.isEmpty()) {
1765       requireGlobalPermission("getTableDescriptors", Permission.Action.ADMIN, null, null);
1766     }
1767     // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the
1768     // request can be granted.
1769     else {
1770       MasterServices masterServices = ctx.getEnvironment().getMasterServices();
1771       for (TableName tableName: tableNamesList) {
1772         // Do not deny if the table does not exist
1773         try {
1774           masterServices.checkTableModifiable(tableName);
1775         } catch (TableNotFoundException ex) {
1776           // Skip checks for a table that does not exist
1777           continue;
1778         } catch (TableNotDisabledException ex) {
1779           // We don't care about this
1780         }
1781         requirePermission("getTableDescriptors", tableName, null, null,
1782           Permission.Action.ADMIN, Permission.Action.CREATE);
1783       }
1784     }
1785   }
1786 
1787   @Override
1788   public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1789       List<HTableDescriptor> descriptors) throws IOException {
1790   }
1791 
1792   @Override
1793   public void preMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx, HRegion regionA,
1794       HRegion regionB) throws IOException {
1795     requirePermission("mergeRegions", regionA.getTableDesc().getTableName(), null, null,
1796       Action.ADMIN);
1797   }
1798 
1799   @Override
1800   public void postMerge(ObserverContext<RegionServerCoprocessorEnvironment> c, HRegion regionA,
1801       HRegion regionB, HRegion mergedRegion) throws IOException { }
1802 
1803   @Override
1804   public void preMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
1805       HRegion regionA, HRegion regionB, List<Mutation> metaEntries) throws IOException { }
1806 
1807   @Override
1808   public void postMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
1809       HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { }
1810 
1811   @Override
1812   public void preRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
1813       HRegion regionA, HRegion regionB) throws IOException { }
1814 
1815   @Override
1816   public void postRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
1817       HRegion regionA, HRegion regionB) throws IOException { }
1818 }