mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-24 23:05:29 +08:00
Dbeaver/pro#5864 add audit logging (#3556)
* dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 Add audit logging * dbeaver/pro#5864 fix review * dbeaver/pro#5864 fix review * dbeaver/pro#5864 fixes * dbeaver/pro#5864 add file name upload logging --------- Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com> Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgenia
Ainur
parent
5ce5f6e10d
commit
bae21e5b2e
@@ -29,6 +29,7 @@ Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.model,
|
||||
io.cloudbeaver.model.app,
|
||||
io.cloudbeaver.model.config,
|
||||
io.cloudbeaver.model.apilog,
|
||||
io.cloudbeaver.model.cli,
|
||||
io.cloudbeaver.model.events,
|
||||
io.cloudbeaver.model.fs,
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed 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 io.cloudbeaver.model.apilog;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interceptor for API call events.
|
||||
* This interface allows to intercept and log API call events in a web application.
|
||||
*/
|
||||
public interface ApiCallInterceptor {
|
||||
|
||||
/**
|
||||
* Intercept API call event.
|
||||
*
|
||||
*/
|
||||
void onApiCallEvent(
|
||||
@NotNull HttpServletRequest request,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@NotNull String apiCall,
|
||||
@NotNull LocalDateTime startTime,
|
||||
@Nullable String errorMessage,
|
||||
@NotNull String apiProtocol
|
||||
);
|
||||
}
|
||||
@@ -57,6 +57,7 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
|
||||
private boolean anonymousAccessEnabled;
|
||||
private boolean grantConnectionsAccessToAnonymousTeam;
|
||||
private boolean systemVariablesResolvingEnabled;
|
||||
private boolean apiCallLoggingEnabled;
|
||||
@Deprecated
|
||||
private String anonymousUserRole;
|
||||
private String anonymousUserTeam;
|
||||
@@ -90,6 +91,7 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
|
||||
this.linkExternalCredentialsWithUser = true;
|
||||
this.grantConnectionsAccessToAnonymousTeam = false;
|
||||
this.systemVariablesResolvingEnabled = false;
|
||||
this.apiCallLoggingEnabled = false;
|
||||
}
|
||||
|
||||
public CBAppConfig(CBAppConfig src) {
|
||||
@@ -114,6 +116,7 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
|
||||
this.linkExternalCredentialsWithUser = src.linkExternalCredentialsWithUser;
|
||||
this.grantConnectionsAccessToAnonymousTeam = src.grantConnectionsAccessToAnonymousTeam;
|
||||
this.systemVariablesResolvingEnabled = src.systemVariablesResolvingEnabled;
|
||||
this.apiCallLoggingEnabled = src.apiCallLoggingEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -354,4 +357,8 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
|
||||
authConfiguration.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isApiCallLoggingEnabled() {
|
||||
return apiCallLoggingEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-17
@@ -29,9 +29,11 @@ import graphql.schema.idl.SchemaParser;
|
||||
import graphql.schema.idl.TypeDefinitionRegistry;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.apilog.ApiCallInterceptor;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.server.HttpConstants;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import io.cloudbeaver.service.DBWBindingContext;
|
||||
import io.cloudbeaver.service.DBWServiceBindingGraphQL;
|
||||
import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
@@ -50,6 +52,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -67,6 +70,7 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
private static final String HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
|
||||
|
||||
private static final String CORE_SCHEMA_FILE_NAME = "schema/schema.graphqls";
|
||||
public static final String API_PROTOCOL = "GraphQL";
|
||||
private final GraphQL graphQL;
|
||||
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
@@ -241,24 +245,34 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
if (operationName != null) {
|
||||
contextBuilder.operationName(operationName);
|
||||
}
|
||||
{
|
||||
String apiCall = operationName;
|
||||
// if (!CommonUtils.isEmpty(apiCall)) {
|
||||
// if (variables != null) {
|
||||
// apiCall += " (" + variables + ")";
|
||||
// }
|
||||
// }
|
||||
String sessionId = GraphQLLoggerUtil.getSessionId(request);
|
||||
String userId = GraphQLLoggerUtil.getUserId(request);
|
||||
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
|
||||
if (apiCall != null) {
|
||||
log.debug("API > " + apiCall + loggerMessage);
|
||||
} else if (DEBUG) {
|
||||
log.debug("API > " + query + loggerMessage);
|
||||
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
|
||||
String userId = GraphQLLoggerUtil.getUserId(request);
|
||||
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
|
||||
if (operationName != null) {
|
||||
log.debug("API > " + operationName + loggerMessage);
|
||||
} else if (DEBUG) {
|
||||
log.debug("API > " + query + loggerMessage);
|
||||
}
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
ExecutionInput executionInput = contextBuilder.build();
|
||||
ExecutionResult executionResult = null;
|
||||
Exception executionException = null;
|
||||
try {
|
||||
executionResult = graphQL.execute(executionInput);
|
||||
} catch (Exception e) {
|
||||
executionException = e;
|
||||
throw e;
|
||||
} finally {
|
||||
String errorMessage = null;
|
||||
if (executionResult != null && executionResult.getErrors() != null && !executionResult.getErrors().isEmpty()) {
|
||||
errorMessage = executionResult.getErrors().getFirst().getMessage();
|
||||
} else if (executionException != null) {
|
||||
errorMessage = executionException.getMessage();
|
||||
}
|
||||
if (WebAppUtils.getWebApplication() instanceof ApiCallInterceptor apiCallInterceptor) {
|
||||
apiCallInterceptor.onApiCallEvent(request, variables, operationName, startTime, errorMessage, API_PROTOCOL);
|
||||
}
|
||||
}
|
||||
ExecutionInput executionInput = contextBuilder.build();
|
||||
ExecutionResult executionResult = graphQL.execute(executionInput);
|
||||
|
||||
Map<String, Object> resJSON = executionResult.toSpecification();
|
||||
String resString = gson.toJson(resJSON);
|
||||
@@ -267,6 +281,7 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
response.getWriter().print(resString);
|
||||
}
|
||||
|
||||
|
||||
private static class WebExecutionStrategy extends AsyncExecutionStrategy {
|
||||
|
||||
public WebExecutionStrategy() {
|
||||
@@ -335,4 +350,4 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
return context.get("bindingContext");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ public class GraphQLLoggerUtil {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static String getSessionId(HttpServletRequest request) {
|
||||
public static String getSmSessionId(HttpServletRequest request) {
|
||||
WebSession session = getWebSession(request);
|
||||
if (session == null) {
|
||||
return null;
|
||||
@@ -53,7 +53,7 @@ public class GraphQLLoggerUtil {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static WebSession getWebSession(HttpServletRequest request) {
|
||||
public static WebSession getWebSession(HttpServletRequest request) {
|
||||
if (request.getSession() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+6
-1
@@ -66,6 +66,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TypeDefinitionRegistry getTypeDefinition() {
|
||||
return loadSchemaDefinition(getClass(), schemaFileName);
|
||||
}
|
||||
@@ -78,7 +79,11 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
|
||||
return apiInterface.cast(proxyImpl);
|
||||
}
|
||||
|
||||
public static TypeDefinitionRegistry loadSchemaDefinition(Class<?> theClass, String schemaPath) {
|
||||
@Nullable
|
||||
public static TypeDefinitionRegistry loadSchemaDefinition(@NotNull Class<?> theClass, @Nullable String schemaPath) {
|
||||
if (schemaPath == null) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream schemaStream = theClass.getClassLoader().getResourceAsStream(schemaPath)) {
|
||||
if (schemaStream == null) {
|
||||
throw new IOException("Schema file '" + schemaPath + "' not found");
|
||||
|
||||
+14
-1
@@ -18,6 +18,7 @@ package io.cloudbeaver.service;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.cloudbeaver.model.apilog.ApiCallInterceptor;
|
||||
import io.cloudbeaver.model.app.ServletApplication;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
@@ -31,6 +32,7 @@ import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class WebServiceServletBase extends HttpServlet {
|
||||
@@ -42,6 +44,7 @@ public abstract class WebServiceServletBase extends HttpServlet {
|
||||
.serializeNulls()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
public static final String API_PROTOCOL = "REST";
|
||||
|
||||
private final ServletApplication application;
|
||||
|
||||
@@ -60,11 +63,21 @@ public abstract class WebServiceServletBase extends HttpServlet {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Web session not found");
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
String errorMessage = null;
|
||||
try {
|
||||
processServiceRequest(webSession, request, response);
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + e.getMessage());
|
||||
errorMessage = e.getMessage();
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + errorMessage);
|
||||
} finally {
|
||||
if (WebAppUtils.getWebApplication() instanceof ApiCallInterceptor apiCallInterceptor) {
|
||||
apiCallInterceptor.onApiCallEvent(
|
||||
request, getVariables(request), request.getRequestURI(), startTime, errorMessage, API_PROTOCOL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
@@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Part;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.navigator.fs.DBNPathBase;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -42,6 +43,8 @@ import java.util.Map;
|
||||
|
||||
@MultipartConfig()
|
||||
public class WebFSServlet extends WebServiceServletBase {
|
||||
|
||||
private static final Log log = Log.getLog(WebFSServlet.class);
|
||||
private static final String PARAM_PROJECT_ID = "projectId";
|
||||
private final DBWServiceFS fs;
|
||||
|
||||
@@ -102,4 +105,20 @@ public class WebFSServlet extends WebServiceServletBase {
|
||||
CommonUtils.getRootCause(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> getVariables(HttpServletRequest request) {
|
||||
Map<String, Object> variables = super.getVariables(request);
|
||||
try {
|
||||
for (Part part : request.getParts()) {
|
||||
if (part.getSubmittedFileName()!= null && !part.getSubmittedFileName().isEmpty()) {
|
||||
variables.put("fileName", part.getSubmittedFileName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to get fileName from request for logging", e);
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user