Skip to content

Commit

Permalink
Merge branch 'devel' into feat/cb-4236/script-reopen
Browse files Browse the repository at this point in the history
  • Loading branch information
dariamarutkina authored Jan 25, 2024
2 parents 425543d + 3e93752 commit 2a178c4
Show file tree
Hide file tree
Showing 92 changed files with 1,432 additions and 1,826 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog


### 23.3.2. 2024-01-22
### 23.3.3. 2024-01-22
- Added password policy for the local authorization. Password parameters can be set in the configuration file;
- The 'Keep alive' option has been added to the connection settings to keep the connection active even in case of inactivity;
- Added ability to display full text for a string data type in value panel;
Expand Down
2 changes: 1 addition & 1 deletion deploy/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:23.04
FROM ubuntu:23.10

MAINTAINER DBeaver Corp, [email protected]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,12 @@
* Federated auth provider.
* Provides links to external auth resource
*/
public interface SMAuthProviderFederated {
public interface SMAuthProviderFederated extends SMSignOutLinkProvider {

@NotNull
String getSignInLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;

/**
* @return a common link for logout, not related with the user context
*/
@NotNull
String getCommonSignOutLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;

@Override
default String getUserSignOutLink(
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 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.auth;

import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;

import java.util.Map;

public interface SMSignOutLinkProvider {

/**
* @return a common link for logout, not related with the user context
*/
@NotNull
String getCommonSignOutLink(String id, @NotNull Map<String, Object> providerConfig) throws DBException;

String getUserSignOutLink(
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials
) throws DBException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.cloudbeaver.auth.CBAuthConstants;
import io.cloudbeaver.auth.SMAuthProviderFederated;
import io.cloudbeaver.auth.SMSignOutLinkProvider;
import io.cloudbeaver.utils.WebAppUtils;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
Expand Down Expand Up @@ -85,8 +86,8 @@ private String buildRedirectUrl(String baseUrl) {
@Property
public String getSignOutLink() throws DBException {
SMAuthProvider<?> instance = providerDescriptor.getInstance();
return instance instanceof SMAuthProviderFederated
? ((SMAuthProviderFederated) instance).getCommonSignOutLink(getId(), config.getParameters())
return instance instanceof SMSignOutLinkProvider
? ((SMSignOutLinkProvider) instance).getCommonSignOutLink(getId(), config.getParameters())
: null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ type DatabaseObjectInfo {
type NavigatorNodeInfo {
# Node ID - generally a full path to the node from root of tree
id: ID!
# # Node URI - a unique path to a node including all parent nodes
# uri: ID! @since(version: "23.3.1")
# Node URI - a unique path to a node including all parent nodes
# uri: ID! @since(version: "23.3.1")
# Node human readable name
name: String
#Node full name
Expand Down
3 changes: 3 additions & 0 deletions server/bundles/io.cloudbeaver.service.auth/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<service id="auth" label="User authentication" description="User authentication services" class="io.cloudbeaver.service.auth.WebServiceBindingAuth">

</service>
<service id="reverse.configurator" label="Reverse Proxy Configurator" description="Reverse proxy configurator"
class="io.cloudbeaver.service.auth.ReverseProxyConfigurator">
</service>
</extension>
<extension point="io.cloudbeaver.handler">
<sessionHandler id="RPSessionHandler" class="io.cloudbeaver.service.auth.RPSessionHandler"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 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.service.auth;

interface RPConstants {
String PARAM_LOGOUT_URL = "logout-url";
String PARAM_USER = "user-header";
String PARAM_TEAM = "team-header";
String PARAM_FIRST_NAME = "first-name-header";
String PARAM_LAST_NAME = "last-name-header";
String PARAM_ROLE_NAME = "role-header";
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMStandardMeta;
Expand All @@ -41,10 +42,10 @@

import java.io.IOException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class RPSessionHandler implements DBWSessionHandler {

Expand All @@ -62,22 +63,38 @@ public boolean handleSessionOpen(WebSession webSession, HttpServletRequest reque
return false;
}

public void reverseProxyAuthentication(@NotNull HttpServletRequest request, @NotNull WebSession webSession) throws DBWebException {
public void reverseProxyAuthentication(@NotNull HttpServletRequest request, @NotNull WebSession webSession) throws DBException {
SMController securityController = webSession.getSecurityController();
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(RPAuthProvider.AUTH_PROVIDER);
if (authProvider == null) {
throw new DBWebException("Auth provider " + RPAuthProvider.AUTH_PROVIDER + " not found");
}
SMAuthProviderExternal<?> authProviderExternal = (SMAuthProviderExternal<?>) authProvider.getInstance();
String userName = request.getHeader(RPAuthProvider.X_USER);
String teams = request.getHeader(RPAuthProvider.X_TEAM);
SMAuthProviderCustomConfiguration configuration = WebAppUtils.getWebAuthApplication()
.getAuthConfiguration()
.getAuthCustomConfigurations()
.stream()
.filter(p -> p.getProvider().equals(authProvider.toString()))
.findFirst()
.orElse(null);
Map<String, Object> paramConfigMap = new HashMap<>();
if (configuration != null) {
authProvider.getConfigurationParameters().forEach(p ->
paramConfigMap.put(p.getId(), configuration.getParameters().get(p.getId())
));
}
String userName = request.getHeader(
resolveParam(paramConfigMap.get(RPConstants.PARAM_USER), RPAuthProvider.X_USER)
);
String teams = request.getHeader(resolveParam(paramConfigMap.get(RPConstants.PARAM_TEAM), RPAuthProvider.X_TEAM));
if (CommonUtils.isEmpty(teams)) {
// backward compatibility
teams = request.getHeader(RPAuthProvider.X_ROLE);
}
String role = request.getHeader(RPAuthProvider.X_ROLE_TE);
String firstName = request.getHeader(RPAuthProvider.X_FIRST_NAME);
String lastName = request.getHeader(RPAuthProvider.X_LAST_NAME);
String role = request.getHeader(resolveParam(paramConfigMap.get(RPConstants.PARAM_ROLE_NAME), RPAuthProvider.X_ROLE_TE));
String firstName = request.getHeader(resolveParam(paramConfigMap.get(RPConstants.PARAM_FIRST_NAME), RPAuthProvider.X_FIRST_NAME));
String lastName = request.getHeader(resolveParam(paramConfigMap.get(RPConstants.PARAM_LAST_NAME), RPAuthProvider.X_LAST_NAME));
String logoutUrl = Objects.requireNonNull(configuration).getParameter(RPConstants.PARAM_LOGOUT_URL);
List<String> userTeams = teams == null ? Collections.emptyList() : List.of(teams.split("\\|"));
if (userName != null) {
try {
Expand All @@ -89,6 +106,9 @@ public void reverseProxyAuthentication(@NotNull HttpServletRequest request, @Not
if (!CommonUtils.isEmpty(lastName)) {
credentials.put(SMStandardMeta.META_LAST_NAME, lastName);
}
if (CommonUtils.isNotEmpty(logoutUrl)) {
credentials.put("logoutUrl", logoutUrl);
}
Map<String, Object> sessionParameters = webSession.getSessionParameters();
sessionParameters.put(SMConstants.SESSION_PARAM_TRUSTED_USER_TEAMS, userTeams);
sessionParameters.put(SMConstants.SESSION_PARAM_TRUSTED_USER_ROLE, role);
Expand All @@ -102,7 +122,7 @@ public void reverseProxyAuthentication(@NotNull HttpServletRequest request, @Not
webSession.getSessionId(),
currentSmSessionId,
sessionParameters,
WebSession.CB_SESSION_TYPE, authProvider.getId(), null, userCredentials);
WebSession.CB_SESSION_TYPE, authProvider.getId(), configuration.getId(), userCredentials);
new WebSessionAuthProcessor(webSession, smAuthInfo, false).authenticateSession();
log.debug(MessageFormat.format(
"Successful reverse proxy authentication: user ''{0}'' with teams {1}", userName, userTeams));
Expand All @@ -120,4 +140,11 @@ public void reverseProxyAuthentication(@NotNull HttpServletRequest request, @Not
public boolean handleSessionClose(WebSession webSession) throws DBException, IOException {
return false;
}

private String resolveParam(Object value, String defaultValue) {
if (value != null && !value.toString().isEmpty()) {
return value.toString();
}
return defaultValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 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.service.auth;

import io.cloudbeaver.auth.provider.rp.RPAuthProvider;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.app.WebApplication;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;

import java.util.HashMap;
import java.util.Map;

public class ReverseProxyConfigurator implements DBWServiceServerConfigurator {
private static final Log log = Log.getLog(ReverseProxyConfigurator.class);

@Override
public void configureServer(
@NotNull WebApplication application,
@Nullable WebSession session,
@NotNull WebAppConfiguration appConfig
) throws DBException {
}

@Override
public void migrateConfigurationIfNeeded(@NotNull WebApplication application) throws DBException {
if (migrationNotNeeded(application)) {
return;
}
migrateConfiguration(application);
}

@Override
public void reloadConfiguration(@NotNull WebAppConfiguration appConfig) throws DBException {

}

private void migrateConfiguration(
@NotNull WebApplication application
) {
if (!(application instanceof WebAuthApplication authApplication)) {
return;
}

SMAuthProviderCustomConfiguration smReverseProxyProviderConfiguration =
authApplication.getAuthConfiguration().getAuthProviderConfiguration(RPAuthProvider.AUTH_PROVIDER);
if (smReverseProxyProviderConfiguration == null) {
smReverseProxyProviderConfiguration = new SMAuthProviderCustomConfiguration(RPAuthProvider.AUTH_PROVIDER);
smReverseProxyProviderConfiguration.setProvider(RPAuthProvider.AUTH_PROVIDER);
smReverseProxyProviderConfiguration.setDisplayName("Reverse Proxy");
smReverseProxyProviderConfiguration.setDescription(
"Automatically created provider after changing Reverse Proxy configuration way in 23.3.4 version"
);
smReverseProxyProviderConfiguration .setIconURL("");
Map<String, Object> parameters = new HashMap<>();
parameters.put(RPConstants.PARAM_USER, RPAuthProvider.X_USER);
parameters.put(RPConstants.PARAM_TEAM, RPAuthProvider.X_TEAM);
parameters.put(RPConstants.PARAM_FIRST_NAME, RPAuthProvider.X_FIRST_NAME);
parameters.put(RPConstants.PARAM_LAST_NAME, RPAuthProvider.X_LAST_NAME);
smReverseProxyProviderConfiguration.setParameters(parameters);
authApplication.getAuthConfiguration().addAuthProviderConfiguration(smReverseProxyProviderConfiguration );
try {
authApplication.flushConfiguration();
} catch (Exception e) {
log.error("Failed to save server configuration", e);
}
}
}

private boolean migrationNotNeeded(@NotNull WebApplication application) {
if (!(application instanceof WebAuthApplication authApplication)) {
return true;
}

if (!authApplication.getAuthConfiguration().isAuthProviderEnabled(RPAuthProvider.AUTH_PROVIDER)) {
log.debug("Reverse proxy provider disabled, migration not needed");
return true;
}

boolean isReverseProxyConfigured = authApplication.getAuthConfiguration()
.getAuthCustomConfigurations().stream()
.anyMatch(p -> p.getProvider().equals(RPAuthProvider.AUTH_PROVIDER));

if (isReverseProxyConfigured) {
log.debug("Reverse proxy provider already exist, migration not needed");
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.SMAuthProviderFederated;
import io.cloudbeaver.auth.SMSignOutLinkProvider;
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebAuthInfo;
Expand Down Expand Up @@ -148,18 +149,26 @@ public WebLogoutInfo authLogout(
var cbApp = CBApplication.getInstance();
for (WebAuthInfo removedInfo : removedInfos) {
if (removedInfo.getAuthProviderDescriptor()
.getInstance() instanceof SMAuthProviderFederated federatedProvider
&& removedInfo.getAuthSession() instanceof SMSessionExternal externalSession
.getInstance() instanceof SMSignOutLinkProvider provider
&& removedInfo.getAuthSession() != null
) {
var providerConfig =
cbApp.getAuthConfiguration().getAuthProviderConfiguration(removedInfo.getAuthConfiguration());
if (providerConfig == null) {
log.warn(removedInfo.getAuthConfiguration() + " provider configuration wasn't found");
continue;
}
String logoutUrl = federatedProvider.getUserSignOutLink(providerConfig,
externalSession.getAuthParameters());
logoutUrls.add(logoutUrl);
String logoutUrl;
if (removedInfo.getAuthSession() instanceof SMSessionExternal externalSession) {
logoutUrl = provider.getUserSignOutLink(providerConfig,
externalSession.getAuthParameters());
} else {
logoutUrl = provider.getUserSignOutLink(providerConfig,
Map.of());
}
if (CommonUtils.isNotEmpty(logoutUrl)) {
logoutUrls.add(logoutUrl);
}
}
}
return new WebLogoutInfo(logoutUrls);
Expand Down
Loading

0 comments on commit 2a178c4

Please sign in to comment.