blob: 41a96a005b5667ab6a0398b56abe3de19628c703 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.impala.analysis;
import static org.apache.impala.analysis.ToSqlOptions.DEFAULT;
import java.util.ArrayList;
import java.util.List;
import org.apache.impala.authorization.Privilege;
import org.apache.impala.catalog.FeDataSourceTable;
import org.apache.impala.catalog.FeTable;
import org.apache.impala.catalog.FeView;
import org.apache.impala.catalog.TableLoadingException;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.thrift.TPrivilege;
import org.apache.impala.thrift.TPrivilegeLevel;
import org.apache.impala.thrift.TPrivilegeScope;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
/**
* Represents a privilege spec from a GRANT/REVOKE statement.
* A privilege spec may correspond to one or more privileges. Currently, a privilege spec
* can represent multiple privileges only at the COLUMN scope.
*/
public class PrivilegeSpec extends StmtNode {
private final TPrivilegeScope scope_;
private final TPrivilegeLevel privilegeLevel_;
private final TableName tableName_;
private final HdfsUri uri_;
private final List<String> columnNames_;
// Set/modified during analysis
private String dbName_;
private String serverName_;
private PrivilegeSpec(TPrivilegeLevel privilegeLevel, TPrivilegeScope scope,
String serverName, String dbName, TableName tableName, HdfsUri uri,
List<String> columnNames) {
Preconditions.checkNotNull(scope);
Preconditions.checkNotNull(privilegeLevel);
privilegeLevel_ = privilegeLevel;
scope_ = scope;
serverName_ = serverName;
tableName_ = tableName;
dbName_ = (tableName_ != null ? tableName_.getDb() : dbName);
uri_ = uri;
columnNames_ = columnNames;
}
public static PrivilegeSpec createServerScopedPriv(TPrivilegeLevel privilegeLevel) {
return createServerScopedPriv(privilegeLevel, null);
}
public static PrivilegeSpec createServerScopedPriv(TPrivilegeLevel privilegeLevel,
String serverName) {
return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.SERVER, serverName, null,
null, null, null);
}
public static PrivilegeSpec createDbScopedPriv(TPrivilegeLevel privilegeLevel,
String dbName) {
Preconditions.checkNotNull(dbName);
return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.DATABASE, null, dbName,
null, null, null);
}
public static PrivilegeSpec createTableScopedPriv(TPrivilegeLevel privilegeLevel,
TableName tableName) {
Preconditions.checkNotNull(tableName);
return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.TABLE, null, null, tableName,
null, null);
}
public static PrivilegeSpec createColumnScopedPriv(TPrivilegeLevel privilegeLevel,
TableName tableName, List<String> columnNames) {
Preconditions.checkNotNull(tableName);
Preconditions.checkNotNull(columnNames);
return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.COLUMN, null, null,
tableName, null, columnNames);
}
public static PrivilegeSpec createUriScopedPriv(TPrivilegeLevel privilegeLevel,
HdfsUri uri) {
Preconditions.checkNotNull(uri);
return new PrivilegeSpec(privilegeLevel, TPrivilegeScope.URI, null, null, null, uri,
null);
}
public List<TPrivilege> toThrift() {
List<TPrivilege> privileges = new ArrayList<>();
if (scope_ == TPrivilegeScope.COLUMN) {
// Create a TPrivilege for every referenced column
for (String column: columnNames_) {
privileges.add(createTPrivilege(column));
}
} else {
privileges.add(createTPrivilege(null));
}
return privileges;
}
/**
* Helper function to construct a TPrivilege from this privilege spec. If the scope is
* COLUMN, 'columnName' must be a non-null column name. Otherwise, 'columnName' is
* null.
*/
private TPrivilege createTPrivilege(String columnName) {
Preconditions.checkState(columnName == null ^ scope_ == TPrivilegeScope.COLUMN);
TPrivilege privilege = new TPrivilege();
privilege.setScope(scope_);
privilege.setServer_name(serverName_);
// We don't currently filter on privilege level, so set it to an arbitrary value.
privilege.setPrivilege_level(privilegeLevel_);
if (dbName_ != null) privilege.setDb_name(dbName_);
if (tableName_ != null) privilege.setTable_name(tableName_.getTbl());
if (uri_ != null) privilege.setUri(uri_.toString());
if (columnName != null) privilege.setColumn_name(columnName);
privilege.setCreate_time_ms(-1);
return privilege;
}
/**
* Return the table path of a COLUMN level privilege. The table path consists
* of server name, database name and table name.
*/
public static String getTablePath(TPrivilege privilege) {
Preconditions.checkState(privilege.getScope() == TPrivilegeScope.COLUMN);
Joiner joiner = Joiner.on(".");
return joiner.join(privilege.getServer_name(), privilege.getDb_name(),
privilege.getTable_name());
}
@Override
public final String toSql() {
return toSql(DEFAULT);
}
@Override
public String toSql(ToSqlOptions options) {
StringBuilder sb = new StringBuilder(privilegeLevel_.toString());
if (scope_ != TPrivilegeScope.COLUMN) {
sb.append(" ON ");
sb.append(scope_.toString());
}
if (scope_ == TPrivilegeScope.SERVER && serverName_ != null) {
sb.append(" " + serverName_);
} else if (scope_ == TPrivilegeScope.DATABASE) {
sb.append(" " + dbName_);
} else if (scope_ == TPrivilegeScope.TABLE) {
sb.append(" " + tableName_.toString());
} else if (scope_ == TPrivilegeScope.COLUMN) {
sb.append(" (");
sb.append(Joiner.on(",").join(columnNames_));
sb.append(")");
sb.append(" ON TABLE " + tableName_.toString());
} else if (scope_ == TPrivilegeScope.URI) {
sb.append(" '" + uri_.getLocation() + "'");
}
return sb.toString();
}
public void collectTableRefs(List<TableRef> tblRefs) {
if (tableName_ != null) tblRefs.add(new TableRef(tableName_.toPath(), null));
}
@Override
public void analyze(Analyzer analyzer) throws AnalysisException {
String configServerName = analyzer.getAuthzConfig().getServerName();
if (serverName_ != null && !serverName_.equals(configServerName)) {
throw new AnalysisException(String.format("Specified server name '%s' does not " +
"match the configured server name '%s'", serverName_, configServerName));
}
serverName_ = configServerName;
Preconditions.checkState(!Strings.isNullOrEmpty(serverName_));
Preconditions.checkNotNull(scope_);
switch (scope_) {
case SERVER:
break;
case DATABASE:
Preconditions.checkState(!Strings.isNullOrEmpty(dbName_));
try {
analyzer.getDb(dbName_, true);
} catch (AnalysisException e) {
throw new AnalysisException(String.format("Error setting/showing privileges " +
"for database '%s'. Verify that the database exists and that you have " +
"permissions to issue a GRANT/REVOKE/SHOW GRANT statement.", dbName_));
}
break;
case URI:
Preconditions.checkNotNull(uri_);
if (privilegeLevel_ != TPrivilegeLevel.ALL) {
throw new AnalysisException("Only 'ALL' privilege may be applied at " +
"URI scope in privilege spec.");
}
uri_.analyze(analyzer, Privilege.ALL, false);
break;
case TABLE:
analyzeTargetTable(analyzer);
break;
case COLUMN:
analyzeColumnPrivScope(analyzer);
break;
default:
throw new IllegalStateException("Unknown TPrivilegeScope in privilege spec: " +
scope_.toString());
}
}
/**
* Analyzes a privilege spec at the COLUMN scope.
* Throws an AnalysisException in the following cases:
* 1. No columns are specified.
* 2. Privilege is applied on a view or an external data source.
* 3. Referenced table and/or columns do not exist.
* 4. Privilege level is not SELECT.
*/
private void analyzeColumnPrivScope(Analyzer analyzer) throws AnalysisException {
Preconditions.checkState(scope_ == TPrivilegeScope.COLUMN);
Preconditions.checkNotNull(columnNames_);
if (columnNames_.isEmpty()) {
throw new AnalysisException("Empty column list in column privilege spec.");
}
if (privilegeLevel_ != TPrivilegeLevel.SELECT) {
throw new AnalysisException("Only 'SELECT' privileges are allowed " +
"in a column privilege spec.");
}
FeTable table = analyzeTargetTable(analyzer);
if (table instanceof FeDataSourceTable) {
throw new AnalysisException("Column-level privileges on external data " +
"source tables are not supported.");
}
for (String columnName: columnNames_) {
if (table.getColumn(columnName) == null) {
// The error message should not reveal the existence or absence of a column.
throw new AnalysisException(String.format("Error setting/showing column-level " +
"privileges for table '%s'. Verify that both table and columns exist " +
"and that you have permissions to issue a GRANT/REVOKE/SHOW GRANT statement.",
tableName_.toString()));
}
}
}
/**
* Verifies that the table referenced in the privilege spec exists in the catalog and
* returns the catalog object.
* Throws an AnalysisException in the following cases:
* 1. The table name is not valid.
* 2. Table is not loaded in the catalog.
* 3. Table does not exist.
* 4. The privilege level is not supported on tables, e.g. CREATE.
*/
private FeTable analyzeTargetTable(Analyzer analyzer) throws AnalysisException {
Preconditions.checkState(scope_ == TPrivilegeScope.TABLE ||
scope_ == TPrivilegeScope.COLUMN);
Preconditions.checkState(!Strings.isNullOrEmpty(tableName_.getTbl()));
if (privilegeLevel_ == TPrivilegeLevel.CREATE) {
throw new AnalysisException("Create-level privileges on tables are not supported.");
}
FeTable table = null;
try {
dbName_ = analyzer.getTargetDbName(tableName_);
Preconditions.checkNotNull(dbName_);
table = analyzer.getTable(dbName_, tableName_.getTbl());
} catch (TableLoadingException e) {
throw new AnalysisException(e.getMessage(), e);
} catch (AnalysisException e) {
throw new AnalysisException(String.format("Error setting/showing privileges for " +
"table '%s'. Verify that the table exists and that you have permissions " +
"to issue a GRANT/REVOKE/SHOW GRANT statement.", tableName_.toString()));
}
Preconditions.checkNotNull(table);
return table;
}
public TPrivilegeScope getScope() { return scope_; }
public TableName getTableName() { return tableName_; }
public HdfsUri getUri() { return uri_; }
public String getDbName() { return dbName_; }
public String getServerName() { return serverName_; }
}