IMPALA-9222: Speed up show tables/DBs if the user has access to parent db/server

Currently we always do the auth check for tables/dbs individually.
If the user has privileges higher in the hierarchy then it is not
necessary to do these checks as they will all succeed.

This change adds a higher level check before the individual checks.
The optimization is only enabled for Sentry, as Ranger has deny
policies, so server/db level access does not guarantee db/table level
access.

Testing:
- the existing auth related test coverage seems enough
- there are no tests for deny policies yet - adding them seems a bigger
  task so I created a follow up jira: IMPALA-9252

Change-Id: Ic1f5c5d1cf447a9f1cec46c45272f250b8580826
Reviewed-on: http://gerrit.cloudera.org:8080/14867
Reviewed-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
Tested-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
diff --git a/fe/src/main/java/org/apache/impala/service/Frontend.java b/fe/src/main/java/org/apache/impala/service/Frontend.java
index f01d787..fdce34f 100644
--- a/fe/src/main/java/org/apache/impala/service/Frontend.java
+++ b/fe/src/main/java/org/apache/impala/service/Frontend.java
@@ -868,7 +868,11 @@
       User user) throws ImpalaException {
     FeCatalog catalog = getCatalog();
     List<String> tblNames = catalog.getTableNames(dbName, matcher);
-    if (authzFactory_.getAuthorizationConfig().isEnabled()) {
+
+    boolean needsAuthChecks = authzFactory_.getAuthorizationConfig().isEnabled()
+                              && !userHasAccessForWholeDb(user, dbName);
+
+    if (needsAuthChecks) {
       List<Future<Boolean>> pendingCheckTasks = Lists.newArrayList();
       Iterator<String> iter = tblNames.iterator();
       while (iter.hasNext()) {
@@ -1010,9 +1014,12 @@
   public List<? extends FeDb> getDbs(PatternMatcher matcher, User user)
       throws InternalException {
     List<? extends FeDb> dbs = getCatalog().getDbs(matcher);
-    // If authorization is enabled, filter out the databases the user does not
-    // have permissions on.
-    if (authzFactory_.getAuthorizationConfig().isEnabled()) {
+
+    boolean needsAuthChecks = authzFactory_.getAuthorizationConfig().isEnabled()
+                              && !userHasAccessForWholeServer(user);
+
+    // Filter out the databases the user does not have permissions on.
+    if (needsAuthChecks) {
       Iterator<? extends FeDb> iter = dbs.iterator();
       List<Future<Boolean>> pendingCheckTasks = Lists.newArrayList();
       while (iter.hasNext()) {
@@ -1040,8 +1047,8 @@
     }
 
     PrivilegeRequestBuilder builder = new PrivilegeRequestBuilder(
-      authzFactory_.getAuthorizableFactory())
-      .anyOf(minPrivilegeSetForShowStmts_);
+        authzFactory_.getAuthorizableFactory())
+        .anyOf(minPrivilegeSetForShowStmts_);
     if (tblName == null) {
       // Check database
       builder = builder.onAnyColumn(dbName, owner);
@@ -1054,6 +1061,46 @@
   }
 
   /**
+   * Check whether the whole server is accessible to given user.
+   */
+  private boolean userHasAccessForWholeServer(User user)
+      throws InternalException {
+    if (authEngineSupportsDenyRules()) return false;
+    PrivilegeRequestBuilder builder = new PrivilegeRequestBuilder(
+        authzFactory_.getAuthorizableFactory()).anyOf(minPrivilegeSetForShowStmts_)
+        .onServer(authzFactory_.getAuthorizationConfig().getServerName());
+    return authzChecker_.get().hasAnyAccess(user, builder.buildSet());
+  }
+
+  /**
+   * Check whether the whole database is accessible to given user.
+   */
+  private boolean userHasAccessForWholeDb(User user, String dbName)
+      throws InternalException, DatabaseNotFoundException {
+    if (authEngineSupportsDenyRules()) return false;
+    // FeDb is needed to respect ownership in Ranger, dbName would be enough for
+    // the privilege request otherwise.
+    FeDb db = getCatalog().getDb(dbName);
+    if (db == null) {
+      throw new DatabaseNotFoundException("Database '" + dbName + "' not found");
+    }
+    PrivilegeRequestBuilder builder = new PrivilegeRequestBuilder(
+        authzFactory_.getAuthorizableFactory()).anyOf(minPrivilegeSetForShowStmts_)
+        .onDb(db);
+    return authzChecker_.get().hasAnyAccess(user, builder.buildSet());
+  }
+
+  /**
+   * Returns whether the authorization engine supports deny rules. If it does,
+   * then a privilege on a higher level object does not imply privilege on lower
+   * level objects in the hierarchy.
+   */
+  private boolean authEngineSupportsDenyRules() {
+    // TODO: could check config for Ranger and return true if deny rules are disabled
+    return !authzFactory_.getAuthorizationConfig().getProviderName().equals("sentry");
+  }
+
+  /**
    * Returns all data sources that match the pattern. If pattern is null,
    * matches all data sources.
    */