Skip to content

Commit

Permalink
[Refactor](auth)(step-2) Add AccessController to support customized a…
Browse files Browse the repository at this point in the history
…uthorization (#16802)

Support specifying AccessControllerFactory when creating catalog

create catalog hive properties(
...
"access_controller.class" = "org.apache.doris.mysql.privilege.RangerAccessControllerFactory",
"access_controller.properties.prop1" = "xxx",
"access_controller.properties.prop2" = "yyy",
...
)
So that user can specified their own access controller, such as RangerAccessController

Add interface to check column level privilege

A new method of CatalogAccessController: checkColsPriv(),
for checking column level privileges.

TODO:
Support grant column level privileges statements in Doris

Add TestExternalCatalog/Database/Table/ScanNode

These classes are used for FE unit test. In unit test you can

create catalog test1 properties(
    "type" = "test"
    "catalog_provider.class" = "org.apache.doris.datasource.ColumnPrivTest$MockedCatalogProvider"
    "access_controller.class" = "org.apache.doris.mysql.privilege.TestAccessControllerFactory",
    "access_controller.properties.key1" = "val1",
    "access_controller.properties.key2" = "val2"
);
To create a test catalog, and specify catalog_provider to mock database/table/schema metadata

Set roles in current user identity in connection context

The roles can be used for authorization in access controller.
  • Loading branch information
morningman authored Feb 20, 2023
1 parent 5291f14 commit 97230a5
Show file tree
Hide file tree
Showing 44 changed files with 1,217 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void analyze(Analyzer analyzer) throws UserException {
if (!Env.getCurrentEnv().getAccessManager().checkCtlPriv(
ConnectContext.get(), catalogName, PrivPredicate.DROP)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_CATALOG_ACCESS_DENIED,
analyzer.getQualifiedUser(), catalogName);
ConnectContext.get().getQualifiedUser(), catalogName);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ public void getTables(Analyzer analyzer, boolean expandView, Map<Long, TableIf>
.checkTblPriv(ConnectContext.get(), tblRef.getName(), PrivPredicate.SELECT)) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SELECT",
ConnectContext.get().getQualifiedUser(), ConnectContext.get().getRemoteIP(),
dbName + ": " + tableName);
dbName + "." + tableName);
}
tableMap.put(table.getId(), table);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ public class SlotDescriptor {
private ColumnStats stats; // only set if 'column' isn't set
private boolean isAgg;
private boolean isMultiRef;
// used for load to get more information of varchar and decimal
private Type originType;
// If set to false, then such slots will be ignored during
// materialize them.Used to optmize to read less data and less memory usage
private boolean needMaterialize = true;
Expand Down Expand Up @@ -162,7 +160,6 @@ public Column getColumn() {
public void setColumn(Column column) {
this.column = column;
this.type = column.getType();
this.originType = column.getOriginType();
}

public void setSrcColumn(Column column) {
Expand Down Expand Up @@ -254,10 +251,6 @@ public void setLabel(String label) {
this.label = label;
}

public void setSourceExprs(List<Expr> exprs) {
sourceExprs = exprs;
}

public void setSourceExpr(Expr expr) {
sourceExprs = Collections.singletonList(expr);
}
Expand Down Expand Up @@ -316,11 +309,9 @@ public boolean layoutEquals(SlotDescriptor other) {
return true;
}

// TODO
public TSlotDescriptor toThrift() {

TSlotDescriptor tSlotDescriptor = new TSlotDescriptor(id.asInt(), parent.getId().asInt(),
(originType != null ? originType.toThrift() : type.toThrift()), -1, byteOffset, nullIndicatorByte,
type.toThrift(), -1, byteOffset, nullIndicatorByte,
nullIndicatorBit, ((column != null) ? column.getName() : ""), slotIdx, isMaterialized);
tSlotDescriptor.setNeedMaterialize(needMaterialize);
if (column != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ public boolean equals(Object other) {
return false;
}

@Override
public int hashCode() {
return Objects.hash(ctl, tbl, db);
}

public String toSql() {
StringBuilder stringBuilder = new StringBuilder();
if (ctl != null && !ctl.equals(InternalCatalog.INTERNAL_CATALOG_NAME)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Set;

// https://dev.mysql.com/doc/refman/8.0/en/account-names.html
// user name must be literally matched.
Expand All @@ -52,12 +53,14 @@ public class UserIdentity implements Writable, GsonPostProcessable {

@SerializedName(value = "user")
private String user;

@SerializedName(value = "host")
private String host;

@SerializedName(value = "isDomain")
private boolean isDomain;
// The roles which this user belongs to.
// Used for authorization in Access Controller
// This field is only set when getting current user from auth and not need to persist
private Set<String> roles;

private boolean isAnalyzed = false;

Expand Down Expand Up @@ -125,6 +128,14 @@ public void setIsAnalyzed() {
this.isAnalyzed = true;
}

public void setRoles(Set<String> roles) {
this.roles = roles;
}

public Set<String> getRoles() {
return roles;
}

public void analyze(String clusterName) throws AnalysisException {
if (isAnalyzed) {
return;
Expand Down
4 changes: 4 additions & 0 deletions fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ void setQualifiedDbName(String qualifiedDbName) {
this.qualifiedDbName = qualifiedDbName;
}

public String getQualifiedDbName() {
return qualifiedDbName;
}

public String getQualifiedName() {
if (StringUtils.isEmpty(qualifiedDbName)) {
return name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ default int getBaseColumnIdxByName(String colName) {
enum TableType {
MYSQL, ODBC, OLAP, SCHEMA, INLINE_VIEW, VIEW, BROKER, ELASTICSEARCH, HIVE, ICEBERG, HUDI, JDBC,
TABLE_VALUED_FUNCTION, HMS_EXTERNAL_TABLE, ES_EXTERNAL_TABLE, MATERIALIZED_VIEW, JDBC_EXTERNAL_TABLE,
ICEBERG_EXTERNAL_TABLE;
ICEBERG_EXTERNAL_TABLE, TEST_EXTERNAL_TABLE;

public String toEngineName() {
switch (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public JdbcExternalTable(long id, String name, String dbName, JdbcExternalCatalo
super(id, name, catalog, dbName, TableType.JDBC_EXTERNAL_TABLE);
}

@Override
protected synchronized void makeSureInitialized() {
if (!objectCreated) {
jdbcTable = toJdbcTable();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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.doris.catalog.external;

import org.apache.doris.catalog.Env;
import org.apache.doris.datasource.ExternalCatalog;
import org.apache.doris.datasource.InitDatabaseLog;
import org.apache.doris.datasource.test.TestExternalCatalog;
import org.apache.doris.persist.gson.GsonPostProcessable;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestExternalDatabase extends ExternalDatabase<TestExternalTable> implements GsonPostProcessable {
private static final Logger LOG = LogManager.getLogger(TestExternalDatabase.class);

// Cache of table name to table id.
private Map<String, Long> tableNameToId = Maps.newConcurrentMap();
@SerializedName(value = "idToTbl")
private Map<Long, TestExternalTable> idToTbl = Maps.newConcurrentMap();

public TestExternalDatabase(ExternalCatalog extCatalog, long id, String name) {
super(extCatalog, id, name);
}

@Override
protected void init() {
InitDatabaseLog initDatabaseLog = new InitDatabaseLog();
initDatabaseLog.setType(InitDatabaseLog.Type.TEST);
initDatabaseLog.setCatalogId(extCatalog.getId());
initDatabaseLog.setDbId(id);
List<String> tableNames = extCatalog.listTableNames(null, name);
if (tableNames != null) {
Map<String, Long> tmpTableNameToId = Maps.newConcurrentMap();
Map<Long, TestExternalTable> tmpIdToTbl = Maps.newHashMap();
for (String tableName : tableNames) {
long tblId;
if (tableNameToId != null && tableNameToId.containsKey(tableName)) {
tblId = tableNameToId.get(tableName);
tmpTableNameToId.put(tableName, tblId);
TestExternalTable table = idToTbl.get(tblId);
tmpIdToTbl.put(tblId, table);
initDatabaseLog.addRefreshTable(tblId);
} else {
tblId = Env.getCurrentEnv().getNextId();
tmpTableNameToId.put(tableName, tblId);
TestExternalTable table = new TestExternalTable(tblId, tableName, name,
(TestExternalCatalog) extCatalog);
tmpIdToTbl.put(tblId, table);
initDatabaseLog.addCreateTable(tblId, tableName);
}
}
tableNameToId = tmpTableNameToId;
idToTbl = tmpIdToTbl;
}
initialized = true;
Env.getCurrentEnv().getEditLog().logInitExternalDb(initDatabaseLog);
}

public void setTableExtCatalog(ExternalCatalog extCatalog) {
for (TestExternalTable table : idToTbl.values()) {
table.setCatalog(extCatalog);
}
}

public void replayInitDb(InitDatabaseLog log, ExternalCatalog catalog) {
Map<String, Long> tmpTableNameToId = Maps.newConcurrentMap();
Map<Long, TestExternalTable> tmpIdToTbl = Maps.newConcurrentMap();
for (int i = 0; i < log.getRefreshCount(); i++) {
TestExternalTable table = getTableForReplay(log.getRefreshTableIds().get(i));
tmpTableNameToId.put(table.getName(), table.getId());
tmpIdToTbl.put(table.getId(), table);
}
for (int i = 0; i < log.getCreateCount(); i++) {
TestExternalTable table = new TestExternalTable(log.getCreateTableIds().get(i),
log.getCreateTableNames().get(i), name, (TestExternalCatalog) catalog);
tmpTableNameToId.put(table.getName(), table.getId());
tmpIdToTbl.put(table.getId(), table);
}
tableNameToId = tmpTableNameToId;
idToTbl = tmpIdToTbl;
initialized = true;
}

// TODO(ftw): drew
@Override
public Set<String> getTableNamesWithLock() {
makeSureInitialized();
return Sets.newHashSet(tableNameToId.keySet());
}

@Override
public List<TestExternalTable> getTables() {
makeSureInitialized();
return Lists.newArrayList(idToTbl.values());
}

@Override
public TestExternalTable getTableNullable(String tableName) {
makeSureInitialized();
if (!tableNameToId.containsKey(tableName)) {
return null;
}
return idToTbl.get(tableNameToId.get(tableName));
}

@Override
public TestExternalTable getTableNullable(long tableId) {
makeSureInitialized();
return idToTbl.get(tableId);
}

public TestExternalTable getTableForReplay(long tableId) {
return idToTbl.get(tableId);
}

@Override
public void gsonPostProcess() throws IOException {
tableNameToId = Maps.newConcurrentMap();
for (TestExternalTable tbl : idToTbl.values()) {
tableNameToId.put(tbl.getName(), tbl.getId());
}
rwLock = new ReentrantReadWriteLock(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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.doris.catalog.external;

import org.apache.doris.catalog.Column;
import org.apache.doris.datasource.test.TestExternalCatalog;
import org.apache.doris.thrift.TTableDescriptor;
import org.apache.doris.thrift.TTableType;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;

/**
* TestExternalTable is a table for unit test.
*/
public class TestExternalTable extends ExternalTable {
private static final Logger LOG = LogManager.getLogger(TestExternalTable.class);

public TestExternalTable(long id, String name, String dbName, TestExternalCatalog catalog) {
super(id, name, catalog, dbName, TableType.TEST_EXTERNAL_TABLE);
}

@Override
protected synchronized void makeSureInitialized() {

}

@Override
public String getMysqlType() {
return type.name();
}

@Override
public TTableDescriptor toThrift() {
makeSureInitialized();
TTableDescriptor tTableDescriptor = new TTableDescriptor(getId(), TTableType.TEST_EXTERNAL_TABLE,
getFullSchema().size(),
0, getName(), "");
return tTableDescriptor;
}

@Override
public List<Column> initSchema() {
return ((TestExternalCatalog) catalog).mockedSchema(dbName, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.doris.common;

/**
* Thrown for authorization errors encountered when accessing Catalog objects.
*/
public class AuthorizationException extends UserException {

public ErrorCode errorCode = ErrorCode.ERR_COMMON_ERROR;
public Object[] msgs;

public AuthorizationException(String msg, Throwable cause) {
super(msg, cause);
}

public AuthorizationException(String msg) {
super(msg);
}

public AuthorizationException(ErrorCode code, Object... msgs) {
super(code.formatErrorMsg(msgs));
this.errorCode = code;
this.msgs = msgs;
}

public String formatErrMsg() {
return errorCode.formatErrorMsg(msgs);
}
}
Loading

0 comments on commit 97230a5

Please sign in to comment.