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());
+  }
+}

Reply via email to