Merge remote-tracking branch 'origin/devel' into feat/CB-2339-enable-users

This commit is contained in:
Konstantin Vasilev
2022-08-31 12:00:28 +03:00
23 changed files with 513 additions and 92 deletions
@@ -20,6 +20,7 @@ import io.cloudbeaver.model.rm.RMUtils;
import org.eclipse.core.resources.IProject;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.registry.BaseProjectImpl;
@@ -93,7 +94,7 @@ public class VirtualProjectImpl extends BaseProjectImpl {
@NotNull
@Override
protected DataSourceRegistry createDataSourceRegistry() {
return new WebDataSourceRegistryProxy(super.createDataSourceRegistry(), dataSourceFilter);
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new WebDataSourceRegistryProxy(new DataSourceRegistry(this), dataSourceFilter);
}
}
@@ -7,30 +7,40 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.access.DBAAuthProfile;
import org.jkiss.dbeaver.model.access.DBACredentialsProvider;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.net.DBWNetworkProfile;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceFolder;
import org.jkiss.dbeaver.registry.DataSourceConfigurationManager;
import org.jkiss.dbeaver.registry.DataSourcePersistentRegistry;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class WebDataSourceRegistryProxy extends DataSourceRegistry {
public class WebDataSourceRegistryProxy implements DBPDataSourceRegistry, DataSourcePersistentRegistry {
private final DataSourceFilter dataSourceFilter;
private final DataSourceRegistry dataSourceRegistry;
public WebDataSourceRegistryProxy(DataSourceRegistry dataSourceRegistry, DataSourceFilter filter) {
super(dataSourceRegistry.getProject());
this.dataSourceRegistry = dataSourceRegistry;
this.dataSourceFilter = filter;
}
@Override
public DBPProject getProject() {
return dataSourceRegistry.getProject();
}
@Nullable
@Override
public DataSourceDescriptor getDataSource(String id) {
DataSourceDescriptor dataSource = super.getDataSource(id);
public DBPDataSourceContainer getDataSource(String id) {
DBPDataSourceContainer dataSource = dataSourceRegistry.getDataSource(id);
if (dataSourceFilter != null && !dataSourceFilter.filter(dataSource)) {
return null;
}
@@ -39,17 +49,17 @@ public class WebDataSourceRegistryProxy extends DataSourceRegistry {
@Nullable
@Override
public DataSourceDescriptor getDataSource(DBPDataSource dataSource) {
public DBPDataSourceContainer getDataSource(DBPDataSource dataSource) {
if (dataSourceFilter != null && !dataSourceFilter.filter(dataSource.getContainer())) {
return null;
}
return super.getDataSource(dataSource);
return dataSourceRegistry.getDataSource(dataSource);
}
@Nullable
@Override
public DataSourceDescriptor findDataSourceByName(String name) {
var dataSource = super.findDataSourceByName(name);
public DBPDataSourceContainer findDataSourceByName(String name) {
var dataSource = dataSourceRegistry.findDataSourceByName(name);
if (dataSource != null) {
if (dataSourceFilter == null || dataSourceFilter.filter(dataSource)) {
return dataSource;
@@ -61,7 +71,7 @@ public class WebDataSourceRegistryProxy extends DataSourceRegistry {
@NotNull
@Override
public List<? extends DBPDataSourceContainer> getDataSourcesByProfile(@NotNull DBWNetworkProfile profile) {
return super.getDataSourcesByProfile(profile)
return dataSourceRegistry.getDataSourcesByProfile(profile)
.stream()
.filter(dataSourceFilter::filter)
.collect(Collectors.toList());
@@ -69,10 +79,233 @@ public class WebDataSourceRegistryProxy extends DataSourceRegistry {
@NotNull
@Override
public List<DataSourceDescriptor> getDataSources() {
return super.getDataSources()
public List<DBPDataSourceContainer> getDataSources() {
return dataSourceRegistry.getDataSources()
.stream()
.filter(dataSourceFilter::filter)
.collect(Collectors.toList());
}
@NotNull
@Override
public DBPDataSourceContainer createDataSource(DBPDriver driver, DBPConnectionConfiguration connConfig) {
return dataSourceRegistry.createDataSource(driver, connConfig);
}
@NotNull
@Override
public DBPDataSourceContainer createDataSource(DBPDataSourceContainer source) {
return dataSourceRegistry.createDataSource(source);
}
@Override
public void addDataSourceListener(@NotNull DBPEventListener listener) {
dataSourceRegistry.addDataSourceListener(listener);
}
@Override
public boolean removeDataSourceListener(@NotNull DBPEventListener listener) {
return dataSourceRegistry.removeDataSourceListener(listener);
}
@Override
public void addDataSource(@NotNull DBPDataSourceContainer dataSource) {
dataSourceRegistry.addDataSource(dataSource);
}
@Override
public void removeDataSource(@NotNull DBPDataSourceContainer dataSource) {
dataSourceRegistry.removeDataSource(dataSource);
}
@Override
public void updateDataSource(@NotNull DBPDataSourceContainer dataSource) {
dataSourceRegistry.updateDataSource(dataSource);
}
@NotNull
@Override
public List<? extends DBPDataSourceFolder> getAllFolders() {
return dataSourceRegistry.getAllFolders();
}
@NotNull
@Override
public List<? extends DBPDataSourceFolder> getRootFolders() {
return dataSourceRegistry.getRootFolders();
}
@Override
public DBPDataSourceFolder getFolder(String path) {
return dataSourceRegistry.getFolder(path);
}
@Override
public DBPDataSourceFolder addFolder(DBPDataSourceFolder parent, String name) {
return dataSourceRegistry.addFolder(parent, name);
}
@Override
public void removeFolder(DBPDataSourceFolder folder, boolean dropContents) {
dataSourceRegistry.removeFolder(folder, dropContents);
}
@Nullable
@Override
public DBSObjectFilter getSavedFilter(String name) {
return dataSourceRegistry.getSavedFilter(name);
}
@NotNull
@Override
public List<DBSObjectFilter> getSavedFilters() {
return dataSourceRegistry.getSavedFilters();
}
@Override
public void updateSavedFilter(DBSObjectFilter filter) {
dataSourceRegistry.updateSavedFilter(filter);
}
@Override
public void removeSavedFilter(String filterName) {
dataSourceRegistry.removeSavedFilter(filterName);
}
@Nullable
@Override
public DBWNetworkProfile getNetworkProfile(String name) {
return dataSourceRegistry.getNetworkProfile(name);
}
@NotNull
@Override
public List<DBWNetworkProfile> getNetworkProfiles() {
return dataSourceRegistry.getNetworkProfiles();
}
@Override
public void updateNetworkProfile(DBWNetworkProfile profile) {
dataSourceRegistry.updateNetworkProfile(profile);
}
@Override
public void removeNetworkProfile(DBWNetworkProfile profile) {
dataSourceRegistry.removeNetworkProfile(profile);
}
@Nullable
@Override
public DBAAuthProfile getAuthProfile(String id) {
return dataSourceRegistry.getAuthProfile(id);
}
@NotNull
@Override
public List<DBAAuthProfile> getAllAuthProfiles() {
return dataSourceRegistry.getAllAuthProfiles();
}
@NotNull
@Override
public List<DBAAuthProfile> getApplicableAuthProfiles(@Nullable DBPDriver driver) {
return dataSourceRegistry.getApplicableAuthProfiles(driver);
}
@Override
public void updateAuthProfile(DBAAuthProfile profile) {
dataSourceRegistry.updateAuthProfile(profile);
}
@Override
public void removeAuthProfile(DBAAuthProfile profile) {
dataSourceRegistry.removeAuthProfile(profile);
}
@Override
public void flushConfig() {
dataSourceRegistry.flushConfig();
}
@Override
public void refreshConfig() {
dataSourceRegistry.refreshConfig();
}
@Override
public Throwable getLastError() {
return dataSourceRegistry.getLastError();
}
@Override
public boolean hasError() {
return dataSourceRegistry.hasError();
}
@Override
public void checkForErrors() throws DBException {
dataSourceRegistry.checkForErrors();
}
@Override
public void notifyDataSourceListeners(DBPEvent event) {
dataSourceRegistry.notifyDataSourceListeners(event);
}
@NotNull
@Override
public ISecurePreferences getSecurePreferences() {
return dataSourceRegistry.getSecurePreferences();
}
@Nullable
@Override
public DBACredentialsProvider getAuthCredentialsProvider() {
return dataSourceRegistry.getAuthCredentialsProvider();
}
@Override
public void dispose() {
dataSourceRegistry.dispose();
}
@Override
public void setAuthCredentialsProvider(DBACredentialsProvider authCredentialsProvider) {
dataSourceRegistry.setAuthCredentialsProvider(authCredentialsProvider);
}
@Override
public Set<DBPDataSourceFolder> getTemporaryFolders() {
return dataSourceRegistry.getTemporaryFolders();
}
@Override
public void loadDataSources(
@NotNull List<DBPDataSourceConfigurationStorage> storages,
@NotNull DataSourceConfigurationManager manager,
boolean refresh,
boolean purgeUntouched
) {
dataSourceRegistry.loadDataSources(storages, manager, refresh, purgeUntouched);
}
@Override
public void saveDataSources() {
dataSourceRegistry.saveDataSources();
}
@Override
public DataSourceConfigurationManager getConfigurationManager() {
return dataSourceRegistry.getConfigurationManager();
}
@Override
public void saveConfigurationToManager(
@NotNull DBRProgressMonitor monitor,
@NotNull DataSourceConfigurationManager configurationManager,
@Nullable Predicate<DBPDataSourceContainer> filter
) {
dataSourceRegistry.saveConfigurationToManager(monitor, configurationManager, filter);
}
}
@@ -22,6 +22,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.auth.*;
import java.time.LocalDateTime;
import java.util.Map;
public abstract class AbstractSessionFederated implements SMSessionFederated {
@@ -56,6 +57,12 @@ public abstract class AbstractSessionFederated implements SMSessionFederated {
return parentSession.getSessionPrincipal();
}
@NotNull
@Override
public LocalDateTime getSessionStart() {
return parentSession.getSessionStart();
}
@Override
public boolean isApplicationSession() {
return false;
@@ -27,6 +27,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceConfigurationStorage;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.auth.SMCredentials;
@@ -259,7 +260,7 @@ public class LocalResourceController implements RMController {
DBPDataSourceRegistry registry = projectMetadata.getDataSourceRegistry();
registry.checkForErrors();
DataSourceConfigurationManagerBuffer buffer = new DataSourceConfigurationManagerBuffer();
((DataSourceRegistry)registry).saveConfigurationToManager(new VoidProgressMonitor(), buffer, null);
((DataSourcePersistentRegistry) registry).saveConfigurationToManager(new VoidProgressMonitor(), buffer, null);
registry.checkForErrors();
return new String(buffer.getData(), StandardCharsets.UTF_8);
}
@@ -267,22 +268,22 @@ public class LocalResourceController implements RMController {
@Override
public void saveProjectDataSources(@NotNull String projectId, @NotNull String configuration) throws DBException {
final DBPProject project = getProjectMetadata(projectId);
final DataSourceRegistry registry = (DataSourceRegistry) project.getDataSourceRegistry();
final DBPDataSourceRegistry registry = project.getDataSourceRegistry();
final DBPDataSourceConfigurationStorage storage = new DataSourceMemoryStorage(configuration.getBytes(StandardCharsets.UTF_8));
final DataSourceConfigurationManager manager = new DataSourceConfigurationManagerBuffer();
registry.loadDataSources(List.of(storage), manager, true, false);
((DataSourcePersistentRegistry) registry).loadDataSources(List.of(storage), manager, true, false);
registry.checkForErrors();
registry.saveDataSources();
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
}
@Override
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
final DBPProject project = getProjectMetadata(projectId);
final DataSourceRegistry registry = (DataSourceRegistry) project.getDataSourceRegistry();
final DBPDataSourceRegistry registry = project.getDataSourceRegistry();
for (String dataSourceId : dataSourceIds) {
final DataSourceDescriptor dataSource = registry.getDataSource(dataSourceId);
final DBPDataSourceContainer dataSource = registry.getDataSource(dataSourceId);
if (dataSource != null) {
registry.removeDataSource(dataSource);
@@ -16,10 +16,7 @@
*/
package io.cloudbeaver.model.session;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.DataSourceFilter;
import io.cloudbeaver.VirtualProjectImpl;
import io.cloudbeaver.*;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebServerMessage;
@@ -70,6 +67,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@@ -160,6 +160,12 @@ public class WebSession extends AbstractSessionPersistent implements SMSession,
return id;
}
@NotNull
@Override
public LocalDateTime getSessionStart() {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(createTime), ZoneId.systemDefault());
}
public WebApplication getApplication() {
return application;
}
@@ -357,7 +363,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession,
this,
filter);
DBPDataSourceRegistry dataSourceRegistry = sessionProject.getDataSourceRegistry();
((DataSourceRegistry) dataSourceRegistry).setAuthCredentialsProvider(this);
dataSourceRegistry.setAuthCredentialsProvider(this);
addSessionProject(sessionProject);
if (!project.isShared() || application.isConfigurationMode()) {
this.defaultProject = sessionProject;
@@ -29,6 +29,7 @@ import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class WebTestUtils {
@@ -42,12 +43,24 @@ public class WebTestUtils {
public static Map<String, Object> doPost(String apiUrl, String input, HttpClient client) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
return doPostWithHeaders(apiUrl, input, client, List.of());
}
public static Map<String, Object> doPostWithHeaders(
String apiUrl,
String input,
HttpClient client,
List<String> headers
) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString(input))
.header("Content-Type", "application/json")
.build();
.header("Content-Type", "application/json");
if (!headers.isEmpty()) {
requestBuilder.headers(headers.toArray(String[]::new));
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
@@ -198,6 +198,7 @@ type DriverInfo {
embedded: Boolean
enabled: Boolean!
requiresServerName: Boolean
requiresDatabaseName: Boolean
licenseRequired: Boolean
license: String
@@ -34,6 +34,7 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPDataSourceFolder;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
@@ -42,7 +43,6 @@ import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.navigator.DBNProject;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.DataSourceProviderDescriptor;
@@ -169,7 +169,6 @@ public class WebServiceUtils extends WebCommonUtils {
if (config.getServerName() != null) {
dsConfig.setServerName(config.getServerName());
}
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
}
if (config.getProperties() != null) {
Map<String, String> newProps = new LinkedHashMap<>();
@@ -197,6 +196,9 @@ public class WebServiceUtils extends WebCommonUtils {
if (config.getConfigurationType() != null) {
dsConfig.setConfigurationType(config.getConfigurationType());
}
if (CommonUtils.isEmpty(config.getUrl())) {
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
}
// Save network handlers
if (config.getNetworkHandlersConfig() != null) {
for (WebNetworkHandlerConfigInput nhc : config.getNetworkHandlersConfig()) {
@@ -340,6 +342,9 @@ public class WebServiceUtils extends WebCommonUtils {
DBNModel.updateConfigAndRefreshDatabases(projectNode.getDatabases());
}
public static boolean isGlobalProject(DBPProject project) {
return CommonUtils.equalObjects(CBApplication.getInstance().getDefaultProjectName(), project.getName());
}
}
@@ -44,6 +44,7 @@ import java.util.Map;
public class WebDatabaseDriverConfig {
public static final String URL_SERVER_FIELD = "{server}";
public static final String URL_DATABASE_FIELD = ".*(?:\\{(?:database|file|folder)}).*";
private final WebSession webSession;
private final DBPDriver driver;
private String id;
@@ -249,4 +250,13 @@ public class WebDatabaseDriverConfig {
public DBPDriverConfigurationType[] getConfigurationTypes() {
return driver.getSupportedConfigurationTypes().toArray(DBPDriverConfigurationType[]::new);
}
@Property
public boolean getRequiresDatabaseName() {
if (driver.getSampleURL() == null) {
return false;
}
return driver.getSampleURL().matches(URL_DATABASE_FIELD);
}
}
@@ -35,7 +35,9 @@ import java.util.Map;
* Application configuration
*/
public class CBAppConfig extends BaseAuthWebAppConfiguration implements WebAuthConfiguration {
public static final DataSourceNavigatorSettings DEFAULT_VIEW_SETTINGS = DataSourceNavigatorSettings.PRESET_FULL.getSettings();
public static final DataSourceNavigatorSettings.Preset PRESET_WEB = new DataSourceNavigatorSettings.Preset("web", "Web", "Default view");
public static final DataSourceNavigatorSettings DEFAULT_VIEW_SETTINGS = PRESET_WEB.getSettings();
private boolean supportsCustomConnections;
private boolean supportsConnectionBrowser;
@@ -21,17 +21,14 @@ import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.app.BaseWebApplication;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebHandlerRegistry;
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
import io.cloudbeaver.server.CBAppConfig;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.core.DBWServiceCore;
import io.cloudbeaver.utils.WebDataSourceUtils;
import io.cloudbeaver.utils.WebConnectionFolderUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -55,7 +52,6 @@ import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
@@ -294,6 +290,8 @@ public class WebServiceCore implements DBWServiceCore {
connectionInfo.clearSavedCredentials();
}
// Mark all specified network configs as saved
boolean[] saveConfig = new boolean[1];
if (networkCredentials != null) {
networkCredentials.forEach(c -> {
if (CommonUtils.toBoolean(c.isSavePassword()) && !CommonUtils.isEmpty(c.getUserName())) {
@@ -302,7 +300,7 @@ public class WebServiceCore implements DBWServiceCore {
handlerCfg.setUserName(c.getUserName());
handlerCfg.setPassword(c.getPassword());
handlerCfg.setSavePassword(true);
dataSourceContainer.persistConfiguration();
saveConfig[0] = true;
}
}
});
@@ -316,6 +314,13 @@ public class WebServiceCore implements DBWServiceCore {
true);
WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo, dataSourceContainer, dataSourceContainer.getConnectionConfiguration());
saveConfig[0] = true;
}
if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) {
// Do not flush config for global project (only admin can do it - CB-2415)
saveConfig[0] = false;
}
if (saveConfig[0]) {
dataSourceContainer.persistConfiguration();
}
@@ -160,7 +160,7 @@ public class WebNavigatorNodeInfo {
} else if (node instanceof DBNLocalFolder) {
DataSourceFolder folder = (DataSourceFolder) ((DBNLocalFolder) node).getFolder();
String projectName = folder.getDataSourceRegistry().getProject().getName();
Set<DBPDataSourceFolder> tempFolders = ((DataSourceRegistry) folder.getDataSourceRegistry()).getTemporaryFolders();
Set<DBPDataSourceFolder> tempFolders = folder.getDataSourceRegistry().getTemporaryFolders();
isShared = !projectName.equals(session.getUserId()) || tempFolders.contains(folder);
}
if (isShared) {
@@ -660,7 +660,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
String connectionId
) throws DBWebException {
DBPProject globalProject = webSession.getProjectById(projectId);
if (!CommonUtils.equalObjects(globalProject.getName(), CBApplication.getInstance().getDefaultProjectName())) {
if (!WebServiceUtils.isGlobalProject(globalProject)) {
throw new DBWebException("Project '" + projectId + "'is not global");
}
try {
@@ -685,7 +685,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
@NotNull List<String> subjects
) throws DBWebException {
DBPProject globalProject = webSession.getProjectById(projectId);
if (!CommonUtils.equalObjects(globalProject.getName(), CBApplication.getInstance().getDefaultProjectName())) {
if (!WebServiceUtils.isGlobalProject(globalProject)) {
throw new DBWebException("Project '" + projectId + "'is not global");
}
DBPDataSourceContainer dataSource = getGlobalRegistry(webSession).getDataSource(connectionId);
@@ -16,31 +16,26 @@
*/
package io.cloudbeaver.service.auth;
import io.cloudbeaver.DBWUserIdentity;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.auth.SMAuthProviderExternal;
import io.cloudbeaver.auth.provider.rp.RPAuthProvider;
import io.cloudbeaver.model.session.WebAuthInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
import io.cloudbeaver.model.session.WebSessionAuthProcessor;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.service.DBWSessionHandler;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.exception.SMException;
import org.jkiss.dbeaver.registry.auth.AuthProviderDescriptor;
import org.jkiss.dbeaver.registry.auth.AuthProviderRegistry;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -70,7 +65,6 @@ public class RPSessionHandler implements DBWSessionHandler {
String userName = request.getHeader(RPAuthProvider.X_USER);
String roles = request.getHeader(RPAuthProvider.X_ROLE);
List<String> userRoles = roles == null ? Collections.emptyList() : List.of(roles.split("\\|"));
SMSession authSession;
if (userName != null) {
try {
Map<String, Object> credentials = new HashMap<>();
@@ -79,39 +73,18 @@ public class RPSessionHandler implements DBWSessionHandler {
sessionParameters.put(SMConstants.SESSION_PARAM_TRUSTED_USER_ROLES, userRoles);
Map<String, Object> userCredentials = authProviderExternal.authExternalUser(
webSession.getProgressMonitor(), sessionParameters, credentials);
String currentSmSessionId = webSession.getUser() == null ? null : webSession.getUserContext().getSmSessionId();
try {
SMAuthInfo smAuthInfo = securityController.authenticate(
webSession.getSessionId(),
webSession.getUserContext().getSmSessionId(),
currentSmSessionId,
sessionParameters,
WebSession.CB_SESSION_TYPE, authProvider.getId(), null, userCredentials);
webSession.updateSMAuthInfo(smAuthInfo);
new WebSessionAuthProcessor(webSession, smAuthInfo, false).authenticateSession();
} catch (SMException e) {
log.debug("Error during user authentication", e);
throw e;
}
WebUser user = webSession.getUser();
DBWUserIdentity userIdentity = authProviderExternal.getUserIdentity(
webSession.getProgressMonitor(), sessionParameters, credentials);
if (CommonUtils.isEmpty(user.getDisplayName())) {
user.setDisplayName(userIdentity.getDisplayName());
}
authSession = authProviderExternal.openSession(
webSession.getProgressMonitor(),
webSession,
sessionParameters,
userCredentials);
WebAuthInfo authInfo = new WebAuthInfo(
webSession,
user,
authProvider,
userIdentity,
authSession,
OffsetDateTime.now());
authInfo.setMessage("Authenticated with " + authProvider.getLabel() + " provider");
webSession.addAuthInfo(authInfo);
} catch (Exception e) {
throw new DBWebException("Error", e);
}
@@ -24,6 +24,8 @@ import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.auth.SMSessionPrincipal;
import java.time.LocalDateTime;
/**
* Local auth provider
*/
@@ -65,6 +67,12 @@ public class LocalAuthSession implements SMSession {
return webSession.getSessionId();
}
@NotNull
@Override
public LocalDateTime getSessionStart() {
return webSession.getSessionStart();
}
@Override
public boolean isApplicationSession() {
return false;
@@ -1,15 +1,23 @@
package io.cloudbeaver.test.platform;
import io.cloudbeaver.auth.provider.rp.RPAuthProvider;
import io.cloudbeaver.utils.WebTestUtils;
import org.jkiss.dbeaver.model.auth.SMAuthStatus;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class AuthenticationTest {
public static final String GQL_TEMPLATE_OPEN_SESSION = "openSession.json";
public static final String GQL_TEMPLATE_ACTIVE_USER = "activeUser.json";
public static final String REVERSE_PROXY_TEST_USER = "reverseProxyTestUser";
@Test
public void testLoginUser() throws Exception {
@@ -18,4 +26,38 @@ public class AuthenticationTest {
client, CEServerTestSuite.getScriptsPath(), CEServerTestSuite.GQL_API_URL);
Assert.assertEquals(SMAuthStatus.SUCCESS.name(), JSONUtils.getString(authInfo, "authStatus"));
}
@Test
public void testReverseProxyAnonymousModeLogin() throws Exception {
HttpClient client = CEServerTestSuite.getClient();
Map<String, Object> sessionInfo = openSession(client);
Assert.assertTrue(JSONUtils.getBoolean(sessionInfo, "valid"));
Map<String, Object> activeUser = getActiveUser(client);
Assert.assertEquals(REVERSE_PROXY_TEST_USER, JSONUtils.getString(activeUser, "userId"));
}
private Map<String, Object> openSession(HttpClient client) throws Exception {
Map<String, Object> data = doPostQuery(client, GQL_TEMPLATE_OPEN_SESSION);
if (data != null) {
return JSONUtils.getObject(data, "session");
}
return Collections.emptyMap();
}
private Map<String, Object> getActiveUser(HttpClient client) throws Exception {
Map<String, Object> data = doPostQuery(client, GQL_TEMPLATE_ACTIVE_USER);
if (data != null) {
return JSONUtils.getObject(data, "user");
}
return Collections.emptyMap();
}
private Map<String, Object> doPostQuery(HttpClient client, String gqlScript) throws Exception {
String input = WebTestUtils.readScriptTemplate(gqlScript, CEServerTestSuite.getScriptsPath());
List<String> headers = List.of(RPAuthProvider.X_USER, REVERSE_PROXY_TEST_USER, RPAuthProvider.X_ROLE, "user");
Map<String, Object> map = WebTestUtils.doPostWithHeaders(CEServerTestSuite.GQL_API_URL, input, client, headers);
return JSONUtils.getObjectOrNull(map, "data");
}
}
@@ -1,6 +1,7 @@
{
server: {
serverPort: "${CLOUDBEAVER_TEST_PORT:18978}",
serverName: "CloudBeaver CE Test Server",
workspaceLocation: "workspace",
contentRoot: "workspace/web",
@@ -36,8 +37,10 @@
anonymousAccessEnabled: true,
anonymousUserRole: "user",
supportsCustomConnections: true,
enableReverseProxyAuth: true,
enabledAuthProviders: [
"local"
"local",
"reverseProxy"
],
enabledFeatures: [
@@ -0,0 +1,4 @@
{
"query": "\n query activeUser {\n user: activeUser {\n userId\n }\n}\n ",
"operationName": "activeUser"
}
@@ -0,0 +1,7 @@
{
"query": "\n mutation openSession($defaultLocale: String) {\n session: openSession(defaultLocale: $defaultLocale) {\n valid\n }\n}\n ",
"variables": {
"defaultLocale": "en"
},
"operationName": "openSession"
}
@@ -90,7 +90,7 @@ const styles = css`
width: 100%;
height: 100%;
}
}
}
}
}
Icon {
@@ -112,12 +112,12 @@ const styles = css`
width: 16px;
height: 16px;
margin-left: 12px;
& IconOrImage {
width: 100%;
height: 100%;
}
&:not(:empty) + input {
padding-left: 34px !important;
}
@@ -131,8 +131,8 @@ type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>,
searchable?: boolean;
defaultValue?: TKey;
loading?: boolean;
keySelector: (item: TValue, index: number) => TKey;
valueSelector: (item: TValue) => string;
keySelector?: (item: TValue, index: number) => TKey;
valueSelector?: (item: TValue) => string;
titleSelector?: (item: TValue) => string | undefined;
iconSelector?: (item: TValue) => string | React.ReactElement | undefined;
isDisabled?: (item: TValue) => boolean;
@@ -0,0 +1,50 @@
import styled, { css } from 'reshadow';
import { Combobox } from '@cloudbeaver/core-blocks';
import type { CrsKey } from './LeafletMap';
const styles = css`
root {
display: inline-flex;
align-items: center;
font-size: 12px;
}
label {
margin-right: 4px;
flex-grow: 0;
flex-shrink: 1;
}
Combobox {
width: 120px;
flex: 0 0 auto;
}
`;
interface Props {
value: CrsKey;
onChange: (value: CrsKey) => void;
}
const items: CrsKey[] = [
'Simple',
'EPSG3395',
'EPSG3857',
'EPSG4326',
'EPSG900913',
];
export function CrsInput(props: Props) {
return styled(styles)(
<root>
<label>CRS:</label>
<Combobox
items={items}
value={props.value}
onSelect={props.onChange}
/>
</root>
);
}
@@ -5,18 +5,51 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useState } from 'react';
import styled, { css } from 'reshadow';
import wellknown from 'wellknown';
import { TextPlaceholder } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel, ResultSetViewAction, ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer';
import { IGeoJSONFeature, IAssociatedValue, LeafletMap } from './LeafletMap';
import { CrsInput } from './CrsInput';
import { IGeoJSONFeature, IAssociatedValue, LeafletMap, CrsKey } from './LeafletMap';
import { ResultSetGISAction } from './ResultSetGISAction';
function getCrsKey(feature?: IGeoJSONFeature): CrsKey {
switch (feature?.properties.srid) {
case 3857:
return 'EPSG3857';
case 4326:
return 'EPSG4326';
case 3395:
return 'EPSG3395';
case 900913:
return 'EPSG900913';
default:
return 'EPSG3857';
}
}
const styles = css`
root {
display: flex;
flex-direction: column;
width: 100%;
}
map {
flex: 1 1 auto;
}
toolbar {
margin-top: 8px;
flex: 0 0 auto;
}
`;
interface Props {
model: IDatabaseDataModel<any, IDatabaseResultSet>;
resultIndex: number;
@@ -87,11 +120,25 @@ export const GISValuePresentation = observer<Props>(function GISValuePresentatio
return values;
}, [view]);
const defaultCrsKey = getCrsKey(parsedGISData[0]);
const [crsKey, setCrsKey] = useState(defaultCrsKey);
if (!parsedGISData.length) {
return <TextPlaceholder>{translate('gis_presentation_placeholder')}</TextPlaceholder>;
}
return (
<LeafletMap geoJSON={parsedGISData} getAssociatedValues={getAssociatedValues} />
return styled(styles)(
<root>
<map>
<LeafletMap key={crsKey} geoJSON={parsedGISData} crsKey={crsKey} getAssociatedValues={getAssociatedValues} />
</map>
<toolbar>
<CrsInput
value={crsKey}
onChange={setCrsKey}
/>
</toolbar>
</root>
);
});
@@ -42,8 +42,11 @@ interface IBaseTile extends TileLayerProps {
checked?: boolean;
}
export type CrsKey = 'Simple' | 'EPSG3857' | 'EPSG4326' | 'EPSG3395' | 'EPSG900913';
interface Props {
geoJSON: IGeoJSONFeature[];
crsKey: CrsKey;
getAssociatedValues: (cell: IResultSetElementKey) => IAssociatedValue[];
}
@@ -87,17 +90,17 @@ function polyStyle() {
};
}
function getCRS(feature?: IGeoJSONFeature): leaflet.CRS {
switch (feature?.properties.srid) {
case 0:
function getCRS(crsKey: CrsKey): leaflet.CRS {
switch (crsKey) {
case 'Simple':
return leaflet.CRS.Simple;
case 3857:
case 'EPSG3857':
return leaflet.CRS.EPSG3857;
case 4326:
case 'EPSG4326':
return leaflet.CRS.EPSG4326;
case 3395:
case 'EPSG3395':
return leaflet.CRS.EPSG3395;
case 900913:
case 'EPSG900913':
return leaflet.CRS.EPSG900913;
default:
return leaflet.CRS.EPSG3857;
@@ -111,14 +114,14 @@ const styles = css`
}
`;
export const LeafletMap: React.FC<Props> = function LeafletMap({ geoJSON, getAssociatedValues }) {
export const LeafletMap: React.FC<Props> = function LeafletMap({ geoJSON, crsKey, getAssociatedValues }) {
const splitContext = useSplit();
const translate = useTranslate();
const [mapRef, setMapRef] = useState<leaflet.Map | null>(null);
const [geoJSONLayerRef, setGeoJSONLayerRef] = useState<leaflet.GeoJSON | null>(null);
const crs = getCRS(geoJSON[0]);
const crs = getCRS(crsKey);
const onEachFeature = useCallback((feature: IGeoJSONFeature, layer: leaflet.Layer) => {
const associatedValues = getAssociatedValues(feature.properties.associatedCell);