Skip to content

Commit

Permalink
Merge branch 'CB-5760-driver-manager-add-title' of https://github.com…
Browse files Browse the repository at this point in the history
…/dbeaver/cloudbeaver into CB-5760-driver-manager-add-title
  • Loading branch information
devnaumov committed Oct 14, 2024
2 parents 5cc0ac6 + b1ba597 commit 68f88df
Show file tree
Hide file tree
Showing 77 changed files with 3,128 additions and 3,084 deletions.
4 changes: 2 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@
"label": "Build CE",
"type": "shell",
"windows": {
"command": "./build-sqlite.bat"
"command": "./build.bat"
},
"osx": {
"command": "./build-sqlite.sh"
"command": "./build.sh"
},
"options": {
"cwd": "${workspaceFolder}/deploy"
Expand Down
7 changes: 4 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ currently being supported with security updates.
| ------- | --------- |
| 22.x | yes |
| 23.x | yes |
| 24.x | yes |

## Reporting a Vulnerability

Please report (suspected) security vulnerabilities to devops@dbeaver.com.
You will receive a response from us within 48 hours.
If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically within a few days.
Please report (suspected) security vulnerabilities to devops@dbeaver.com.
You will receive a response from us within 48 hours.
If the issue is confirmed, we will release a patch as soon as possible, depending on complexity, but historically, within a few days.
24 changes: 2 additions & 22 deletions config/GlobalConfiguration/.dbeaver/data-sources.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,4 @@
{
"folders": {},
"connections": {
"postgresql-template-1": {
"provider": "postgresql",
"driver": "postgres-jdbc",
"name": "PostgreSQL (Template)",
"save-password": false,
"show-system-objects": false,
"read-only": true,
"template": true,
"configuration": {
"host": "localhost",
"port": "5432",
"database": "postgres",
"url": "jdbc:postgresql://localhost:5432/postgres",
"type": "dev",
"provider-properties": {
"@dbeaver-show-non-default-db@": "false"
}
}
}
}
"folders": {},
"connections": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public Gson getGson() {
return getGsonBuilder().create();
}

@NotNull
protected abstract GsonBuilder getGsonBuilder();

public abstract T getServerConfiguration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public List<BaseProjectImpl> getProjects() {
@Nullable
@Override
public BaseProjectImpl getProject(@NotNull String projectName) {
if (globalProject.getId().equals(projectName)) {
if (globalProject != null && globalProject.getId().equals(projectName)) {
return globalProject;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.Strictness;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.WebPropertyInfo;
Expand Down Expand Up @@ -294,7 +295,7 @@ public static void saveAuthProperties(
// Make new Gson parser with type adapters to deserialize into existing credentials
InstanceCreator<DBAAuthCredentials> credTypeAdapter = type -> credentials;
Gson credGson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(credentials.getClass(), credTypeAdapter)
.create();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
*/
package io.cloudbeaver.server;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.*;
import io.cloudbeaver.model.app.BaseServerConfigurationController;
import io.cloudbeaver.model.app.BaseWebApplication;
import io.cloudbeaver.model.config.CBAppConfig;
Expand Down Expand Up @@ -326,6 +324,7 @@ public Map<String, Object> readConfigurationFile(Path path) throws DBException {
}
}

@NotNull
protected GsonBuilder getGsonBuilder() {
// Stupid way to populate existing objects but ok google (https://github.com/google/gson/issues/431)
InstanceCreator<CBAppConfig> appConfigCreator = type -> appConfiguration;
Expand All @@ -336,7 +335,8 @@ protected GsonBuilder getGsonBuilder() {
InstanceCreator<PasswordPolicyConfiguration> smPasswordPoliceConfigCreator =
type -> securityManagerConfiguration.getPasswordPolicyConfiguration();
return new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
.registerTypeAdapter(getServerConfiguration().getClass(), serverConfigCreator)
.registerTypeAdapter(CBAppConfig.class, appConfigCreator)
.registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator)
Expand Down Expand Up @@ -372,7 +372,7 @@ private synchronized void writeRuntimeConfig(Path runtimeConfigPath, Map<String,

try (Writer out = new OutputStreamWriter(Files.newOutputStream(runtimeConfigPath), StandardCharsets.UTF_8)) {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.setPrettyPrinting()
.create();
gson.toJson(configurationProperties, out);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private void savePasswordPolicyConfig(Map<String, Object> originServerConfig, Ma
}
}

@NotNull
@Override
protected GsonBuilder getGsonBuilder() {
GsonBuilder gsonBuilder = super.getGsonBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,100 +19,162 @@
import io.cloudbeaver.WebSessionGlobalProjectImpl;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.utils.WebAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMObjectPermissionsGrant;
import org.jkiss.dbeaver.model.websocket.event.WSEventType;
import org.jkiss.dbeaver.model.websocket.event.WSProjectUpdateEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.model.websocket.event.permissions.WSObjectPermissionEvent;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class WSObjectPermissionUpdatedEventHandler extends WSDefaultEventHandler<WSObjectPermissionEvent> {
private static final Log log = Log.getLog(WSObjectPermissionUpdatedEventHandler.class);

@Override
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSObjectPermissionEvent event) {
try {
public void handleEvent(@NotNull WSObjectPermissionEvent event) {
String objectId = event.getObjectId();
Consumer<BaseWebSession> runnable = switch (event.getSmObjectType()) {
case project:
yield getUpdateUserProjectsInfoConsumer(event, objectId);
case datasource:
try {
SMAdminController smController = CBApplication.getInstance().getSecurityController();
Set<String> dataSourcePermissions = smController.getObjectPermissionGrants(event.getObjectId(), event.getSmObjectType())
.stream()
.map(SMObjectPermissionsGrant::getSubjectId).collect(Collectors.toSet());
yield getUpdateUserDataSourcesInfoConsumer(event, objectId, dataSourcePermissions);
} catch (DBException e) {
log.error("Error getting permissions for data source " + objectId, e);
yield null;
}
};
if (runnable == null) {
return;
}
log.debug(event.getTopicId() + " event handled");
Collection<BaseWebSession> allSessions = CBPlatform.getInstance().getSessionManager().getAllActiveSessions();
for (var activeUserSession : allSessions) {
if (!isAcceptableInSession(activeUserSession, event)) {
log.debug("Cannot handle %s event '%s' in session %s".formatted(
event.getTopicId(),
event.getId(),
activeUserSession.getSessionId()
));
continue;
}
log.debug("%s event '%s' handled".formatted(event.getTopicId(), event.getId()));
runnable.accept(activeUserSession);
}
}

@NotNull
private Consumer<BaseWebSession> getUpdateUserDataSourcesInfoConsumer(
@NotNull WSObjectPermissionEvent event,
@NotNull String dataSourceId,
@NotNull Set<String> dataSourcePermissions
) {
return (activeUserSession) -> {
// we have accessible data sources only in web session
if (event.getSmObjectType() == SMObjectType.datasource && !(activeUserSession instanceof WebSession)) {
// admins already have access for all shared connections
if (!(activeUserSession instanceof WebSession webSession) || SMUtils.isAdmin(webSession)) {
return;
}
var objectId = event.getObjectId();

boolean isAccessibleNow;
switch (event.getSmObjectType()) {
case project:
if (WSEventType.OBJECT_PERMISSIONS_UPDATED.getEventId().equals(event.getId())) {
var accessibleProjectIds = activeUserSession.getUserContext().getAccessibleProjectIds();
if (accessibleProjectIds.contains(event.getObjectId())) {
return;
}
activeUserSession.addSessionProject(objectId);
activeUserSession.addSessionEvent(
WSProjectUpdateEvent.create(
event.getSessionId(),
event.getUserId(),
objectId
)
);
} else if (WSEventType.OBJECT_PERMISSIONS_DELETED.getEventId().equals(event.getId())) {
activeUserSession.removeSessionProject(objectId);
activeUserSession.addSessionEvent(
WSProjectUpdateEvent.delete(
event.getSessionId(),
event.getUserId(),
objectId
)
);
}
break;
case datasource:
var webSession = (WebSession) activeUserSession;
var dataSources = List.of(objectId);
if (!isAcceptableInSession(webSession, event)) {
return;
}
var user = activeUserSession.getUserContext().getUser();
var userSubjects = new HashSet<>(Set.of(user.getTeams()));
userSubjects.add(user.getUserId());
boolean shouldBeAccessible = dataSourcePermissions.stream().anyMatch(userSubjects::contains);
List<String> dataSources = List.of(dataSourceId);
WebSessionGlobalProjectImpl project = webSession.getGlobalProject();
if (project == null) {
log.error("Project " + WebAppUtils.getGlobalProjectId() +
" is not found in session " + activeUserSession.getSessionId());
return;
}
boolean isAccessibleNow = project.findWebConnectionInfo(dataSourceId) != null;
if (WSEventType.OBJECT_PERMISSIONS_UPDATED.getEventId().equals(event.getId())) {
if (isAccessibleNow || !shouldBeAccessible) {
return;
}
project.addAccessibleConnectionToCache(dataSourceId);
webSession.addSessionEvent(
WSDataSourceEvent.create(
event.getSessionId(),
event.getUserId(),
project.getId(),
dataSources,
WSDataSourceProperty.CONFIGURATION
)
);
} else if (WSEventType.OBJECT_PERMISSIONS_DELETED.getEventId().equals(event.getId())) {
if (!isAccessibleNow || shouldBeAccessible) {
return;
}
project.removeAccessibleConnectionFromCache(dataSourceId);
webSession.addSessionEvent(
WSDataSourceEvent.delete(
event.getSessionId(),
event.getUserId(),
project.getId(),
dataSources,
WSDataSourceProperty.CONFIGURATION
)
);
}
};
}

WebSessionGlobalProjectImpl project = webSession.getGlobalProject();
if (project == null) {
log.error("Project " + WebAppUtils.getGlobalProjectId() +
" is not found in session " + activeUserSession.getSessionId());
@NotNull
private Consumer<BaseWebSession> getUpdateUserProjectsInfoConsumer(
@NotNull WSObjectPermissionEvent event,
@NotNull String projectId
) {
return (activeUserSession) -> {
try {
if (WSEventType.OBJECT_PERMISSIONS_UPDATED.getEventId().equals(event.getId())) {
var accessibleProjectIds = activeUserSession.getUserContext().getAccessibleProjectIds();
if (accessibleProjectIds.contains(event.getObjectId())) {
return;
}
if (WSEventType.OBJECT_PERMISSIONS_UPDATED.getEventId().equals(event.getId())) {
isAccessibleNow = project.findWebConnectionInfo(objectId) != null;
if (isAccessibleNow) {
return;
}
project.addAccessibleConnectionToCache(objectId);
webSession.addSessionEvent(
WSDataSourceEvent.create(
event.getSessionId(),
event.getUserId(),
project.getId(),
dataSources,
WSDataSourceProperty.CONFIGURATION
)
);
} else if (WSEventType.OBJECT_PERMISSIONS_DELETED.getEventId().equals(event.getId())) {
project.removeAccessibleConnectionFromCache(objectId);
webSession.addSessionEvent(
WSDataSourceEvent.delete(
event.getSessionId(),
event.getUserId(),
project.getId(),
dataSources,
WSDataSourceProperty.CONFIGURATION
)
);
}
activeUserSession.addSessionProject(projectId);
activeUserSession.addSessionEvent(
WSProjectUpdateEvent.create(
event.getSessionId(),
event.getUserId(),
projectId
)
);
} else if (WSEventType.OBJECT_PERMISSIONS_DELETED.getEventId().equals(event.getId())) {
activeUserSession.removeSessionProject(projectId);
activeUserSession.addSessionEvent(
WSProjectUpdateEvent.delete(
event.getSessionId(),
event.getUserId(),
projectId
)
);
}
} catch (DBException e) {
log.error("Error on changing permissions for project " +
event.getObjectId() + " in session " + activeUserSession.getSessionId(), e);
}
} catch (DBException e) {
log.error("Error on changing permissions for project " +
event.getObjectId() + " in session " + activeUserSession.getSessionId(), e);
}
};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ private void getObjectFeatures(DBSObject object, List<String> features) {
features.add(OBJECT_FEATURE_OBJECT_CONTAINER);
try {
Class<? extends DBSObject> childType = objectContainer.getPrimaryChildType(null);
Collection<? extends DBSObject> childrenCollection = objectContainer.getChildren(session.getProgressMonitor());
if (DBSTable.class.isAssignableFrom(childType) && childrenCollection != null) {
if (DBSTable.class.isAssignableFrom(childType)) {
features.add(OBJECT_FEATURE_ENTITY_CONTAINER);
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.Strictness;
import io.cloudbeaver.auth.provider.local.LocalAuthProviderConstants;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.model.config.WebDatabaseConfig;
Expand Down Expand Up @@ -301,7 +302,9 @@ CBDatabaseInitialData getInitialData() throws DBException {
initialDataPath = WebAppUtils.getRelativePath(
databaseConfiguration.getInitialDataConfiguration(), application.getHomeDirectory());
try (Reader reader = new InputStreamReader(new FileInputStream(initialDataPath), StandardCharsets.UTF_8)) {
Gson gson = new GsonBuilder().setLenient().create();
Gson gson = new GsonBuilder()
.setStrictness(Strictness.LENIENT)
.create();
return gson.fromJson(reader, CBDatabaseInitialData.class);
} catch (Exception e) {
throw new DBException("Error loading initial data configuration", e);
Expand Down
Loading

0 comments on commit 68f88df

Please sign in to comment.