View Javadoc

1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    *     http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package org.apache.hadoop.hbase.security.access;
16  
17  import java.io.IOException;
18  import java.net.InetAddress;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.TreeSet;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.CoprocessorEnvironment;
33  import org.apache.hadoop.hbase.HColumnDescriptor;
34  import org.apache.hadoop.hbase.HRegionInfo;
35  import org.apache.hadoop.hbase.HTableDescriptor;
36  import org.apache.hadoop.hbase.KeyValue;
37  import org.apache.hadoop.hbase.ServerName;
38  import org.apache.hadoop.hbase.client.Append;
39  import org.apache.hadoop.hbase.client.Delete;
40  import org.apache.hadoop.hbase.client.Get;
41  import org.apache.hadoop.hbase.client.Increment;
42  import org.apache.hadoop.hbase.client.Put;
43  import org.apache.hadoop.hbase.client.Result;
44  import org.apache.hadoop.hbase.client.Scan;
45  import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
46  import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
47  import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
48  import org.apache.hadoop.hbase.coprocessor.MasterObserver;
49  import org.apache.hadoop.hbase.coprocessor.ObserverContext;
50  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
51  import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
52  import org.apache.hadoop.hbase.coprocessor.RegionServerObserver;
53  import org.apache.hadoop.hbase.filter.CompareFilter;
54  import org.apache.hadoop.hbase.filter.FilterList;
55  import org.apache.hadoop.hbase.filter.WritableByteArrayComparable;
56  import org.apache.hadoop.hbase.ipc.HBaseRPC;
57  import org.apache.hadoop.hbase.ipc.ProtocolSignature;
58  import org.apache.hadoop.hbase.ipc.RequestContext;
59  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
60  import org.apache.hadoop.hbase.regionserver.HRegion;
61  import org.apache.hadoop.hbase.regionserver.InternalScanner;
62  import org.apache.hadoop.hbase.regionserver.RegionScanner;
63  import org.apache.hadoop.hbase.regionserver.Store;
64  import org.apache.hadoop.hbase.regionserver.StoreFile;
65  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
66  import org.apache.hadoop.hbase.security.AccessDeniedException;
67  import org.apache.hadoop.hbase.security.User;
68  import org.apache.hadoop.hbase.security.access.Permission.Action;
69  import org.apache.hadoop.hbase.util.Bytes;
70  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
71  import org.apache.hadoop.hbase.util.Pair;
72  
73  import com.google.common.collect.ListMultimap;
74  import com.google.common.collect.Lists;
75  import com.google.common.collect.MapMaker;
76  import com.google.common.collect.Maps;
77  import com.google.common.collect.Sets;
78  
79  /**
80   * Provides basic authorization checks for data access and administrative
81   * operations.
82   *
83   * <p>
84   * {@code AccessController} performs authorization checks for HBase operations
85   * based on:
86   * <ul>
87   *   <li>the identity of the user performing the operation</li>
88   *   <li>the scope over which the operation is performed, in increasing
89   *   specificity: global, table, column family, or qualifier</li>
90   *   <li>the type of action being performed (as mapped to
91   *   {@link Permission.Action} values)</li>
92   * </ul>
93   * If the authorization check fails, an {@link AccessDeniedException}
94   * will be thrown for the operation.
95   * </p>
96   *
97   * <p>
98   * To perform authorization checks, {@code AccessController} relies on the
99   * {@link org.apache.hadoop.hbase.ipc.SecureRpcEngine} being loaded to provide
100  * the user identities for remote requests.
101  * </p>
102  *
103  * <p>
104  * The access control lists used for authorization can be manipulated via the
105  * exposed {@link AccessControllerProtocol} implementation, and the associated
106  * {@code grant}, {@code revoke}, and {@code user_permission} HBase shell
107  * commands.
108  * </p>
109  */
110 public class AccessController extends BaseRegionObserver
111     implements MasterObserver, RegionServerObserver, AccessControllerProtocol {
112   /**
113    * Represents the result of an authorization check for logging and error
114    * reporting.
115    */
116   private static class AuthResult {
117     private final boolean allowed;
118     private final byte[] table;
119     private final byte[] family;
120     private final byte[] qualifier;
121     private final Permission.Action action;
122     private final String request;
123     private final String reason;
124     private final User user;
125 
126     public AuthResult(boolean allowed, String request, String reason,  User user,
127         Permission.Action action, byte[] table, byte[] family, byte[] qualifier) {
128       this.allowed = allowed;
129       this.request = request;
130       this.reason = reason;
131       this.user = user;
132       this.table = table;
133       this.family = family;
134       this.qualifier = qualifier;
135       this.action = action;
136     }
137 
138     public boolean isAllowed() { return allowed; }
139 
140     public User getUser() { return user; }
141 
142     public String getReason() { return reason; }
143 
144     public String getRequest() { return request; }
145 
146     public String toContextString() {
147       return "(user=" + (user != null ? user.getName() : "UNKNOWN") + ", " +
148           "scope=" + (table == null ? "GLOBAL" : Bytes.toString(table)) + ", " +
149           "family=" + (family != null ? Bytes.toString(family) : "") + ", " +
150           "qualifer=" + (qualifier != null ? Bytes.toString(qualifier) : "") + ", " +
151           "action=" + (action != null ? action.toString() : "") + ")";
152     }
153 
154     public String toString() {
155       return new StringBuilder("AuthResult")
156           .append(toContextString()).toString();
157     }
158 
159     public static AuthResult allow(String request, String reason, User user, Permission.Action action,
160         byte[] table, byte[] family, byte[] qualifier) {
161       return new AuthResult(true, request, reason, user, action, table, family, qualifier);
162     }
163 
164     public static AuthResult allow(String request, String reason, User user, Permission.Action action, byte[] table) {
165       return new AuthResult(true, request, reason, user, action, table, null, null);
166     }
167 
168     public static AuthResult deny(String request, String reason, User user,
169         Permission.Action action, byte[] table) {
170       return new AuthResult(false, request, reason, user, action, table, null, null);
171     }
172 
173     public static AuthResult deny(String request, String reason, User user,
174         Permission.Action action, byte[] table, byte[] family, byte[] qualifier) {
175       return new AuthResult(false, request, reason, user, action, table, family, qualifier);
176     }
177   }
178 
179   public static final Log LOG = LogFactory.getLog(AccessController.class);
180 
181   private static final Log AUDITLOG =
182     LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
183 
184   /**
185    * Version number for AccessControllerProtocol
186    */
187   private static final long PROTOCOL_VERSION = 1L;
188 
189   TableAuthManager authManager = null;
190 
191   // flags if we are running on a region of the _acl_ table
192   boolean aclRegion = false;
193 
194   // defined only for Endpoint implementation, so it can have way to
195   // access region services.
196   private RegionCoprocessorEnvironment regionEnv;
197 
198   /** Mapping of scanner instances to the user who created them */
199   private Map<InternalScanner,String> scannerOwners =
200       new MapMaker().weakKeys().makeMap();
201 
202   void initialize(RegionCoprocessorEnvironment e) throws IOException {
203     final HRegion region = e.getRegion();
204 
205     Map<byte[],ListMultimap<String,TablePermission>> tables =
206         AccessControlLists.loadAll(region);
207     // For each table, write out the table's permissions to the respective
208     // znode for that table.
209     for (Map.Entry<byte[],ListMultimap<String,TablePermission>> t:
210       tables.entrySet()) {
211       byte[] table = t.getKey();
212       ListMultimap<String,TablePermission> perms = t.getValue();
213       byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms,
214           regionEnv.getConfiguration());
215       this.authManager.getZKPermissionWatcher().writeToZookeeper(table, serialized);
216     }
217   }
218 
219   /**
220    * Writes all table ACLs for the tables in the given Map up into ZooKeeper
221    * znodes.  This is called to synchronize ACL changes following {@code _acl_}
222    * table updates.
223    */
224   void updateACL(RegionCoprocessorEnvironment e,
225       final Map<byte[], List<KeyValue>> familyMap) {
226     Set<byte[]> tableSet = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
227     for (Map.Entry<byte[], List<KeyValue>> f : familyMap.entrySet()) {
228       List<KeyValue> kvs = f.getValue();
229       for (KeyValue kv: kvs) {
230         if (Bytes.equals(kv.getBuffer(), kv.getFamilyOffset(),
231             kv.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
232             AccessControlLists.ACL_LIST_FAMILY.length)) {
233           tableSet.add(kv.getRow());
234         }
235       }
236     }
237 
238     ZKPermissionWatcher zkw = this.authManager.getZKPermissionWatcher();
239     Configuration conf = regionEnv.getConfiguration();
240     for (byte[] tableName: tableSet) {
241       try {
242         ListMultimap<String,TablePermission> perms =
243           AccessControlLists.getTablePermissions(conf, tableName);
244         byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
245         zkw.writeToZookeeper(tableName, serialized);
246       } catch (IOException ex) {
247         LOG.error("Failed updating permissions mirror for '" + tableName + "'", ex);
248       }
249     }
250   }
251 
252   /**
253    * Check the current user for authorization to perform a specific action
254    * against the given set of row data.
255    *
256    * <p>Note: Ordering of the authorization checks
257    * has been carefully optimized to short-circuit the most common requests
258    * and minimize the amount of processing required.</p>
259    *
260    * @param permRequest the action being requested
261    * @param e the coprocessor environment
262    * @param families the map of column families to qualifiers present in
263    * the request
264    * @return
265    */
266   AuthResult permissionGranted(String request, User user, TablePermission.Action permRequest,
267       RegionCoprocessorEnvironment e,
268       Map<byte [], ? extends Collection<?>> families) {
269     HRegionInfo hri = e.getRegion().getRegionInfo();
270     byte[] tableName = hri.getTableName();
271 
272     // 1. All users need read access to .META. and -ROOT- tables.
273     // this is a very common operation, so deal with it quickly.
274     if (hri.isRootRegion() || hri.isMetaRegion()) {
275       if (permRequest == TablePermission.Action.READ) {
276         return AuthResult.allow(request, "All users allowed", user, permRequest, tableName);
277       }
278     }
279 
280     if (user == null) {
281       return AuthResult.deny(request, "No user associated with request!", null, permRequest, tableName);
282     }
283 
284     // Users with CREATE/ADMIN rights need to modify .META. and _acl_ table
285     // e.g. When a new table is created a new entry in .META. is added,
286     // so the user need to be allowed to write on it.
287     // e.g. When a table is removed an entry is removed from .META. and _acl_
288     // and the user need to be allowed to write on both tables.
289     if (permRequest == TablePermission.Action.WRITE &&
290        (hri.isRootRegion() || hri.isMetaRegion() ||
291         Bytes.equals(tableName, AccessControlLists.ACL_GLOBAL_NAME)) &&
292        (authManager.authorize(user, Permission.Action.CREATE) ||
293         authManager.authorize(user, Permission.Action.ADMIN)))
294     {
295        return AuthResult.allow(request, "Table permission granted", user, permRequest, tableName);
296     }
297 
298     // 2. check for the table-level, if successful we can short-circuit
299     if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
300       return AuthResult.allow(request, "Table permission granted", user, permRequest, tableName);
301     }
302 
303     // 3. check permissions against the requested families
304     if (families != null && families.size() > 0) {
305       // all families must pass
306       for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
307         // a) check for family level access
308         if (authManager.authorize(user, tableName, family.getKey(),
309             permRequest)) {
310           continue;  // family-level permission overrides per-qualifier
311         }
312 
313         // b) qualifier level access can still succeed
314         if ((family.getValue() != null) && (family.getValue().size() > 0)) {
315           if (family.getValue() instanceof Set) {
316             // for each qualifier of the family
317             Set<byte[]> familySet = (Set<byte[]>)family.getValue();
318             for (byte[] qualifier : familySet) {
319               if (!authManager.authorize(user, tableName, family.getKey(),
320                                          qualifier, permRequest)) {
321                 return AuthResult.deny(request, "Failed qualifier check", user,
322                     permRequest, tableName, family.getKey(), qualifier);
323               }
324             }
325           } else if (family.getValue() instanceof List) { // List<KeyValue>
326             List<KeyValue> kvList = (List<KeyValue>)family.getValue();
327             for (KeyValue kv : kvList) {
328               if (!authManager.authorize(user, tableName, family.getKey(),
329                       kv.getQualifier(), permRequest)) {
330                 return AuthResult.deny(request, "Failed qualifier check", user,
331                     permRequest, tableName, family.getKey(), kv.getQualifier());
332               }
333             }
334           }
335         } else {
336           // no qualifiers and family-level check already failed
337           return AuthResult.deny(request, "Failed family check", user, permRequest,
338               tableName, family.getKey(), null);
339         }
340       }
341 
342       // all family checks passed
343       return AuthResult.allow(request, "All family checks passed", user, permRequest,
344           tableName);
345     }
346 
347     // 4. no families to check and table level access failed
348     return AuthResult.deny(request, "No families to check and table permission failed",
349         user, permRequest, tableName);
350   }
351 
352   private void logResult(AuthResult result) {
353     if (AUDITLOG.isTraceEnabled()) {
354       InetAddress remoteAddr = null;
355       RequestContext ctx = RequestContext.get();
356       if (ctx != null) {
357         remoteAddr = ctx.getRemoteAddress();
358       }
359       AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
360           " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
361           "; reason: " + result.getReason() +
362           "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
363           "; request: " + result.getRequest() +
364           "; context: " + result.toContextString());
365     }
366   }
367 
368   /**
369    * Returns the active user to which authorization checks should be applied.
370    * If we are in the context of an RPC call, the remote user is used,
371    * otherwise the currently logged in user is used.
372    */
373   private User getActiveUser() throws IOException {
374     User user = RequestContext.getRequestUser();
375     if (!RequestContext.isInRequestContext()) {
376       // for non-rpc handling, fallback to system user
377       user = User.getCurrent();
378     }
379 
380     return user;
381   }
382 
383   /**
384    * Authorizes that the current user has any of the given permissions for the
385    * given table, column family and column qualifier.
386    * @param tableName Table requested
387    * @param family Column family requested
388    * @param qualifier Column qualifier requested
389    * @throws IOException if obtaining the current user fails
390    * @throws AccessDeniedException if user has no authorization
391    */
392   private void requirePermission(String request, byte[] tableName, byte[] family, byte[] qualifier,
393       Action... permissions) throws IOException {
394     User user = getActiveUser();
395     AuthResult result = null;
396 
397     for (Action permission : permissions) {
398       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
399         result = AuthResult.allow(request, "Table permission granted", user,
400                                   permission, tableName, family, qualifier);
401         break;
402       } else {
403         // rest of the world
404         result = AuthResult.deny(request, "Insufficient permissions", user,
405                                  permission, tableName, family, qualifier);
406       }
407     }
408     logResult(result);
409     if (!result.isAllowed()) {
410       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
411     }
412   }
413 
414   /**
415    * Authorizes that the current user has global privileges for the given action.
416    * @param perm The action being requested
417    * @throws IOException if obtaining the current user fails
418    * @throws AccessDeniedException if authorization is denied
419    */
420   private void requirePermission(String request, Permission.Action perm) throws IOException {
421     User user = getActiveUser();
422     if (authManager.authorize(user, perm)) {
423       logResult(AuthResult.allow(request, "Global check allowed", user, perm, null));
424     } else {
425       logResult(AuthResult.deny(request, "Global check failed", user, perm, null));
426       throw new AccessDeniedException("Insufficient permissions for user '" +
427           (user != null ? user.getShortName() : "null") +"' (global, action=" +
428           perm.toString() + ")");
429     }
430   }
431 
432   /**
433    * Authorizes that the current user has permission to perform the given
434    * action on the set of table column families.
435    * @param perm Action that is required
436    * @param env The current coprocessor environment
437    * @param families The set of column families present/required in the request
438    * @throws AccessDeniedException if the authorization check failed
439    */
440   private void requirePermission(String request, Permission.Action perm,
441         RegionCoprocessorEnvironment env, Collection<byte[]> families)
442       throws IOException {
443     // create a map of family-qualifier
444     HashMap<byte[], Set<byte[]>> familyMap = new HashMap<byte[], Set<byte[]>>();
445     for (byte[] family : families) {
446       familyMap.put(family, null);
447     }
448     requirePermission(request, perm, env, familyMap);
449   }
450 
451   /**
452    * Authorizes that the current user has permission to perform the given
453    * action on the set of table column families.
454    * @param perm Action that is required
455    * @param env The current coprocessor environment
456    * @param families The map of column families-qualifiers.
457    * @throws AccessDeniedException if the authorization check failed
458    */
459   public void requirePermission(String request, Permission.Action perm,
460         RegionCoprocessorEnvironment env,
461         Map<byte[], ? extends Collection<?>> families)
462       throws IOException {
463     User user = getActiveUser();
464     AuthResult result = permissionGranted(request, user, perm, env, families);
465     logResult(result);
466 
467     if (!result.isAllowed()) {
468       StringBuffer sb = new StringBuffer("");
469       if ((families != null && families.size() > 0)) {
470         for (byte[] familyName : families.keySet()) {
471           if (sb.length() != 0) {
472             sb.append(", ");
473           }
474           sb.append(Bytes.toString(familyName));
475         }
476       }
477       throw new AccessDeniedException("Insufficient permissions (table=" +
478         env.getRegion().getTableDesc().getNameAsString()+
479         ((families != null && families.size() > 0) ? ", family: " +
480         sb.toString() : "") + ", action=" +
481         perm.toString() + ")");
482     }
483   }
484 
485   /**
486    * Returns <code>true</code> if the current user is allowed the given action
487    * over at least one of the column qualifiers in the given column families.
488    */
489   private boolean hasFamilyQualifierPermission(User user,
490       TablePermission.Action perm,
491       RegionCoprocessorEnvironment env,
492       Map<byte[], ? extends Set<byte[]>> familyMap)
493     throws IOException {
494     HRegionInfo hri = env.getRegion().getRegionInfo();
495     byte[] tableName = hri.getTableName();
496 
497     if (user == null) {
498       return false;
499     }
500 
501     if (familyMap != null && familyMap.size() > 0) {
502       // at least one family must be allowed
503       for (Map.Entry<byte[], ? extends Set<byte[]>> family :
504           familyMap.entrySet()) {
505         if (family.getValue() != null && !family.getValue().isEmpty()) {
506           for (byte[] qualifier : family.getValue()) {
507             if (authManager.matchPermission(user, tableName,
508                 family.getKey(), qualifier, perm)) {
509               return true;
510             }
511           }
512         } else {
513           if (authManager.matchPermission(user, tableName, family.getKey(),
514               perm)) {
515             return true;
516           }
517         }
518       }
519     } else if (LOG.isDebugEnabled()) {
520       LOG.debug("Empty family map passed for permission check");
521     }
522 
523     return false;
524   }
525 
526   /* ---- MasterObserver implementation ---- */
527   public void start(CoprocessorEnvironment env) throws IOException {
528 
529     ZooKeeperWatcher zk = null;
530     if (env instanceof MasterCoprocessorEnvironment) {
531       // if running on HMaster
532       MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
533       zk = mEnv.getMasterServices().getZooKeeper();      
534     } else if (env instanceof RegionServerCoprocessorEnvironment) {      
535       RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
536       zk = rsEnv.getRegionServerServices().getZooKeeper();      
537     } else if (env instanceof RegionCoprocessorEnvironment) {
538       // if running at region
539       regionEnv = (RegionCoprocessorEnvironment) env;
540       zk = regionEnv.getRegionServerServices().getZooKeeper();
541     }
542 
543     // If zk is null or IOException while obtaining auth manager,
544     // throw RuntimeException so that the coprocessor is unloaded.
545     if (zk != null) {
546       try {
547         this.authManager = TableAuthManager.get(zk, env.getConfiguration());
548       } catch (IOException ioe) {
549         throw new RuntimeException("Error obtaining TableAuthManager", ioe);
550       }
551     } else {
552       throw new RuntimeException("Error obtaining TableAuthManager, zk found null.");
553     }
554   }
555 
556   public void stop(CoprocessorEnvironment env) {
557 
558   }
559 
560   @Override
561   public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
562       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
563     requirePermission("createTable", Permission.Action.CREATE);
564   }
565 
566   @Override
567   public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
568       HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
569     if (!AccessControlLists.isAclTable(desc)) {
570       String owner = desc.getOwnerString();
571       // default the table owner to current user, if not specified.
572       if (owner == null) owner = getActiveUser().getShortName();
573       UserPermission userperm = new UserPermission(Bytes.toBytes(owner), desc.getName(), null,
574           Action.values());
575       AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
576     }
577   }
578 
579   @Override
580   public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName)
581       throws IOException {
582    requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
583   }
584 
585   @Override
586   public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
587       byte[] tableName) throws IOException {
588     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName);
589   }
590 
591   @Override
592   public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
593       HTableDescriptor htd) throws IOException {
594     requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
595   }
596 
597   @Override
598   public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
599       byte[] tableName, HTableDescriptor htd) throws IOException {
600     String owner = htd.getOwnerString();
601     // default the table owner to current user, if not specified.
602     if (owner == null) owner = getActiveUser().getShortName();
603     UserPermission userperm = new UserPermission(Bytes.toBytes(owner), htd.getName(), null,
604         Action.values());
605     AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
606   }
607 
608   @Override
609   public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
610       HColumnDescriptor column) throws IOException {
611     requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
612   }
613 
614   @Override
615   public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> c,
616       byte[] tableName, HColumnDescriptor column) throws IOException {}
617 
618   @Override
619   public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
620       HColumnDescriptor descriptor) throws IOException {
621     requirePermission("modifyColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
622   }
623 
624   @Override
625   public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c,
626       byte[] tableName, HColumnDescriptor descriptor) throws IOException {}
627 
628   @Override
629   public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName,
630       byte[] col) throws IOException {
631     requirePermission("deleteColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
632   }
633 
634   @Override
635   public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
636       byte[] tableName, byte[] col) throws IOException {
637     AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(),
638                                               tableName, col);
639   }
640 
641   @Override
642   public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName)
643       throws IOException {
644     requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
645   }
646 
647   @Override
648   public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> c,
649       byte[] tableName) throws IOException {}
650 
651   @Override
652   public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, byte[] tableName)
653       throws IOException {
654     if (Bytes.equals(tableName, AccessControlLists.ACL_GLOBAL_NAME)) {
655       throw new AccessDeniedException("Not allowed to disable "
656           + AccessControlLists.ACL_TABLE_NAME_STR + " table.");
657     }
658     requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
659   }
660 
661   @Override
662   public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> c,
663       byte[] tableName) throws IOException {}
664 
665   @Override
666   public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
667       ServerName srcServer, ServerName destServer) throws IOException {
668     requirePermission("move", region.getTableName(), null, null, Action.ADMIN);
669   }
670 
671   @Override
672   public void postMove(ObserverContext<MasterCoprocessorEnvironment> c,
673       HRegionInfo region, ServerName srcServer, ServerName destServer)
674     throws IOException {}
675 
676   @Override
677   public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
678       throws IOException {
679     requirePermission("assign", regionInfo.getTableName(), null, null, Action.ADMIN);
680   }
681 
682   @Override
683   public void postAssign(ObserverContext<MasterCoprocessorEnvironment> c,
684       HRegionInfo regionInfo) throws IOException {}
685 
686   @Override
687   public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
688       boolean force) throws IOException {
689     requirePermission("unassign", regionInfo.getTableName(), null, null, Action.ADMIN);
690   }
691 
692   @Override
693   public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> c,
694       HRegionInfo regionInfo, boolean force) throws IOException {}
695 
696   @Override
697   public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
698       throws IOException {
699     requirePermission("balance", Permission.Action.ADMIN);
700   }
701   @Override
702   public void postBalance(ObserverContext<MasterCoprocessorEnvironment> c)
703       throws IOException {}
704 
705   @Override
706   public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
707       boolean newValue) throws IOException {
708     requirePermission("balanceSwitch", Permission.Action.ADMIN);
709     return newValue;
710   }
711   @Override
712   public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
713       boolean oldValue, boolean newValue) throws IOException {}
714 
715   @Override
716   public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
717       throws IOException {
718     requirePermission("shutdown", Permission.Action.ADMIN);
719   }
720 
721   @Override
722   public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
723       throws IOException {
724     requirePermission("stopMaster", Permission.Action.ADMIN);
725   }
726 
727   @Override
728   public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
729       throws IOException {
730     // initialize the ACL storage table
731     AccessControlLists.init(ctx.getEnvironment().getMasterServices());
732   }
733 
734   @Override
735   public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
736       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
737       throws IOException {
738     requirePermission("snapshot", Permission.Action.ADMIN);
739   }
740 
741   @Override
742   public void postSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
743       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
744       throws IOException {
745   }
746 
747   @Override
748   public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
749       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
750       throws IOException {
751     requirePermission("cloneSnapshot", Permission.Action.ADMIN);
752   }
753 
754   @Override
755   public void postCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
756       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
757       throws IOException {
758   }
759 
760   @Override
761   public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
762       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
763       throws IOException {
764     requirePermission("restoreSnapshot", Permission.Action.ADMIN);
765   }
766 
767   @Override
768   public void postRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
769       final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
770       throws IOException {
771   }
772 
773   @Override
774   public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
775       final SnapshotDescription snapshot) throws IOException {
776     requirePermission("deleteSnapshot", Permission.Action.ADMIN);
777   }
778 
779   @Override
780   public void postDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
781       final SnapshotDescription snapshot) throws IOException {
782   }
783 
784   /* ---- RegionObserver implementation ---- */
785 
786   @Override
787   public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
788     RegionCoprocessorEnvironment env = e.getEnvironment();
789     final HRegion region = env.getRegion();
790     if (region == null) {
791       LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
792       return;
793     } else {
794       HRegionInfo regionInfo = region.getRegionInfo();
795       if (isSpecialTable(regionInfo)) {
796         isSystemOrSuperUser(regionEnv.getConfiguration());
797       } else {
798         requirePermission("open", Action.ADMIN);
799       }
800     }
801   }
802 
803   @Override
804   public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
805     RegionCoprocessorEnvironment env = c.getEnvironment();
806     final HRegion region = env.getRegion();
807     if (region == null) {
808       LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
809       return;
810     }
811     if (AccessControlLists.isAclRegion(region)) {
812       aclRegion = true;
813       try {
814         initialize(env);
815       } catch (IOException ex) {
816         // if we can't obtain permissions, it's better to fail
817         // than perform checks incorrectly
818         throw new RuntimeException("Failed to initialize permissions cache", ex);
819       }
820     }
821   }
822 
823   @Override
824   public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
825     requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
826   }
827 
828   @Override
829   public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
830     requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
831   }
832 
833   @Override
834   public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
835       final Store store, final InternalScanner scanner) throws IOException {
836     requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
837     return scanner;
838   }
839 
840   @Override
841   public void preCompactSelection(final ObserverContext<RegionCoprocessorEnvironment> e,
842       final Store store, final List<StoreFile> candidates) throws IOException {
843     requirePermission("compactSelection", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
844   }
845 
846   @Override
847   public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
848       final byte [] row, final byte [] family, final Result result)
849       throws IOException {
850     requirePermission("getClosestRowBefore", TablePermission.Action.READ, c.getEnvironment(),
851         (family != null ? Lists.newArrayList(family) : null));
852   }
853 
854   @Override
855   public void preGet(final ObserverContext<RegionCoprocessorEnvironment> c,
856       final Get get, final List<KeyValue> result) throws IOException {
857     /*
858      if column family level checks fail, check for a qualifier level permission
859      in one of the families.  If it is present, then continue with the AccessControlFilter.
860       */
861     RegionCoprocessorEnvironment e = c.getEnvironment();
862     User requestUser = getActiveUser();
863     AuthResult authResult = permissionGranted("get", requestUser,
864         TablePermission.Action.READ, e, get.getFamilyMap());
865     if (!authResult.isAllowed()) {
866       if (hasFamilyQualifierPermission(requestUser,
867           TablePermission.Action.READ, e, get.getFamilyMap())) {
868         byte[] table = getTableName(e);
869         AccessControlFilter filter = new AccessControlFilter(authManager,
870             requestUser, table);
871 
872         // wrap any existing filter
873         if (get.getFilter() != null) {
874           FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL,
875               Lists.newArrayList(filter, get.getFilter()));
876           get.setFilter(wrapper);
877         } else {
878           get.setFilter(filter);
879         }
880         logResult(AuthResult.allow("get", "Access allowed with filter", requestUser,
881             TablePermission.Action.READ, authResult.table));
882       } else {
883         logResult(authResult);
884         throw new AccessDeniedException("Insufficient permissions (table=" +
885           e.getRegion().getTableDesc().getNameAsString() + ", action=READ)");
886       }
887     } else {
888       // log auth success
889       logResult(authResult);
890     }
891   }
892 
893   @Override
894   public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
895       final Get get, final boolean exists) throws IOException {
896     requirePermission("exists", TablePermission.Action.READ, c.getEnvironment(),
897         get.familySet());
898     return exists;
899   }
900 
901   @Override
902   public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
903       final Put put, final WALEdit edit, final boolean writeToWAL)
904       throws IOException {
905     requirePermission("put", TablePermission.Action.WRITE, c.getEnvironment(),
906         put.getFamilyMap());
907   }
908 
909   @Override
910   public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
911       final Put put, final WALEdit edit, final boolean writeToWAL) {
912     if (aclRegion) {
913       updateACL(c.getEnvironment(), put.getFamilyMap());
914     }
915   }
916 
917   @Override
918   public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
919       final Delete delete, final WALEdit edit, final boolean writeToWAL)
920       throws IOException {
921     requirePermission("delete", TablePermission.Action.WRITE, c.getEnvironment(),
922         delete.getFamilyMap());
923   }
924 
925   @Override
926   public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
927       final Delete delete, final WALEdit edit, final boolean writeToWAL)
928       throws IOException {
929     if (aclRegion) {
930       updateACL(c.getEnvironment(), delete.getFamilyMap());
931     }
932   }
933 
934   @Override
935   public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
936       final byte [] row, final byte [] family, final byte [] qualifier,
937       final CompareFilter.CompareOp compareOp,
938       final WritableByteArrayComparable comparator, final Put put,
939       final boolean result) throws IOException {
940     Collection<byte[]> familyMap = Arrays.asList(new byte[][]{family});
941     requirePermission("checkAndPut", TablePermission.Action.READ, c.getEnvironment(), familyMap);
942     requirePermission("checkAndPut", TablePermission.Action.WRITE, c.getEnvironment(), familyMap);
943     return result;
944   }
945 
946   @Override
947   public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
948       final byte [] row, final byte [] family, final byte [] qualifier,
949       final CompareFilter.CompareOp compareOp,
950       final WritableByteArrayComparable comparator, final Delete delete,
951       final boolean result) throws IOException {
952     Collection<byte[]> familyMap = Arrays.asList(new byte[][]{family});
953     requirePermission("checkAndDelete", TablePermission.Action.READ, c.getEnvironment(), familyMap);
954     requirePermission("checkAndDelete", TablePermission.Action.WRITE, c.getEnvironment(), familyMap);
955     return result;
956   }
957 
958   @Override
959   public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
960       final byte [] row, final byte [] family, final byte [] qualifier,
961       final long amount, final boolean writeToWAL)
962       throws IOException {
963     requirePermission("incrementColumnValue", TablePermission.Action.WRITE, c.getEnvironment(),
964         Arrays.asList(new byte[][]{family}));
965     return -1;
966   }
967 
968   @Override
969   public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
970       throws IOException {
971     requirePermission("append", TablePermission.Action.WRITE, c.getEnvironment(), append.getFamilyMap());
972     return null;
973   }
974 
975   @Override
976   public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
977       final Increment increment)
978       throws IOException {
979     requirePermission("increment", TablePermission.Action.WRITE, c.getEnvironment(),
980         increment.getFamilyMap().keySet());
981     return null;
982   }
983 
984   @Override
985   public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
986       final Scan scan, final RegionScanner s) throws IOException {
987     /*
988      if column family level checks fail, check for a qualifier level permission
989      in one of the families.  If it is present, then continue with the AccessControlFilter.
990       */
991     RegionCoprocessorEnvironment e = c.getEnvironment();
992     User user = getActiveUser();
993     AuthResult authResult = permissionGranted("scannerOpen", user, TablePermission.Action.READ, e,
994         scan.getFamilyMap());
995     if (!authResult.isAllowed()) {
996       if (hasFamilyQualifierPermission(user, TablePermission.Action.READ, e,
997           scan.getFamilyMap())) {
998         byte[] table = getTableName(e);
999         AccessControlFilter filter = new AccessControlFilter(authManager,
1000             user, table);
1001 
1002         // wrap any existing filter
1003         if (scan.hasFilter()) {
1004           FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1005               Lists.newArrayList(filter, scan.getFilter()));
1006           scan.setFilter(wrapper);
1007         } else {
1008           scan.setFilter(filter);
1009         }
1010         logResult(AuthResult.allow("scannerOpen", "Access allowed with filter", user,
1011             TablePermission.Action.READ, authResult.table));
1012       } else {
1013         // no table/family level perms and no qualifier level perms, reject
1014         logResult(authResult);
1015         throw new AccessDeniedException("Insufficient permissions for user '"+
1016             (user != null ? user.getShortName() : "null")+"' "+
1017             "for scanner open on table " + Bytes.toString(getTableName(e)));
1018       }
1019     } else {
1020       // log success
1021       logResult(authResult);
1022     }
1023     return s;
1024   }
1025 
1026   @Override
1027   public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
1028       final Scan scan, final RegionScanner s) throws IOException {
1029     User user = getActiveUser();
1030     if (user != null && user.getShortName() != null) {      // store reference to scanner owner for later checks
1031       scannerOwners.put(s, user.getShortName());
1032     }
1033     return s;
1034   }
1035 
1036   @Override
1037   public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
1038       final InternalScanner s, final List<Result> result,
1039       final int limit, final boolean hasNext) throws IOException {
1040     requireScannerOwner(s);
1041     return hasNext;
1042   }
1043 
1044   @Override
1045   public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1046       final InternalScanner s) throws IOException {
1047     requireScannerOwner(s);
1048   }
1049 
1050   @Override
1051   public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
1052       final InternalScanner s) throws IOException {
1053     // clean up any associated owner mapping
1054     scannerOwners.remove(s);
1055   }
1056 
1057   /**
1058    * Verify, when servicing an RPC, that the caller is the scanner owner.
1059    * If so, we assume that access control is correctly enforced based on
1060    * the checks performed in preScannerOpen()
1061    */
1062   private void requireScannerOwner(InternalScanner s)
1063       throws AccessDeniedException {
1064     if (RequestContext.isInRequestContext()) {
1065       String requestUserName = RequestContext.getRequestUserName();
1066       String owner = scannerOwners.get(s);
1067       if (owner != null && !owner.equals(requestUserName)) {
1068         throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
1069       }
1070     }
1071   }
1072 
1073   /**
1074    * Verifies user has WRITE privileges on
1075    * the Column Families involved in the bulkLoadHFile
1076    * request. Specific Column Write privileges are presently
1077    * ignored.
1078    */
1079   @Override
1080   public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
1081       List<Pair<byte[], String>> familyPaths) throws IOException {
1082     List<byte[]> cfs = new LinkedList<byte[]>();
1083     for(Pair<byte[],String> el : familyPaths) {
1084       cfs.add(el.getFirst());
1085     }
1086     requirePermission("bulkLoadHFile", Permission.Action.WRITE, ctx.getEnvironment(), cfs);
1087   }
1088 
1089   private AuthResult hasSomeAccess(RegionCoprocessorEnvironment e, String request, Action action) throws IOException {
1090     User requestUser = getActiveUser();
1091     byte[] tableName = e.getRegion().getTableDesc().getName();
1092     AuthResult authResult = permissionGranted(request, requestUser,
1093         action, e, Collections.EMPTY_MAP);
1094     if (!authResult.isAllowed()) {
1095       for(UserPermission userPerm:
1096           AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), tableName)) {
1097         for(Permission.Action userAction: userPerm.getActions()) {
1098           if(userAction.equals(action)) {
1099             return AuthResult.allow(request, "Access allowed", requestUser,
1100                 action, tableName);
1101           }
1102         }
1103       }
1104     }
1105     return authResult;
1106   }
1107 
1108   /**
1109    * Authorization check for
1110    * SecureBulkLoadProtocol.prepareBulkLoad()
1111    * @param e
1112    * @throws IOException
1113    */
1114   public void prePrepareBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1115     AuthResult authResult = hasSomeAccess(e, "prepareBulkLoad", Action.WRITE);
1116     logResult(authResult);
1117     if (!authResult.isAllowed()) {
1118       throw new AccessDeniedException("Insufficient permissions (table=" +
1119         e.getRegion().getTableDesc().getNameAsString() + ", action=WRITE)");
1120     }
1121   }
1122 
1123   /**
1124    * Authorization security check for
1125    * SecureBulkLoadProtocol.cleanupBulkLoad()
1126    * @param e
1127    * @throws IOException
1128    */
1129   //TODO this should end up as a coprocessor hook
1130   public void preCleanupBulkLoad(RegionCoprocessorEnvironment e) throws IOException {
1131     AuthResult authResult = hasSomeAccess(e, "cleanupBulkLoad", Action.WRITE);
1132     logResult(authResult);
1133     if (!authResult.isAllowed()) {
1134       throw new AccessDeniedException("Insufficient permissions (table=" +
1135         e.getRegion().getTableDesc().getNameAsString() + ", action=WRITE)");
1136     }
1137   }
1138 
1139   /* ---- AccessControllerProtocol implementation ---- */
1140   /*
1141    * These methods are only allowed to be called against the _acl_ region(s).
1142    * This will be restricted by both client side and endpoint implementations.
1143    */
1144   @Override
1145   public void grant(UserPermission perm) throws IOException {
1146     // verify it's only running at .acl.
1147     if (aclRegion) {
1148       if (LOG.isDebugEnabled()) {
1149         LOG.debug("Received request to grant access permission " + perm.toString());
1150       }
1151 
1152       requirePermission("grant", perm.getTable(), perm.getFamily(), perm.getQualifier(), Action.ADMIN);
1153 
1154       AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
1155       if (AUDITLOG.isTraceEnabled()) {
1156         // audit log should store permission changes in addition to auth results
1157         AUDITLOG.trace("Granted permission " + perm.toString());
1158       }
1159     } else {
1160       throw new CoprocessorException(AccessController.class, "This method "
1161           + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table.");
1162     }
1163   }
1164 
1165   @Override
1166   @Deprecated
1167   public void grant(byte[] user, TablePermission permission)
1168       throws IOException {
1169     grant(new UserPermission(user, permission.getTable(),
1170             permission.getFamily(), permission.getQualifier(),
1171             permission.getActions()));
1172   }
1173 
1174   @Override
1175   public void revoke(UserPermission perm) throws IOException {
1176     // only allowed to be called on _acl_ region
1177     if (aclRegion) {
1178       if (LOG.isDebugEnabled()) {
1179         LOG.debug("Received request to revoke access permission " + perm.toString());
1180       }
1181 
1182       requirePermission("revoke", perm.getTable(), perm.getFamily(),
1183                         perm.getQualifier(), Action.ADMIN);
1184 
1185       AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
1186       if (AUDITLOG.isTraceEnabled()) {
1187         // audit log should record all permission changes
1188         AUDITLOG.trace("Revoked permission " + perm.toString());
1189       }
1190     } else {
1191       throw new CoprocessorException(AccessController.class, "This method "
1192           + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table.");
1193     }
1194   }
1195 
1196   @Override
1197   @Deprecated
1198   public void revoke(byte[] user, TablePermission permission)
1199       throws IOException {
1200     revoke(new UserPermission(user, permission.getTable(),
1201             permission.getFamily(), permission.getQualifier(),
1202             permission.getActions()));
1203   }
1204 
1205   @Override
1206   public List<UserPermission> getUserPermissions(final byte[] tableName) throws IOException {
1207     // only allowed to be called on _acl_ region
1208     if (aclRegion) {
1209       requirePermission("userPermissions", tableName, null, null, Action.ADMIN);
1210 
1211       List<UserPermission> perms = AccessControlLists.getUserPermissions(
1212         regionEnv.getConfiguration(), tableName);
1213       return perms;
1214     } else {
1215       throw new CoprocessorException(AccessController.class, "This method "
1216           + "can only execute at " + Bytes.toString(AccessControlLists.ACL_TABLE_NAME) + " table.");
1217     }
1218   }
1219 
1220   @Override
1221   public void checkPermissions(Permission[] permissions) throws IOException {
1222     byte[] tableName = regionEnv.getRegion().getTableDesc().getName();
1223     for (Permission permission : permissions) {
1224       if (permission instanceof TablePermission) {
1225         TablePermission tperm = (TablePermission) permission;
1226         for (Permission.Action action : permission.getActions()) {
1227           if (!Arrays.equals(tperm.getTable(), tableName)) {
1228             throw new CoprocessorException(AccessController.class, String.format("This method "
1229                 + "can only execute at the table specified in TablePermission. " +
1230                 "Table of the region:%s , requested table:%s", Bytes.toString(tableName),
1231                 Bytes.toString(tperm.getTable())));
1232           }
1233 
1234           HashMap<byte[], Set<byte[]>> familyMap = Maps.newHashMapWithExpectedSize(1);
1235           if (tperm.getFamily() != null) {
1236             if (tperm.getQualifier() != null) {
1237               familyMap.put(tperm.getFamily(), Sets.newHashSet(tperm.getQualifier()));
1238             } else {
1239               familyMap.put(tperm.getFamily(), null);
1240             }
1241           }
1242 
1243           requirePermission("checkPermissions", action, regionEnv, familyMap);
1244         }
1245 
1246       } else {
1247         for (Permission.Action action : permission.getActions()) {
1248           requirePermission("checkPermissions", action);
1249         }
1250       }
1251     }
1252   }
1253 
1254   @Override
1255   public long getProtocolVersion(String protocol, long clientVersion) throws IOException {
1256     return PROTOCOL_VERSION;
1257   }
1258 
1259   @Override
1260   public ProtocolSignature getProtocolSignature(String protocol,
1261       long clientVersion, int clientMethodsHash) throws IOException {
1262     if (AccessControllerProtocol.class.getName().equals(protocol)) {
1263       return new ProtocolSignature(PROTOCOL_VERSION, null);
1264     }
1265     throw new HBaseRPC.UnknownProtocolException(
1266         "Unexpected protocol requested: "+protocol);
1267   }
1268 
1269   private byte[] getTableName(RegionCoprocessorEnvironment e) {
1270     HRegion region = e.getRegion();
1271     byte[] tableName = null;
1272 
1273     if (region != null) {
1274       HRegionInfo regionInfo = region.getRegionInfo();
1275       if (regionInfo != null) {
1276         tableName = regionInfo.getTableName();
1277       }
1278     }
1279     return tableName;
1280   }
1281 
1282 
1283   @Override
1284   public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
1285       throws IOException {
1286     requirePermission("close", Permission.Action.ADMIN);
1287   }
1288 
1289   @Override
1290   public void preLockRow(ObserverContext<RegionCoprocessorEnvironment> ctx, byte[] regionName,
1291       byte[] row) throws IOException {
1292     requirePermission("lockRow", getTableName(ctx.getEnvironment()), null, null,
1293       Permission.Action.WRITE, Permission.Action.CREATE);
1294   }
1295 
1296   @Override
1297   public void preUnlockRow(ObserverContext<RegionCoprocessorEnvironment> ctx, byte[] regionName,
1298       long lockId) throws IOException {
1299     requirePermission("unlockRow", getTableName(ctx.getEnvironment()), null, null,
1300       Permission.Action.WRITE, Permission.Action.CREATE);
1301   }
1302 
1303   private void isSystemOrSuperUser(Configuration conf) throws IOException {
1304     User user = User.getCurrent();
1305     if (user == null) {
1306       throw new IOException("Unable to obtain the current user, "
1307           + "authorization checks for internal operations will not work correctly!");
1308     }
1309 
1310     String currentUser = user.getShortName();
1311     List<String> superusers = Lists.asList(currentUser,
1312       conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
1313 
1314     User activeUser = getActiveUser();
1315     if (!(superusers.contains(activeUser.getShortName()))) {
1316       throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null")
1317           + "is not system or super user.");
1318     }
1319   }
1320 
1321   private boolean isSpecialTable(HRegionInfo regionInfo) {
1322     byte[] tableName = regionInfo.getTableName();
1323     return tableName.equals(AccessControlLists.ACL_TABLE_NAME)
1324       || tableName.equals(Bytes.toBytes("-ROOT-"))
1325       || tableName.equals(Bytes.toBytes(".META."));
1326   }
1327 
1328   @Override
1329   public void preStopRegionServer(ObserverContext<RegionServerCoprocessorEnvironment> env)
1330       throws IOException {
1331     requirePermission("stop", Permission.Action.ADMIN);
1332   }
1333 }