View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.security.access;
20  
21  import java.io.IOException;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.concurrent.ConcurrentSkipListMap;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.AuthUtil;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.Cell;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.exceptions.DeserializationException;
35  import org.apache.hadoop.hbase.security.Superusers;
36  import org.apache.hadoop.hbase.security.User;
37  import org.apache.hadoop.hbase.security.UserProvider;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
40  import org.apache.zookeeper.KeeperException;
41  
42  import com.google.common.collect.ArrayListMultimap;
43  import com.google.common.collect.ListMultimap;
44  import com.google.common.collect.Lists;
45  
46  /**
47   * Performs authorization checks for a given user's assigned permissions
48   */
49  @InterfaceAudience.Private
50  public class TableAuthManager {
51    private static class PermissionCache<T extends Permission> {
52      /** Cache of user permissions */
53      private ListMultimap<String,T> userCache = ArrayListMultimap.create();
54      /** Cache of group permissions */
55      private ListMultimap<String,T> groupCache = ArrayListMultimap.create();
56  
57      public List<T> getUser(String user) {
58        return userCache.get(user);
59      }
60  
61      public void putUser(String user, T perm) {
62        userCache.put(user, perm);
63      }
64  
65      public List<T> replaceUser(String user, Iterable<? extends T> perms) {
66        return userCache.replaceValues(user, perms);
67      }
68  
69      public List<T> getGroup(String group) {
70        return groupCache.get(group);
71      }
72  
73      public void putGroup(String group, T perm) {
74        groupCache.put(group, perm);
75      }
76  
77      public List<T> replaceGroup(String group, Iterable<? extends T> perms) {
78        return groupCache.replaceValues(group, perms);
79      }
80  
81      /**
82       * Returns a combined map of user and group permissions, with group names prefixed by
83       * {@link AuthUtil#GROUP_PREFIX}.
84       */
85      public ListMultimap<String,T> getAllPermissions() {
86        ListMultimap<String,T> tmp = ArrayListMultimap.create();
87        tmp.putAll(userCache);
88        for (String group : groupCache.keySet()) {
89          tmp.putAll(AuthUtil.toGroupEntry(group), groupCache.get(group));
90        }
91        return tmp;
92      }
93    }
94  
95    private static Log LOG = LogFactory.getLog(TableAuthManager.class);
96  
97    private static TableAuthManager instance;
98  
99    /** Cache of global permissions */
100   private volatile PermissionCache<Permission> globalCache;
101 
102   private ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>> tableCache =
103       new ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>>();
104 
105   private ConcurrentSkipListMap<String, PermissionCache<TablePermission>> nsCache =
106     new ConcurrentSkipListMap<String, PermissionCache<TablePermission>>();
107 
108   private Configuration conf;
109   private ZKPermissionWatcher zkperms;
110   private volatile long mtime;
111 
112   private TableAuthManager(ZooKeeperWatcher watcher, Configuration conf)
113       throws IOException {
114     this.conf = conf;
115 
116     // initialize global permissions based on configuration
117     globalCache = initGlobal(conf);
118 
119     this.zkperms = new ZKPermissionWatcher(watcher, this, conf);
120     try {
121       this.zkperms.start();
122     } catch (KeeperException ke) {
123       LOG.error("ZooKeeper initialization failed", ke);
124     }
125   }
126 
127   /**
128    * Returns a new {@code PermissionCache} initialized with permission assignments
129    * from the {@code hbase.superuser} configuration key.
130    */
131   private PermissionCache<Permission> initGlobal(Configuration conf) throws IOException {
132     UserProvider userProvider = UserProvider.instantiate(conf);
133     User user = userProvider.getCurrent();
134     if (user == null) {
135       throw new IOException("Unable to obtain the current user, " +
136           "authorization checks for internal operations will not work correctly!");
137     }
138     PermissionCache<Permission> newCache = new PermissionCache<Permission>();
139     String currentUser = user.getShortName();
140 
141     // the system user is always included
142     List<String> superusers = Lists.asList(currentUser, conf.getStrings(
143         Superusers.SUPERUSER_CONF_KEY, new String[0]));
144     if (superusers != null) {
145       for (String name : superusers) {
146         if (AuthUtil.isGroupPrincipal(name)) {
147           newCache.putGroup(AuthUtil.getGroupName(name),
148               new Permission(Permission.Action.values()));
149         } else {
150           newCache.putUser(name, new Permission(Permission.Action.values()));
151         }
152       }
153     }
154     return newCache;
155   }
156 
157   public ZKPermissionWatcher getZKPermissionWatcher() {
158     return this.zkperms;
159   }
160 
161   public void refreshTableCacheFromWritable(TableName table,
162                                        byte[] data) throws IOException {
163     if (data != null && data.length > 0) {
164       ListMultimap<String,TablePermission> perms;
165       try {
166         perms = AccessControlLists.readPermissions(data, conf);
167       } catch (DeserializationException e) {
168         throw new IOException(e);
169       }
170 
171       if (perms != null) {
172         if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
173           updateGlobalCache(perms);
174         } else {
175           updateTableCache(table, perms);
176         }
177       }
178     } else {
179       LOG.debug("Skipping permission cache refresh because writable data is empty");
180     }
181   }
182 
183   public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException {
184     if (data != null && data.length > 0) {
185       ListMultimap<String,TablePermission> perms;
186       try {
187         perms = AccessControlLists.readPermissions(data, conf);
188       } catch (DeserializationException e) {
189         throw new IOException(e);
190       }
191       if (perms != null) {
192         updateNsCache(namespace, perms);
193       }
194     } else {
195       LOG.debug("Skipping permission cache refresh because writable data is empty");
196     }
197   }
198 
199   /**
200    * Updates the internal global permissions cache
201    *
202    * @param userPerms
203    */
204   private void updateGlobalCache(ListMultimap<String,TablePermission> userPerms) {
205     PermissionCache<Permission> newCache = null;
206     try {
207       newCache = initGlobal(conf);
208       for (Map.Entry<String,TablePermission> entry : userPerms.entries()) {
209         if (AuthUtil.isGroupPrincipal(entry.getKey())) {
210           newCache.putGroup(AuthUtil.getGroupName(entry.getKey()),
211               new Permission(entry.getValue().getActions()));
212         } else {
213           newCache.putUser(entry.getKey(), new Permission(entry.getValue().getActions()));
214         }
215       }
216       globalCache = newCache;
217       mtime++;
218     } catch (IOException e) {
219       // Never happens
220       LOG.error("Error occured while updating the global cache", e);
221     }
222   }
223 
224   /**
225    * Updates the internal permissions cache for a single table, splitting
226    * the permissions listed into separate caches for users and groups to optimize
227    * group lookups.
228    *
229    * @param table
230    * @param tablePerms
231    */
232   private void updateTableCache(TableName table,
233                                 ListMultimap<String,TablePermission> tablePerms) {
234     PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
235 
236     for (Map.Entry<String,TablePermission> entry : tablePerms.entries()) {
237       if (AuthUtil.isGroupPrincipal(entry.getKey())) {
238         newTablePerms.putGroup(AuthUtil.getGroupName(entry.getKey()), entry.getValue());
239       } else {
240         newTablePerms.putUser(entry.getKey(), entry.getValue());
241       }
242     }
243 
244     tableCache.put(table, newTablePerms);
245     mtime++;
246   }
247 
248   /**
249    * Updates the internal permissions cache for a single table, splitting
250    * the permissions listed into separate caches for users and groups to optimize
251    * group lookups.
252    *
253    * @param namespace
254    * @param tablePerms
255    */
256   private void updateNsCache(String namespace,
257                              ListMultimap<String, TablePermission> tablePerms) {
258     PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
259 
260     for (Map.Entry<String, TablePermission> entry : tablePerms.entries()) {
261       if (AuthUtil.isGroupPrincipal(entry.getKey())) {
262         newTablePerms.putGroup(AuthUtil.getGroupName(entry.getKey()), entry.getValue());
263       } else {
264         newTablePerms.putUser(entry.getKey(), entry.getValue());
265       }
266     }
267 
268     nsCache.put(namespace, newTablePerms);
269     mtime++;
270   }
271 
272   private PermissionCache<TablePermission> getTablePermissions(TableName table) {
273     if (!tableCache.containsKey(table)) {
274       tableCache.putIfAbsent(table, new PermissionCache<TablePermission>());
275     }
276     return tableCache.get(table);
277   }
278 
279   private PermissionCache<TablePermission> getNamespacePermissions(String namespace) {
280     if (!nsCache.containsKey(namespace)) {
281       nsCache.putIfAbsent(namespace, new PermissionCache<TablePermission>());
282     }
283     return nsCache.get(namespace);
284   }
285 
286   /**
287    * Authorizes a global permission
288    * @param perms
289    * @param action
290    * @return true if authorized, false otherwise
291    */
292   private boolean authorize(List<Permission> perms, Permission.Action action) {
293     if (perms != null) {
294       for (Permission p : perms) {
295         if (p.implies(action)) {
296           return true;
297         }
298       }
299     } else if (LOG.isDebugEnabled()) {
300       LOG.debug("No permissions found for " + action);
301     }
302 
303     return false;
304   }
305 
306   /**
307    * Authorize a global permission based on ACLs for the given user and the
308    * user's groups.
309    * @param user
310    * @param action
311    * @return true if known and authorized, false otherwise
312    */
313   public boolean authorize(User user, Permission.Action action) {
314     if (user == null) {
315       return false;
316     }
317 
318     if (authorize(globalCache.getUser(user.getShortName()), action)) {
319       return true;
320     }
321 
322     String[] groups = user.getGroupNames();
323     if (groups != null) {
324       for (String group : groups) {
325         if (authorize(globalCache.getGroup(group), action)) {
326           return true;
327         }
328       }
329     }
330     return false;
331   }
332 
333   private boolean authorize(List<TablePermission> perms,
334                             TableName table, byte[] family,
335                             Permission.Action action) {
336     return authorize(perms, table, family, null, action);
337   }
338 
339   private boolean authorize(List<TablePermission> perms,
340                             TableName table, byte[] family,
341                             byte[] qualifier, Permission.Action action) {
342     if (perms != null) {
343       for (TablePermission p : perms) {
344         if (p.implies(table, family, qualifier, action)) {
345           return true;
346         }
347       }
348     } else if (LOG.isDebugEnabled()) {
349       LOG.debug("No permissions found for table="+table);
350     }
351     return false;
352   }
353 
354   private boolean hasAccess(List<TablePermission> perms,
355                             TableName table, Permission.Action action) {
356     if (perms != null) {
357       for (TablePermission p : perms) {
358         if (p.implies(action)) {
359           return true;
360         }
361       }
362     } else if (LOG.isDebugEnabled()) {
363       LOG.debug("No permissions found for table="+table);
364     }
365     return false;
366   }
367 
368   /**
369    * Authorize a user for a given KV. This is called from AccessControlFilter.
370    */
371   public boolean authorize(User user, TableName table, Cell cell, Permission.Action action) {
372     try {
373       List<Permission> perms = AccessControlLists.getCellPermissionsForUser(user, cell);
374       if (LOG.isTraceEnabled()) {
375         LOG.trace("Perms for user " + user.getShortName() + " in cell " + cell + ": " +
376           (perms != null ? perms : ""));
377       }
378       if (perms != null) {
379         for (Permission p: perms) {
380           if (p.implies(action)) {
381             return true;
382           }
383         }
384       }
385     } catch (IOException e) {
386       // We failed to parse the KV tag
387       LOG.error("Failed parse of ACL tag in cell " + cell);
388       // Fall through to check with the table and CF perms we were able
389       // to collect regardless
390     }
391     return false;
392   }
393 
394   public boolean authorize(User user, String namespace, Permission.Action action) {
395     // Global authorizations supercede namespace level
396     if (authorize(user, action)) {
397       return true;
398     }
399     // Check namespace permissions
400     PermissionCache<TablePermission> tablePerms = nsCache.get(namespace);
401     if (tablePerms != null) {
402       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
403       if (authorize(userPerms, namespace, action)) {
404         return true;
405       }
406       String[] groupNames = user.getGroupNames();
407       if (groupNames != null) {
408         for (String group : groupNames) {
409           List<TablePermission> groupPerms = tablePerms.getGroup(group);
410           if (authorize(groupPerms, namespace, action)) {
411             return true;
412           }
413         }
414       }
415     }
416     return false;
417   }
418 
419   private boolean authorize(List<TablePermission> perms, String namespace,
420                             Permission.Action action) {
421     if (perms != null) {
422       for (TablePermission p : perms) {
423         if (p.implies(namespace, action)) {
424           return true;
425         }
426       }
427     } else if (LOG.isDebugEnabled()) {
428       LOG.debug("No permissions for authorize() check, table=" + namespace);
429     }
430 
431     return false;
432   }
433 
434   /**
435    * Checks authorization to a given table and column family for a user, based on the
436    * stored user permissions.
437    *
438    * @param user
439    * @param table
440    * @param family
441    * @param action
442    * @return true if known and authorized, false otherwise
443    */
444   public boolean authorizeUser(User user, TableName table, byte[] family,
445       Permission.Action action) {
446     return authorizeUser(user, table, family, null, action);
447   }
448 
449   public boolean authorizeUser(User user, TableName table, byte[] family,
450       byte[] qualifier, Permission.Action action) {
451     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
452     // Global and namespace authorizations supercede table level
453     if (authorize(user, table.getNamespaceAsString(), action)) {
454       return true;
455     }
456     // Check table permissions
457     return authorize(getTablePermissions(table).getUser(user.getShortName()), table, family,
458         qualifier, action);
459   }
460 
461   /**
462    * Checks if the user has access to the full table or at least a family/qualifier
463    * for the specified action.
464    *
465    * @param user
466    * @param table
467    * @param action
468    * @return true if the user has access to the table, false otherwise
469    */
470   public boolean userHasAccess(User user, TableName table, Permission.Action action) {
471     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
472     // Global and namespace authorizations supercede table level
473     if (authorize(user, table.getNamespaceAsString(), action)) {
474       return true;
475     }
476     // Check table permissions
477     return hasAccess(getTablePermissions(table).getUser(user.getShortName()), table, action);
478   }
479 
480   /**
481    * Checks global authorization for a given action for a group, based on the stored
482    * permissions.
483    */
484   public boolean authorizeGroup(String groupName, Permission.Action action) {
485     List<Permission> perms = globalCache.getGroup(groupName);
486     if (LOG.isDebugEnabled()) {
487       LOG.debug("authorizing " + (perms != null && !perms.isEmpty() ? perms.get(0) : "") +
488         " for " + action);
489     }
490     return authorize(perms, action);
491   }
492 
493   /**
494    * Checks authorization to a given table, column family and column for a group, based
495    * on the stored permissions.
496    * @param groupName
497    * @param table
498    * @param family
499    * @param qualifier
500    * @param action
501    * @return true if known and authorized, false otherwise
502    */
503   public boolean authorizeGroup(String groupName, TableName table, byte[] family,
504       byte[] qualifier, Permission.Action action) {
505     // Global authorization supercedes table level
506     if (authorizeGroup(groupName, action)) {
507       return true;
508     }
509     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
510     // Namespace authorization supercedes table level
511     String namespace = table.getNamespaceAsString();
512     if (authorize(getNamespacePermissions(namespace).getGroup(groupName), namespace, action)) {
513       return true;
514     }
515     // Check table level
516     List<TablePermission> tblPerms = getTablePermissions(table).getGroup(groupName);
517     if (LOG.isDebugEnabled()) {
518       LOG.debug("authorizing " + (tblPerms != null && !tblPerms.isEmpty() ? tblPerms.get(0) : "") +
519         " for " +groupName + " on " + table + "." + Bytes.toString(family) + "." +
520         Bytes.toString(qualifier) + " with " + action);
521     }
522     return authorize(tblPerms, table, family, qualifier, action);
523   }
524 
525   /**
526    * Checks if the user has access to the full table or at least a family/qualifier
527    * for the specified action.
528    * @param groupName
529    * @param table
530    * @param action
531    * @return true if the group has access to the table, false otherwise
532    */
533   public boolean groupHasAccess(String groupName, TableName table, Permission.Action action) {
534     // Global authorization supercedes table level
535     if (authorizeGroup(groupName, action)) {
536       return true;
537     }
538     if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
539     // Namespace authorization supercedes table level
540     if (hasAccess(getNamespacePermissions(table.getNamespaceAsString()).getGroup(groupName),
541         table, action)) {
542       return true;
543     }
544     // Check table level
545     return hasAccess(getTablePermissions(table).getGroup(groupName), table, action);
546   }
547 
548   public boolean authorize(User user, TableName table, byte[] family,
549       byte[] qualifier, Permission.Action action) {
550     if (authorizeUser(user, table, family, qualifier, action)) {
551       return true;
552     }
553 
554     String[] groups = user.getGroupNames();
555     if (groups != null) {
556       for (String group : groups) {
557         if (authorizeGroup(group, table, family, qualifier, action)) {
558           return true;
559         }
560       }
561     }
562     return false;
563   }
564 
565   public boolean hasAccess(User user, TableName table, Permission.Action action) {
566     if (userHasAccess(user, table, action)) {
567       return true;
568     }
569 
570     String[] groups = user.getGroupNames();
571     if (groups != null) {
572       for (String group : groups) {
573         if (groupHasAccess(group, table, action)) {
574           return true;
575         }
576       }
577     }
578     return false;
579   }
580 
581   public boolean authorize(User user, TableName table, byte[] family,
582       Permission.Action action) {
583     return authorize(user, table, family, null, action);
584   }
585 
586   /**
587    * Returns true if the given user has a {@link TablePermission} matching up
588    * to the column family portion of a permission.  Note that this permission
589    * may be scoped to a given column qualifier and does not guarantee that
590    * authorize() on the same column family would return true.
591    */
592   public boolean matchPermission(User user,
593       TableName table, byte[] family, Permission.Action action) {
594     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
595     if (tablePerms != null) {
596       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
597       if (userPerms != null) {
598         for (TablePermission p : userPerms) {
599           if (p.matchesFamily(table, family, action)) {
600             return true;
601           }
602         }
603       }
604 
605       String[] groups = user.getGroupNames();
606       if (groups != null) {
607         for (String group : groups) {
608           List<TablePermission> groupPerms = tablePerms.getGroup(group);
609           if (groupPerms != null) {
610             for (TablePermission p : groupPerms) {
611               if (p.matchesFamily(table, family, action)) {
612                 return true;
613               }
614             }
615           }
616         }
617       }
618     }
619 
620     return false;
621   }
622 
623   public boolean matchPermission(User user,
624       TableName table, byte[] family, byte[] qualifier,
625       Permission.Action action) {
626     PermissionCache<TablePermission> tablePerms = tableCache.get(table);
627     if (tablePerms != null) {
628       List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
629       if (userPerms != null) {
630         for (TablePermission p : userPerms) {
631           if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
632             return true;
633           }
634         }
635       }
636 
637       String[] groups = user.getGroupNames();
638       if (groups != null) {
639         for (String group : groups) {
640           List<TablePermission> groupPerms = tablePerms.getGroup(group);
641           if (groupPerms != null) {
642             for (TablePermission p : groupPerms) {
643               if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
644                 return true;
645               }
646             }
647           }
648         }
649       }
650     }
651     return false;
652   }
653 
654   public void removeNamespace(byte[] ns) {
655     nsCache.remove(Bytes.toString(ns));
656   }
657 
658   public void removeTable(TableName table) {
659     tableCache.remove(table);
660   }
661 
662   /**
663    * Overwrites the existing permission set for a given user for a table, and
664    * triggers an update for zookeeper synchronization.
665    * @param username
666    * @param table
667    * @param perms
668    */
669   public void setTableUserPermissions(String username, TableName table,
670       List<TablePermission> perms) {
671     PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
672     tablePerms.replaceUser(username, perms);
673     writeTableToZooKeeper(table, tablePerms);
674   }
675 
676   /**
677    * Overwrites the existing permission set for a group and triggers an update
678    * for zookeeper synchronization.
679    * @param group
680    * @param table
681    * @param perms
682    */
683   public void setTableGroupPermissions(String group, TableName table,
684       List<TablePermission> perms) {
685     PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
686     tablePerms.replaceGroup(group, perms);
687     writeTableToZooKeeper(table, tablePerms);
688   }
689 
690   /**
691    * Overwrites the existing permission set for a given user for a table, and
692    * triggers an update for zookeeper synchronization.
693    * @param username
694    * @param namespace
695    * @param perms
696    */
697   public void setNamespaceUserPermissions(String username, String namespace,
698       List<TablePermission> perms) {
699     PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
700     tablePerms.replaceUser(username, perms);
701     writeNamespaceToZooKeeper(namespace, tablePerms);
702   }
703 
704   /**
705    * Overwrites the existing permission set for a group and triggers an update
706    * for zookeeper synchronization.
707    * @param group
708    * @param namespace
709    * @param perms
710    */
711   public void setNamespaceGroupPermissions(String group, String namespace,
712       List<TablePermission> perms) {
713     PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
714     tablePerms.replaceGroup(group, perms);
715     writeNamespaceToZooKeeper(namespace, tablePerms);
716   }
717 
718   public void writeTableToZooKeeper(TableName table,
719       PermissionCache<TablePermission> tablePerms) {
720     byte[] serialized = new byte[0];
721     if (tablePerms != null) {
722       serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
723     }
724     zkperms.writeToZookeeper(table.getName(), serialized);
725   }
726 
727   public void writeNamespaceToZooKeeper(String namespace,
728       PermissionCache<TablePermission> tablePerms) {
729     byte[] serialized = new byte[0];
730     if (tablePerms != null) {
731       serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
732     }
733     zkperms.writeToZookeeper(Bytes.toBytes(AccessControlLists.toNamespaceEntry(namespace)),
734         serialized);
735   }
736 
737   public long getMTime() {
738     return mtime;
739   }
740 
741   static Map<ZooKeeperWatcher,TableAuthManager> managerMap =
742     new HashMap<ZooKeeperWatcher,TableAuthManager>();
743 
744   public synchronized static TableAuthManager get(
745       ZooKeeperWatcher watcher, Configuration conf) throws IOException {
746     instance = managerMap.get(watcher);
747     if (instance == null) {
748       instance = new TableAuthManager(watcher, conf);
749       managerMap.put(watcher, instance);
750     }
751     return instance;
752   }
753 }