This is an automated email from the ASF dual-hosted git repository.
HTHou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 17e4efb6922 Record audit logs when revoke failed (#18352)
17e4efb6922 is described below
commit 17e4efb6922c2604f5f30a72a81978e63f71edda
Author: libo <[email protected]>
AuthorDate: Thu Jul 30 12:05:30 2026 +0800
Record audit logs when revoke failed (#18352)
* Record audit logs when revoke failed
* Move public logic of revoke audit log to DNAuditLogger
* Avoid null session access in revoke audit logging
---
.../rest/protocol/filter/AuthorizationFilter.java | 7 +-
.../protocol/handler/AuthorizationHandler.java | 16 +-
.../rest/protocol/v1/impl/RestApiServiceImpl.java | 3 +-
.../rest/protocol/v2/impl/RestApiServiceImpl.java | 3 +-
.../org/apache/iotdb/db/audit/DNAuditLogger.java | 129 +++++++++++++++
.../org/apache/iotdb/db/auth/AuthorityChecker.java | 27 +--
.../db/protocol/session/RestClientSession.java | 6 +-
.../protocol/thrift/impl/ClientRPCServiceImpl.java | 10 +-
.../iotdb/db/queryengine/plan/Coordinator.java | 77 +++++----
.../db/audit/DNAuditLoggerRevokeFailureTest.java | 183 +++++++++++++++++++++
10 files changed, 412 insertions(+), 49 deletions(-)
diff --git
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java
index a7bb35e3b2e..332370ca047 100644
---
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java
+++
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java
@@ -29,10 +29,12 @@ import org.apache.iotdb.rest.protocol.model.ExecutionStatus;
import org.apache.iotdb.rpc.TSStatusCode;
import jakarta.servlet.annotation.WebFilter;
+import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
+import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
@@ -52,6 +54,8 @@ public class AuthorizationFilter implements
ContainerRequestFilter, ContainerRes
private static final SessionManager SESSION_MANAGER =
SessionManager.getInstance();
+ @Context private HttpServletRequest servletRequest;
+
public AuthorizationFilter() throws AuthException {
// do nothing
}
@@ -97,7 +101,8 @@ public class AuthorizationFilter implements
ContainerRequestFilter, ContainerRes
String sessionid = UUID.randomUUID().toString();
if (SESSION_MANAGER.getCurrSession() == null) {
- RestClientSession restClientSession = new RestClientSession(sessionid);
+ String clientAddress = servletRequest == null ? sessionid :
servletRequest.getRemoteAddr();
+ RestClientSession restClientSession = new RestClientSession(sessionid,
clientAddress);
restClientSession.setUsername(user.getUsername());
SESSION_MANAGER.registerSession(restClientSession);
SESSION_MANAGER.supplySession(
diff --git
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/handler/AuthorizationHandler.java
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/handler/AuthorizationHandler.java
index 4344554bce6..65967bde0e6 100644
---
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/handler/AuthorizationHandler.java
+++
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/handler/AuthorizationHandler.java
@@ -18,8 +18,10 @@
package org.apache.iotdb.rest.protocol.handler;
import org.apache.iotdb.common.rpc.thrift.TSStatus;
-import org.apache.iotdb.commons.audit.UserEntity;
import org.apache.iotdb.db.auth.AuthorityChecker;
+import org.apache.iotdb.db.protocol.session.IClientSession;
+import org.apache.iotdb.db.protocol.session.SessionManager;
+import
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext;
import org.apache.iotdb.db.queryengine.plan.statement.Statement;
import org.apache.iotdb.rest.protocol.model.ExecutionStatus;
import org.apache.iotdb.rpc.TSStatusCode;
@@ -30,10 +32,20 @@ import jakarta.ws.rs.core.SecurityContext;
public class AuthorizationHandler {
public Response checkAuthority(SecurityContext securityContext, Statement
statement) {
+ return checkAuthority(securityContext, statement, null);
+ }
+
+ public Response checkAuthority(
+ SecurityContext securityContext, Statement statement, String sqlString) {
String userName = securityContext.getUserPrincipal().getName();
long userId = AuthorityChecker.getUserId(userName).orElse(-1L);
+ IClientSession clientSession =
SessionManager.getInstance().getCurrSession();
TSStatus status =
- AuthorityChecker.checkAuthority(statement, new UserEntity(userId,
userName, ""));
+ AuthorityChecker.checkAuthority(
+ statement,
+ new TreeAccessCheckContext(
+ userId, userName, clientSession == null ? "" :
clientSession.getClientAddress())
+ .setSqlString(sqlString));
if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
return Response.ok()
.entity(new
ExecutionStatus().code(status.getCode()).message(status.getMessage()))
diff --git
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java
index a5d7302920d..de3e6e682e8 100644
---
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java
+++
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java
@@ -125,7 +125,8 @@ public class RestApiServiceImpl extends RestApiService {
config.getQueryTimeoutThreshold(),
false);
} else {
- Response response =
authorizationHandler.checkAuthority(securityContext, statement);
+ Response response =
+ authorizationHandler.checkAuthority(securityContext, statement,
sql.getSql());
if (response != null) {
return response;
}
diff --git
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java
index f6a2ef7b54c..39770ace3b0 100644
---
a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java
+++
b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java
@@ -267,7 +267,8 @@ public class RestApiServiceImpl extends RestApiService {
.build();
}
- Response response = authorizationHandler.checkAuthority(securityContext,
statement);
+ Response response =
+ authorizationHandler.checkAuthority(securityContext, statement,
sql.getSql());
if (response != null) {
return response;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/DNAuditLogger.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/DNAuditLogger.java
index 1f2b54d6d34..02fedbd5e1f 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/DNAuditLogger.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/audit/DNAuditLogger.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.audit;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.commons.audit.AbstractAuditLogger;
import org.apache.iotdb.commons.audit.AuditEventType;
import org.apache.iotdb.commons.audit.AuditLogFields;
@@ -28,9 +29,16 @@ import org.apache.iotdb.commons.audit.PrivilegeLevel;
import org.apache.iotdb.commons.auth.entity.PrivilegeType;
import org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.common.SessionInfo;
import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
import org.apache.iotdb.db.queryengine.plan.Coordinator;
+import
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement;
+import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType;
+import org.apache.iotdb.db.queryengine.plan.statement.AuthorType;
+import org.apache.iotdb.db.queryengine.plan.statement.Statement;
import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement;
+import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement;
+import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.tsfile.common.conf.TSFileConfig;
import org.apache.tsfile.enums.TSDataType;
@@ -38,6 +46,8 @@ import org.apache.tsfile.utils.Binary;
import jakarta.validation.constraints.NotNull;
+import javax.annotation.Nullable;
+
import java.util.Arrays;
import java.util.function.Supplier;
import java.util.regex.Matcher;
@@ -191,6 +201,125 @@ public class DNAuditLogger extends AbstractAuditLogger {
public void logFromCN(AuditLogFields auditLogFields, String log, int nodeId)
throws IllegalPathException {}
+ public void logRevokeFailure(
+ Statement statement, IAuditEntity auditEntity, @Nullable TSStatus
status) {
+ logRevokeFailure(
+ getTargetName(statement),
+ auditEntity.getUserId(),
+ auditEntity.getUsername(),
+ auditEntity.getCliHostname(),
+ auditEntity.getDatabase(),
+ auditEntity.getSqlString(),
+ status);
+ }
+
+ public void logRevokeFailure(
+ Statement statement,
+ SessionInfo sessionInfo,
+ @Nullable String sql,
+ @Nullable TSStatus status) {
+ logRevokeFailure(getTargetName(statement), sessionInfo, sql, status);
+ }
+
+ public void logRevokeFailure(
+ org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement
statement,
+ SessionInfo sessionInfo,
+ @Nullable String sql,
+ @Nullable TSStatus status) {
+ logRevokeFailure(getTargetName(statement), sessionInfo, sql, status);
+ }
+
+ private void logRevokeFailure(
+ @Nullable String targetName,
+ @Nullable SessionInfo sessionInfo,
+ @Nullable String sql,
+ @Nullable TSStatus status) {
+ if (targetName == null || isSuccessful(status) || sessionInfo == null) {
+ return;
+ }
+ logRevokeFailure(
+ targetName,
+ sessionInfo.getUserId(),
+ sessionInfo.getUserName(),
+ sessionInfo.getCliHostname(),
+ sessionInfo.getDatabaseName().orElse(null),
+ sql,
+ status);
+ }
+
+ private void logRevokeFailure(
+ @Nullable String targetName,
+ long userId,
+ String username,
+ String clientAddress,
+ @Nullable String database,
+ @Nullable String sql,
+ @Nullable TSStatus status) {
+ if (targetName == null || isSuccessful(status)) {
+ return;
+ }
+ log(
+ new AuditLogFields(
+ userId,
+ username,
+ clientAddress,
+ AuditEventType.REVOKE_FAILED,
+ AuditLogOperation.CONTROL,
+ PrivilegeType.SECURITY,
+ false,
+ database,
+ sql),
+ () -> targetName);
+ }
+
+ @Nullable
+ private static String getTargetName(Statement statement) {
+ if (!(statement instanceof AuthorStatement)) {
+ return null;
+ }
+ AuthorStatement authorStatement = (AuthorStatement) statement;
+ if (authorStatement.getAuthorType() == AuthorType.REVOKE_USER
+ || authorStatement.getAuthorType() == AuthorType.REVOKE_USER_ROLE) {
+ return authorStatement.getUserName();
+ }
+ if (authorStatement.getAuthorType() == AuthorType.REVOKE_ROLE) {
+ return authorStatement.getRoleName();
+ }
+ return null;
+ }
+
+ @Nullable
+ private static String getTargetName(
+ org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement
statement) {
+ if (!(statement instanceof RelationalAuthorStatement)) {
+ return null;
+ }
+ RelationalAuthorStatement authorStatement = (RelationalAuthorStatement)
statement;
+ AuthorRType type = authorStatement.getAuthorType();
+ if (type == AuthorRType.REVOKE_USER_ANY
+ || type == AuthorRType.REVOKE_USER_ALL
+ || type == AuthorRType.REVOKE_USER_DB
+ || type == AuthorRType.REVOKE_USER_TB
+ || type == AuthorRType.REVOKE_USER_SYS
+ || type == AuthorRType.REVOKE_USER_ROLE) {
+ return authorStatement.getUserName();
+ }
+ if (type == AuthorRType.REVOKE_ROLE_ANY
+ || type == AuthorRType.REVOKE_ROLE_ALL
+ || type == AuthorRType.REVOKE_ROLE_DB
+ || type == AuthorRType.REVOKE_ROLE_TB
+ || type == AuthorRType.REVOKE_ROLE_SYS) {
+ return authorStatement.getRoleName();
+ }
+ return null;
+ }
+
+ private static boolean isSuccessful(@Nullable TSStatus status) {
+ return status != null
+ && (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()
+ || status.getCode() ==
TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode());
+ }
+
private static class DNAuditLoggerHolder {
private static final DNAuditLogger INSTANCE = new DNAuditLogger();
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
index 646f093ba57..fe8748ba4c3 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
@@ -39,6 +39,7 @@ import org.apache.iotdb.confignode.rpc.thrift.TPathPrivilege;
import org.apache.iotdb.confignode.rpc.thrift.TRoleResp;
import org.apache.iotdb.confignode.rpc.thrift.TTablePrivilege;
import org.apache.iotdb.confignode.rpc.thrift.TUserResp;
+import org.apache.iotdb.db.audit.DNAuditLogger;
import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeInsertionDataNodeListener;
import org.apache.iotdb.db.protocol.session.IClientSession;
@@ -193,21 +194,27 @@ public class AuthorityChecker {
public static TSStatus checkAuthority(Statement statement, IAuditEntity
auditEntity) {
long startTime = System.nanoTime();
+ TSStatus status = null;
try {
if (auditEntity instanceof TreeAccessCheckContext) {
- return accessControl.checkPermissionBeforeProcess(
- statement, (TreeAccessCheckContext) auditEntity);
+ status =
+ accessControl.checkPermissionBeforeProcess(
+ statement, (TreeAccessCheckContext) auditEntity);
+ } else {
+ status =
+ accessControl.checkPermissionBeforeProcess(
+ statement,
+ (TreeAccessCheckContext)
+ new TreeAccessCheckContext(
+ auditEntity.getUserId(),
+ auditEntity.getUsername(),
+ auditEntity.getCliHostname())
+ .setSqlString(auditEntity.getSqlString()));
}
- return accessControl.checkPermissionBeforeProcess(
- statement,
- (TreeAccessCheckContext)
- new TreeAccessCheckContext(
- auditEntity.getUserId(),
- auditEntity.getUsername(),
- auditEntity.getCliHostname())
- .setSqlString(auditEntity.getSqlString()));
+ return status;
} finally {
PERFORMANCE_OVERVIEW_METRICS.recordAuthCost(System.nanoTime() -
startTime);
+ DNAuditLogger.getInstance().logRevokeFailure(statement, auditEntity,
status);
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/RestClientSession.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/RestClientSession.java
index d122c3c7dc5..58bae05fffe 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/RestClientSession.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/RestClientSession.java
@@ -29,17 +29,19 @@ import java.util.concurrent.ConcurrentHashMap;
public class RestClientSession extends IClientSession {
private final String clientID;
+ private final String clientAddress;
// Map from statement name to PreparedStatementInfo
private final Map<String, PreparedStatementInfo> preparedStatements = new
ConcurrentHashMap<>();
- public RestClientSession(String clientID) {
+ public RestClientSession(String clientID, String clientAddress) {
this.clientID = clientID;
+ this.clientAddress = clientAddress;
}
@Override
public String getClientAddress() {
- return clientID;
+ return clientAddress;
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
index ea67ebb8523..1752c8acf76 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
@@ -3710,7 +3710,10 @@ public class ClientRPCServiceImpl implements
IClientRPCServiceWithHandler {
queryId);
}
- if (result != null && result.status.getCode() ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ if (result == null) {
+ throw new IllegalStateException();
+ }
+ if (result.status.getCode() ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
LOGGER.info(DataNodeMiscMessages.COMPLETED_BATCH_EXECUTING_TREE,
totalSubStatements, queryId);
}
@@ -3792,7 +3795,10 @@ public class ClientRPCServiceImpl implements
IClientRPCServiceWithHandler {
queryId);
}
- if (result != null && result.status.getCode() ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ if (result == null) {
+ throw new IllegalStateException();
+ }
+ if (result.status.getCode() ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
LOGGER.info(
DataNodeMiscMessages.COMPLETED_BATCH_EXECUTING_TABLE,
totalSubStatements, queryId);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
index a7edb91c5cf..8919182c7ce 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
@@ -42,6 +42,7 @@ import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Query;
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Table;
import
org.apache.iotdb.commons.queryengine.plan.relational.type.InternalTypeManager;
import org.apache.iotdb.commons.queryengine.plan.relational.type.TypeManager;
+import org.apache.iotdb.db.audit.DNAuditLogger;
import org.apache.iotdb.db.auth.AuthorityChecker;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -393,20 +394,28 @@ public class Coordinator {
long timeOut,
boolean userQuery,
boolean debug) {
- return execution(
- queryId,
- session,
- sql,
- userQuery,
- debug,
- ((queryContext, startTime) ->
- createQueryExecutionForTreeModel(
- statement,
- queryContext,
- partitionFetcher,
- schemaFetcher,
- timeOut > 0 ? timeOut : CONFIG.getQueryTimeoutThreshold(),
- startTime)));
+ ExecutionResult result = null;
+ try {
+ result =
+ execution(
+ queryId,
+ session,
+ sql,
+ userQuery,
+ debug,
+ ((queryContext, startTime) ->
+ createQueryExecutionForTreeModel(
+ statement,
+ queryContext,
+ partitionFetcher,
+ schemaFetcher,
+ timeOut > 0 ? timeOut :
CONFIG.getQueryTimeoutThreshold(),
+ startTime)));
+ return result;
+ } finally {
+ DNAuditLogger.getInstance()
+ .logRevokeFailure(statement, session, sql, result == null ? null :
result.status);
+ }
}
private IQueryExecution createQueryExecutionForTreeModel(
@@ -531,22 +540,30 @@ public class Coordinator {
boolean userQuery,
boolean debug,
boolean readOnlyInternalQuery) {
- return execution(
- queryId,
- session,
- sql,
- userQuery,
- debug,
- readOnlyInternalQuery,
- ((queryContext, startTime) ->
- createQueryExecutionForTableModel(
- statement,
- sqlParser,
- clientSession,
- queryContext,
- metadata,
- timeOut > 0 ? timeOut : CONFIG.getQueryTimeoutThreshold(),
- startTime)));
+ ExecutionResult result = null;
+ try {
+ result =
+ execution(
+ queryId,
+ session,
+ sql,
+ userQuery,
+ debug,
+ readOnlyInternalQuery,
+ ((queryContext, startTime) ->
+ createQueryExecutionForTableModel(
+ statement,
+ sqlParser,
+ clientSession,
+ queryContext,
+ metadata,
+ timeOut > 0 ? timeOut :
CONFIG.getQueryTimeoutThreshold(),
+ startTime)));
+ return result;
+ } finally {
+ DNAuditLogger.getInstance()
+ .logRevokeFailure(statement, session, sql, result == null ? null :
result.status);
+ }
}
/** For compatibility of MQTT and REST, this method should never be called.
*/
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/DNAuditLoggerRevokeFailureTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/DNAuditLoggerRevokeFailureTest.java
new file mode 100644
index 00000000000..337e9c7acb5
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/audit/DNAuditLoggerRevokeFailureTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.iotdb.db.audit;
+
+import org.apache.iotdb.commons.audit.AuditEventType;
+import org.apache.iotdb.commons.audit.AuditLogOperation;
+import org.apache.iotdb.commons.audit.IAuditEntity;
+import org.apache.iotdb.commons.audit.UserEntity;
+import org.apache.iotdb.commons.auth.entity.PrivilegeType;
+import org.apache.iotdb.commons.queryengine.common.SessionInfo;
+import org.apache.iotdb.commons.queryengine.common.SqlDialect;
+import
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext;
+import
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement;
+import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType;
+import org.apache.iotdb.db.queryengine.plan.statement.AuthorType;
+import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement;
+import org.apache.iotdb.rpc.RpcUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.time.ZoneId;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class DNAuditLoggerRevokeFailureTest {
+
+ @Test
+ public void testTreeRevokeUserFailure() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ String sql = "REVOKE READ ON root.** FROM user1";
+ AuthorStatement statement = new AuthorStatement(AuthorType.REVOKE_USER);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(
+ statement, treeAuditEntity(sql),
RpcUtils.getStatus(TSStatusCode.NO_PERMISSION));
+
+ assertAuditLog(auditLogger, "user1", sql);
+ }
+
+ @Test
+ public void testTableRevokeRoleFailure() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ String sql = "REVOKE SELECT ON DATABASE db FROM ROLE role1";
+ RelationalAuthorStatement statement = new
RelationalAuthorStatement(AuthorRType.REVOKE_ROLE_DB);
+ statement.setRoleName("role1");
+
+ auditLogger.logRevokeFailure(
+ statement, sessionInfo(), sql,
RpcUtils.getStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR));
+
+ assertAuditLog(auditLogger, "role1", sql);
+ }
+
+ @Test
+ public void testSuccessfulRevokeIsIgnored() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ AuthorStatement statement = new AuthorStatement(AuthorType.REVOKE_USER);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(statement, sessionInfo(), "revoke",
RpcUtils.SUCCESS_STATUS);
+
+ verify(auditLogger, never()).log(any(), any());
+ }
+
+ @Test
+ public void testRedirectedRevokeIsIgnored() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ AuthorStatement statement = new AuthorStatement(AuthorType.REVOKE_USER);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(
+ statement, sessionInfo(), "revoke",
RpcUtils.getStatus(TSStatusCode.REDIRECTION_RECOMMEND));
+
+ verify(auditLogger, never()).log(any(), any());
+ }
+
+ @Test
+ public void testMissingStatusIsFailure() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ AuthorStatement statement = new AuthorStatement(AuthorType.REVOKE_USER);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(statement, sessionInfo(), "revoke", null);
+
+ assertAuditLog(auditLogger, "user1", "revoke");
+ }
+
+ @Test
+ public void testNonRevokeFailureIsIgnored() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(
+ statement, sessionInfo(), "grant",
RpcUtils.getStatus(TSStatusCode.NO_PERMISSION));
+
+ verify(auditLogger, never()).log(any(), any());
+ }
+
+ @Test
+ public void testNonRevokeWithMissingSessionIsIgnored() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ AuthorStatement statement = new AuthorStatement(AuthorType.GRANT_USER);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(
+ statement, (SessionInfo) null, "grant",
RpcUtils.getStatus(TSStatusCode.NO_PERMISSION));
+
+ verify(auditLogger, never()).log(any(), any());
+ }
+
+ @Test
+ public void testRoleMembershipRevokeFailure() {
+ DNAuditLogger auditLogger = mock(DNAuditLogger.class, CALLS_REAL_METHODS);
+ RelationalAuthorStatement statement =
+ new RelationalAuthorStatement(AuthorRType.REVOKE_USER_ROLE);
+ statement.setUserName("user1");
+
+ auditLogger.logRevokeFailure(
+ statement, sessionInfo(), "revoke role",
RpcUtils.getStatus(TSStatusCode.NO_PERMISSION));
+
+ assertAuditLog(auditLogger, "user1", "revoke role");
+ }
+
+ private static IAuditEntity treeAuditEntity(String sql) {
+ return new TreeAccessCheckContext(7L, "operator", "127.0.0.1")
+ .setDatabase("database")
+ .setSqlString(sql);
+ }
+
+ private static SessionInfo sessionInfo() {
+ return new SessionInfo(
+ 1L,
+ new UserEntity(7L, "operator", "127.0.0.1"),
+ ZoneId.systemDefault(),
+ "database",
+ SqlDialect.TABLE);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void assertAuditLog(DNAuditLogger auditLogger, String
targetName, String sql) {
+ ArgumentCaptor<IAuditEntity> entityCaptor =
ArgumentCaptor.forClass(IAuditEntity.class);
+ ArgumentCaptor<Supplier<String>> logCaptor =
ArgumentCaptor.forClass(Supplier.class);
+ verify(auditLogger).log(entityCaptor.capture(), logCaptor.capture());
+
+ IAuditEntity entity = entityCaptor.getValue();
+ assertEquals(7L, entity.getUserId());
+ assertEquals("operator", entity.getUsername());
+ assertEquals("127.0.0.1", entity.getCliHostname());
+ assertEquals(AuditEventType.REVOKE_FAILED, entity.getAuditEventType());
+ assertEquals(AuditLogOperation.CONTROL, entity.getAuditLogOperation());
+ assertEquals(PrivilegeType.SECURITY, entity.getPrivilegeTypes().get(0));
+ assertFalse(entity.getResult());
+ assertEquals("database", entity.getDatabase());
+ assertEquals(sql, entity.getSqlString());
+ assertEquals(targetName, logCaptor.getValue().get());
+ }
+}