Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/template/cloudbeaver-base.conf
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@
value: true
},

dbUserPasswordChangeEnabled: {
env: "CLOUDBEAVER_APP_DB_USER_PASSWORD_CHANGE_ENABLED",
value: false
},

resourceQuotas: {
resourceManagerFileSizeLimit: {
env: "CLOUDBEAVER_RESOURCE_QUOTA_RESOURCE_MANAGER_FILE_SIZE_LIMIT",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public class WebConnectionInfo {
private static final String FEATURE_RESTRICT_METADATA_EDIT = "restrictMetadataEdit";

private static final String TOOL_SESSION_MANAGER = "sessionManager";

private final WebSession session;
private final DBPDataSourceContainer dataSourceContainer;
private WebServerError connectError;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public interface WebAppConfiguration extends ServletAppConfiguration {

boolean isAdminCredentialsSaveEnabled();

default boolean isDbUserPasswordChangeEnabled() {
return false;
}

default String[] getDisabledBetaFeatures() {
return new String[0];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
private boolean forwardProxy;
private boolean publicCredentialsSaveEnabled;
private boolean adminCredentialsSaveEnabled;
private boolean dbUserPasswordChangeEnabled;
private boolean linkExternalCredentialsWithUser;

private boolean redirectOnFederatedAuth;
Expand Down Expand Up @@ -80,6 +81,7 @@ public CBAppConfig() {
this.supportsCustomConnections = true;
this.publicCredentialsSaveEnabled = true;
this.adminCredentialsSaveEnabled = true;
this.dbUserPasswordChangeEnabled = false;
this.redirectOnFederatedAuth = false;
this.enabledDrivers = new String[0];
this.disabledDrivers = new String[0];
Expand All @@ -104,6 +106,7 @@ public CBAppConfig(CBAppConfig src) {
this.supportsCustomConnections = src.supportsCustomConnections;
this.publicCredentialsSaveEnabled = src.publicCredentialsSaveEnabled;
this.adminCredentialsSaveEnabled = src.adminCredentialsSaveEnabled;
this.dbUserPasswordChangeEnabled = src.dbUserPasswordChangeEnabled;
this.redirectOnFederatedAuth = src.redirectOnFederatedAuth;
this.enabledDrivers = src.enabledDrivers;
this.disabledDrivers = src.disabledDrivers;
Expand Down Expand Up @@ -154,6 +157,14 @@ public void setPublicCredentialsSaveEnabled(boolean publicCredentialsSaveEnabled
this.publicCredentialsSaveEnabled = publicCredentialsSaveEnabled;
}

public boolean isDbUserPasswordChangeEnabled() {
return dbUserPasswordChangeEnabled;
}

public void setDbUserPasswordChangeEnabled(boolean dbUserPasswordChangeEnabled) {
this.dbUserPasswordChangeEnabled = dbUserPasswordChangeEnabled;
}

public boolean isAdminCredentialsSaveEnabled() {
return adminCredentialsSaveEnabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class AdminServerConfig {
private final boolean customConnectionsEnabled;
private final boolean publicCredentialsSaveEnabled;
private final boolean adminCredentialsSaveEnabled;
private final boolean dbUserPasswordChangeEnabled;
private final List<String> enabledFeatures;
private final List<String> enabledAuthProviders;
private final String[] enabledDrivers;
Expand Down Expand Up @@ -75,6 +76,11 @@ public AdminServerConfig(@NotNull Map<String, Object> params) {
"adminCredentialsSaveEnabled",
appConfig.isAdminCredentialsSaveEnabled()
);
this.dbUserPasswordChangeEnabled = JSONUtils.getBoolean(
params,
"dbUserPasswordChangeEnabled",
appConfig.isDbUserPasswordChangeEnabled()
);
this.resourceManagerEnabled = JSONUtils.getBoolean(params, "resourceManagerEnabled", appConfig.isResourceManagerEnabled());
this.secretManagerEnabled = JSONUtils.getBoolean(params, "secretManagerEnabled", appConfig.isSecretManagerEnabled());

Expand Down Expand Up @@ -163,6 +169,10 @@ public boolean isAdminCredentialsSaveEnabled() {
return adminCredentialsSaveEnabled;
}

public boolean isDbUserPasswordChangeEnabled() {
return dbUserPasswordChangeEnabled;
}

public long getSessionExpireTime() {
return sessionExpireTime;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ protected Map<String, Object> collectConfigurationProperties(
appConfigProperties,
"adminCredentialsSaveEnabled",
appConfig.isAdminCredentialsSaveEnabled());
copyConfigValue(
oldAppConfig,
appConfigProperties,
"dbUserPasswordChangeEnabled",
appConfig.isDbUserPasswordChangeEnabled());
copyConfigValue(
oldAppConfig, appConfigProperties, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth());
copyConfigValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ protected void populateConfigurations(@NotNull I input, @NotNull C serverConfig,
appConfig.setSupportsCustomConnections(input.isCustomConnectionsEnabled());
appConfig.setPublicCredentialsSaveEnabled(input.isPublicCredentialsSaveEnabled());
appConfig.setAdminCredentialsSaveEnabled(input.isAdminCredentialsSaveEnabled());
appConfig.setDbUserPasswordChangeEnabled(input.isDbUserPasswordChangeEnabled());
Comment thread
TobyTheHutt marked this conversation as resolved.
updateDisabledFeaturesConfig(appConfig, input.getEnabledFeatures());
// custom logic for enabling embedded drivers
updateDisabledDriversConfig(appConfig, input.getDisabledDrivers());
Expand Down
3 changes: 3 additions & 0 deletions server/bundles/io.cloudbeaver.server/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
<eventHandler class="io.cloudbeaver.server.events.WSObjectSettingsEventHandler">
<topic id="cb_object_settings"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSSecurityAuditEventHandler">
<topic id="cb_security_audit"/>
</eventHandler>
</extension>

<extension point="org.jkiss.dbeaver.settings">
Expand Down
15 changes: 15 additions & 0 deletions server/bundles/io.cloudbeaver.server/schema/service.core.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ type ServerConfig {
"Defines is it is possible to save global database credentials"
adminCredentialsSaveEnabled: Boolean!

"Enables database user password changes."
dbUserPasswordChangeEnabled: Boolean! @since(version: "26.2.1")

"Defines if the server requires a license"
licenseRequired: Boolean!
"Defines if the server license is valid"
Expand Down Expand Up @@ -902,6 +905,18 @@ extend type Mutation {
"Test connection configuration. Returns remote server version"
testConnection( config: ConnectionConfig!, projectId: ID): ConnectionInfo!

"""
Change the DB user password for this connection.
The caller MUST have PERMISSION_PROJECT_DATASOURCES_EDIT on the connection's project.
Driver exceptions propagate to the caller when the database rejects the change.
"""
changeConnectionUserPassword(
projectId: ID,
connectionId: ID!,
oldPassword: String!,
newPassword: String!
): Boolean! @since(version: "26.2.1")

"Test network handler connectivity"
testNetworkHandler(projectId: ID, connectionId: ID, config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public boolean isAdminCredentialsSaveEnabled() {
return application.getAppConfiguration().isAdminCredentialsSaveEnabled();
}

@Property
public boolean isDbUserPasswordChangeEnabled() {
return application.getAppConfiguration().isDbUserPasswordChangeEnabled();
}

@Property
public boolean isLicenseRequired() {
return application.isLicenseRequired();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 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.server.events;

import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;

public class WSSecurityAuditEvent extends WSAbstractEvent {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need that class? I think it would be better just write logs as usual by using Log.getLog(Class.class)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WSSecurityAuditEventHandler writes a structured log line and prints machine readable fields like kind, reasonCode, connectionId, driverId or errorClass, rather than just plaintext. It also adds the capability for future WebSocket subscriptions and ensures consistend vocabulary with other CB EventBus consumers.

log.info does not provide this, which is why I decided for this approach and continue to recommend it. Let me know if you concur of whether you want me to fall back to log.info anyway.

public static final String TOPIC = "cb_security_audit";
public static final String ID = "cb_security_audit_updated";

public enum Kind {
/** Emitted before invoking the DBeaver core password-change handler. */
ATTEMPTED,
/** Emitted after handler completes and credentials are persisted. */
SUCCEEDED,
/** Emitted when the handler raises an exception or credential persistence fails. */
FAILED,
/** Emitted when any pre-invocation gate rejects the request. */
GATE_REJECTED
}

@Nullable
private final String projectId;
@Nullable
private final String connectionId;
@Nullable
private final String driverId;
@NotNull
private final Kind kind;
@Nullable
private final String reasonCode;
@Nullable
private final String errorClass;

public WSSecurityAuditEvent(
@Nullable String sessionId,
@Nullable String userId,
@Nullable String projectId,
@Nullable String connectionId,
@Nullable String driverId,
@NotNull Kind kind,
@Nullable String reasonCode,
@Nullable String errorClass
) {
super(ID, TOPIC, sessionId, userId);
this.projectId = projectId;
this.connectionId = connectionId;
this.driverId = driverId;
this.kind = kind;
this.reasonCode = reasonCode;
this.errorClass = errorClass;
}

@Nullable
public String getProjectId() {
return projectId;
}

@Nullable
public String getConnectionId() {
return connectionId;
}

@Nullable
public String getDriverId() {
return driverId;
}

@NotNull
public Kind getKind() {
return kind;
}

@Nullable
public String getReasonCode() {
return reasonCode;
}

@Nullable
public String getErrorClass() {
return errorClass;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 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.server.events;

import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.WSEventHandler;

public class WSSecurityAuditEventHandler implements WSEventHandler<WSSecurityAuditEvent> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think that we need it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #4586 (comment). Please confirm you really don't want it, before I just fall back.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TobyTheHutt Let's remove it.

private static final Log auditLog = Log.getLog(WSSecurityAuditEventHandler.class);

@Override
public void handleEvent(@NotNull WSSecurityAuditEvent event) {
auditLog.info(String.format(
"topic=%s id=%s kind=%s reasonCode=%s userId=%s sessionId=%s "
+ "projectId=%s connectionId=%s driverId=%s errorClass=%s timestamp=%d",
event.getTopicId(),
event.getId(),
event.getKind(),
event.getReasonCode(),
event.getUserId(),
event.getSessionId(),
event.getProjectId(),
event.getConnectionId(),
event.getDriverId(),
event.getErrorClass(),
event.getTimestamp()
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ WebConnectionInfo testConnection(
@NotNull Map<String, Object> connectionConfig
) throws DBWebException;

@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
Comment thread
TobyTheHutt marked this conversation as resolved.
boolean changeConnectionUserPassword(
@NotNull WebSession webSession,
@Nullable @WebObjectId String projectId,
@NotNull String connectionId,
@WebParameterSecure @NotNull String oldPassword,
@WebParameterSecure @NotNull String newPassword
) throws DBWebException;

@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
WebNetworkEndpointInfo testNetworkHandler(
@NotNull WebSession webSession,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ public void bindWiring(DBWBindingContext model) throws DBWebException {
.dataFetcher("testConnection", env -> getService(env).testConnection(
getWebSession(env), getProjectReference(env), getArgumentVal(env, "config")
))
.dataFetcher("changeConnectionUserPassword", env -> getService(env).changeConnectionUserPassword(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "connectionId"),
getArgumentVal(env, "oldPassword"),
getArgumentVal(env, "newPassword")
))
.dataFetcher("testNetworkHandler", env -> getService(env).testNetworkHandler(
getWebSession(env),
getProjectReference(env),
Expand Down
Loading