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