mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13658510a9 | ||
|
|
05b04fc5b2 | ||
|
|
a1f0e1e542 | ||
|
|
d00009ca64 | ||
|
|
2e3ae65e82 | ||
|
|
deee3d7a7b | ||
|
|
1c79a0bb74 | ||
|
|
15dde2ece4 |
+6
-5
@@ -31,8 +31,6 @@ import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMResource;
|
||||
import org.jkiss.dbeaver.model.rm.RMResourceType;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.IOUtils;
|
||||
@@ -44,7 +42,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
|
||||
private final RMResource resource;
|
||||
|
||||
DBNResourceManagerResource(DBNNode parentNode, RMResource resource) {
|
||||
DBNResourceManagerResource(@NotNull DBNNode parentNode, @NotNull RMResource resource) {
|
||||
super(parentNode);
|
||||
this.resource = resource;
|
||||
}
|
||||
@@ -71,6 +69,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
if (resource.isFolder()) {
|
||||
/*
|
||||
if (getParentNode() instanceof DBNResourceManagerResource) {
|
||||
return DBIcon.TREE_FOLDER;
|
||||
}
|
||||
@@ -82,11 +81,12 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
if (folderResType != null) {
|
||||
return folderResType.getFolderIcon();
|
||||
}
|
||||
*/
|
||||
return DBIcon.TREE_FOLDER;
|
||||
} else {
|
||||
var fileExtension = IOUtils.getFileExtension(getNodeDisplayName());
|
||||
if (!CommonUtils.isEmpty(fileExtension)) {
|
||||
RMProject project = getProjectNode();
|
||||
RMProject project = getRmProject();
|
||||
if (project != null) {
|
||||
for (RMResourceType rt : project.getResourceTypes()) {
|
||||
if (ArrayUtils.contains(rt.getFileExtensions(), fileExtension)) {
|
||||
@@ -99,7 +99,8 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
}
|
||||
}
|
||||
|
||||
private RMProject getProjectNode() {
|
||||
@Nullable
|
||||
public RMProject getRmProject() {
|
||||
for (DBNNode node = this; node != null; node = node.getParentNode()) {
|
||||
if (node instanceof DBNResourceManagerProject) {
|
||||
return ((DBNResourceManagerProject) node).getProject();
|
||||
|
||||
+8
-8
@@ -102,7 +102,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void addSessionError(Throwable exception);
|
||||
public abstract void addSessionError(@NotNull Throwable exception);
|
||||
|
||||
public void addEventHandler(@NotNull CBWebSessionEventHandler handler) {
|
||||
synchronized (sessionEventHandlers) {
|
||||
@@ -116,11 +116,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
|
||||
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
|
||||
return userContext.refresh(smAuthInfo);
|
||||
}
|
||||
|
||||
public synchronized void refreshUserData() {
|
||||
public void refreshUserData() {
|
||||
try {
|
||||
userContext.refreshPermissions();
|
||||
if (userContext.isAuthorizedInSecurityManager()) {
|
||||
@@ -150,7 +150,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
return workspace.getAuthContext();
|
||||
}
|
||||
|
||||
protected void clearSessionContext() {
|
||||
protected synchronized void clearSessionContext() {
|
||||
this.workspace.getAuthContext().clear();
|
||||
this.workspace.getAuthContext().addSession(this);
|
||||
}
|
||||
@@ -176,12 +176,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
return lastAccessTime;
|
||||
}
|
||||
|
||||
public synchronized void touchSession() {
|
||||
public void touchSession() {
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized WebUserContext getUserContext() {
|
||||
public WebUserContext getUserContext() {
|
||||
return userContext;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
this.eventsFilter = eventsFilter;
|
||||
}
|
||||
|
||||
public boolean isProjectAccessible(String projectId) {
|
||||
public boolean isProjectAccessible(@NotNull String projectId) {
|
||||
return userContext.getAccessibleProjectIds().contains(projectId);
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
userContext.getAccessibleProjectIds().remove(projectId);
|
||||
}
|
||||
|
||||
public abstract void addSessionMessage(WebServerMessage message);
|
||||
public abstract void addSessionMessage(@NotNull WebServerMessage message);
|
||||
|
||||
@Property
|
||||
public boolean isValid() {
|
||||
|
||||
+2
-2
@@ -35,12 +35,12 @@ public class WebHeadlessSession extends BaseWebSession {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSessionError(Throwable exception) {
|
||||
public void addSessionError(@NotNull Throwable exception) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSessionMessage(WebServerMessage message) {
|
||||
public void addSessionMessage(@NotNull WebServerMessage message) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
+27
-23
@@ -185,11 +185,12 @@ public class WebSession extends BaseWebSession
|
||||
this.cacheExpired = cacheExpired;
|
||||
}
|
||||
|
||||
public synchronized WebUser getUser() {
|
||||
public WebUser getUser() {
|
||||
return this.userContext.getUser();
|
||||
}
|
||||
|
||||
public synchronized Map<String, String> getUserMetaParameters() {
|
||||
@NotNull
|
||||
public Map<String, String> getUserMetaParameters() {
|
||||
var user = getUser();
|
||||
if (user == null) {
|
||||
return Map.of();
|
||||
@@ -201,20 +202,20 @@ public class WebSession extends BaseWebSession
|
||||
return allMetaParams;
|
||||
}
|
||||
|
||||
public synchronized String getUserId() {
|
||||
public String getUserId() {
|
||||
return userContext.getUserId();
|
||||
}
|
||||
|
||||
public synchronized boolean hasPermission(String perm) {
|
||||
return getSessionPermissions().contains(DBWConstants.PERMISSION_ADMIN) ||
|
||||
getSessionPermissions().contains(perm);
|
||||
Set<String> sessionPermissions = getSessionPermissions();
|
||||
return sessionPermissions.contains(DBWConstants.PERMISSION_ADMIN) || sessionPermissions.contains(perm);
|
||||
}
|
||||
|
||||
public synchronized boolean isAuthorizedInSecurityManager() {
|
||||
public boolean isAuthorizedInSecurityManager() {
|
||||
return userContext.isAuthorizedInSecurityManager();
|
||||
}
|
||||
|
||||
public synchronized Set<String> getSessionPermissions() {
|
||||
public Set<String> getSessionPermissions() {
|
||||
if (userContext.getUserPermissions() == null) {
|
||||
refreshSessionAuth();
|
||||
}
|
||||
@@ -222,12 +223,12 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized SMController getSecurityController() {
|
||||
public SMController getSecurityController() {
|
||||
return userContext.getSecurityController();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized SMAdminController getAdminSecurityController() throws DBException {
|
||||
public SMAdminController getAdminSecurityController() throws DBException {
|
||||
if (!hasPermission(DBWConstants.PERMISSION_ADMIN)) {
|
||||
throw new DBException("Admin permissions required");
|
||||
}
|
||||
@@ -235,17 +236,17 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized RMController getRmController() {
|
||||
public RMController getRmController() {
|
||||
return userContext.getRmController();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized DBFileController getFileController() {
|
||||
public DBFileController getFileController() {
|
||||
return userContext.getFileController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void refreshUserData() {
|
||||
public void refreshUserData() {
|
||||
super.refreshUserData();
|
||||
refreshSessionAuth();
|
||||
|
||||
@@ -253,7 +254,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
|
||||
// Note: for admin use only
|
||||
public synchronized void resetUserState(boolean needResetUserCache) throws DBException {
|
||||
public void resetUserState(boolean needResetUserCache) throws DBException {
|
||||
clearAuthTokens();
|
||||
if (needResetUserCache) {
|
||||
try {
|
||||
@@ -267,7 +268,7 @@ public class WebSession extends BaseWebSession
|
||||
clearSessionContext();
|
||||
}
|
||||
|
||||
public synchronized void resetUserState() throws DBException {
|
||||
public void resetUserState() throws DBException {
|
||||
resetUserState(true);
|
||||
}
|
||||
|
||||
@@ -437,7 +438,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
|
||||
public void updateInfo(boolean isOldHttpSessionUsed) {
|
||||
log.trace("Update session lifetime " + getSessionId() + " for user " + getUserId());
|
||||
touchSession();
|
||||
if (isOldHttpSessionUsed) {
|
||||
@@ -501,6 +502,7 @@ public class WebSession extends BaseWebSession
|
||||
super.close(clearTokens, sendSessionExpiredEvent);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<WebAuthInfo> clearAuthTokens() throws DBException {
|
||||
ArrayList<WebAuthInfo> tokensCopy;
|
||||
synchronized (authTokens) {
|
||||
@@ -630,11 +632,11 @@ public class WebSession extends BaseWebSession
|
||||
return asyncTask;
|
||||
}
|
||||
|
||||
public void addSessionError(Throwable exception) {
|
||||
public void addSessionError(@NotNull Throwable exception) {
|
||||
addSessionMessage(new WebServerMessage(exception));
|
||||
}
|
||||
|
||||
public void addSessionMessage(WebServerMessage message) {
|
||||
public void addSessionMessage(@NotNull WebServerMessage message) {
|
||||
synchronized (sessionMessages) {
|
||||
sessionMessages.add(message);
|
||||
}
|
||||
@@ -645,14 +647,15 @@ public class WebSession extends BaseWebSession
|
||||
message.getMessage()));
|
||||
}
|
||||
|
||||
public void addInfoMessage(String message) {
|
||||
public void addInfoMessage(@NotNull String message) {
|
||||
addSessionMessage(new WebServerMessage(MessageType.INFO, message));
|
||||
}
|
||||
|
||||
public void addWarningMessage(String message) {
|
||||
public void addWarningMessage(@NotNull String message) {
|
||||
addSessionMessage(new WebServerMessage(MessageType.WARNING, message));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<WebServerMessage> readLog(Integer maxEntries, Boolean clearLog) {
|
||||
synchronized (sessionMessages) {
|
||||
List<WebServerMessage> messages = new ArrayList<>();
|
||||
@@ -700,7 +703,7 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public List<WebAuthInfo> getAllAuthInfo() {
|
||||
synchronized (authTokens) {
|
||||
return new ArrayList<>(authTokens);
|
||||
@@ -752,7 +755,7 @@ public class WebSession extends BaseWebSession
|
||||
});
|
||||
}
|
||||
|
||||
private void removeAuthInfo(WebAuthInfo oldAuthInfo) {
|
||||
private void removeAuthInfo(@NotNull WebAuthInfo oldAuthInfo) {
|
||||
oldAuthInfo.closeAuth();
|
||||
synchronized (authTokens) {
|
||||
authTokens.remove(oldAuthInfo);
|
||||
@@ -778,6 +781,7 @@ public class WebSession extends BaseWebSession
|
||||
return oldInfo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<DBACredentialsProvider> getContextCredentialsProviders() {
|
||||
return getAdapters(DBACredentialsProvider.class);
|
||||
}
|
||||
@@ -865,11 +869,11 @@ public class WebSession extends BaseWebSession
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public synchronized void resetAuthToken() throws DBException {
|
||||
public void resetAuthToken() throws DBException {
|
||||
this.userContext.reset();
|
||||
}
|
||||
|
||||
public synchronized boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
|
||||
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
|
||||
boolean contextChanged = super.updateSMSession(smAuthInfo);
|
||||
if (contextChanged) {
|
||||
refreshUserData();
|
||||
|
||||
+16
-16
@@ -53,9 +53,9 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
private final ServletApplication application;
|
||||
private final DBPWorkspace workspace;
|
||||
|
||||
private WebUser user;
|
||||
private Set<String> userPermissions;
|
||||
private SMCredentials smCredentials = null;
|
||||
private volatile WebUser user;
|
||||
private volatile Set<String> userPermissions;
|
||||
private volatile SMCredentials smCredentials = null;
|
||||
private String smSessionId;
|
||||
private String refreshToken;
|
||||
|
||||
@@ -87,7 +87,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
* @return - true if context changed
|
||||
* @throws DBException - if user already authorized and new token come from another user
|
||||
*/
|
||||
public synchronized boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
|
||||
public boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
|
||||
if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) {
|
||||
throw new DBCException("Required information about session permissions is missing");
|
||||
}
|
||||
@@ -175,56 +175,56 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized SMController getSecurityController() {
|
||||
public SMController getSecurityController() {
|
||||
return securityController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public synchronized SMCredentials getActiveUserCredentials() {
|
||||
public SMCredentials getActiveUserCredentials() {
|
||||
return smCredentials;
|
||||
}
|
||||
|
||||
public synchronized boolean isAuthorizedInSecurityManager() {
|
||||
public boolean isAuthorizedInSecurityManager() {
|
||||
return smCredentials != null;
|
||||
}
|
||||
|
||||
public synchronized boolean isNonAnonymousUserAuthorizedInSM() {
|
||||
public boolean isNonAnonymousUserAuthorizedInSM() {
|
||||
return isAuthorizedInSecurityManager() && getUser() != null;
|
||||
}
|
||||
|
||||
public synchronized boolean isAnonymousUserAuthorizedInSM() {
|
||||
public boolean isAnonymousUserAuthorizedInSM() {
|
||||
return isAuthorizedInSecurityManager() && getUser() == null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized WebUser getUser() {
|
||||
public WebUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized String getUserId() {
|
||||
public String getUserId() {
|
||||
return user == null ? null : user.getUserId();
|
||||
}
|
||||
|
||||
protected synchronized void setUser(@Nullable WebUser user) {
|
||||
protected void setUser(@Nullable WebUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized SMAdminController getAdminSecurityController() {
|
||||
public SMAdminController getAdminSecurityController() {
|
||||
return adminSecurityController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized Set<String> getUserPermissions() {
|
||||
public Set<String> getUserPermissions() {
|
||||
return userPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* reread the current user's permissions
|
||||
*/
|
||||
public synchronized void refreshPermissions() throws DBException {
|
||||
public void refreshPermissions() throws DBException {
|
||||
if (isAuthorizedInSecurityManager()) {
|
||||
log.debug("refresh permissions " + getUserId() + " " + getSmSessionId());
|
||||
setUserPermissions(securityController.getTokenPermissions().getPermissions());
|
||||
@@ -246,7 +246,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized String getSmSessionId() {
|
||||
public String getSmSessionId() {
|
||||
return smSessionId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
* 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.
|
||||
@@ -47,6 +47,8 @@ public class ServletAppUtils {
|
||||
private static final String HEADER_REFERER = "Referer";
|
||||
|
||||
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
|
||||
private static final String HEADER_FORWARDED_PROTO = "X-Forwarded-Proto";
|
||||
private static final String HEADER_FORWARDED_PORT = "X-Forwarded-Port";
|
||||
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
|
||||
private static final Set<Integer> DEFAULT_PORTS = Set.of(
|
||||
80,
|
||||
@@ -294,19 +296,37 @@ public class ServletAppUtils {
|
||||
@NotNull
|
||||
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
String origin = request.getHeader(HEADER_ORIGIN);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Origin header: " + origin);
|
||||
}
|
||||
if (CommonUtils.isEmpty(origin)) {
|
||||
origin = request.getHeader(HEADER_X_ORIGIN);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("X-Origin header: " + origin);
|
||||
}
|
||||
}
|
||||
if (CommonUtils.isEmpty(origin)) {
|
||||
origin = request.getHeader(HEADER_REFERER);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Referer header: " + origin);
|
||||
}
|
||||
}
|
||||
String forwardedScheme = request.getHeader(HEADER_FORWARDED_SCHEME);
|
||||
if (CommonUtils.isEmpty(forwardedScheme)) {
|
||||
forwardedScheme = request.getHeader(HEADER_FORWARDED_PROTO);
|
||||
}
|
||||
String forwardedHost = request.getHeader(HEADER_FORWARDED_HOST);
|
||||
if (CommonUtils.isNotEmpty(forwardedScheme) && CommonUtils.isNotEmpty(forwardedHost)) {
|
||||
origin = forwardedScheme + "://" + forwardedHost;
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("forwarded origin: " + origin);
|
||||
}
|
||||
}
|
||||
if (CommonUtils.isEmpty(origin)) {
|
||||
URI requestUrl = URI.create(request.getRequestURL().toString());
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Request URL: " + requestUrl);
|
||||
}
|
||||
origin = getRootUrlFromUri(requestUrl) + "/";
|
||||
}
|
||||
origin = removeSideSlashes(origin);
|
||||
@@ -318,7 +338,15 @@ public class ServletAppUtils {
|
||||
|
||||
origin = removeSideSlashes(origin);
|
||||
URI uri = URI.create(origin);
|
||||
if (DEFAULT_PORTS.contains(uri.getPort())) {
|
||||
int port = uri.getPort();
|
||||
if (CommonUtils.isNotEmpty(request.getHeader(HEADER_FORWARDED_PORT))) {
|
||||
try {
|
||||
port = Integer.parseInt(request.getHeader(HEADER_FORWARDED_PORT));
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Failed to parse port from header: " + request.getHeader(HEADER_FORWARDED_PORT), e);
|
||||
}
|
||||
}
|
||||
if (DEFAULT_PORTS.contains(port)) {
|
||||
try {
|
||||
origin = new URI(
|
||||
uri.getScheme(),
|
||||
@@ -333,6 +361,9 @@ public class ServletAppUtils {
|
||||
log.error("Failed to create URI without port", e);
|
||||
}
|
||||
}
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Origin URI: " + origin);
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
+14
-3
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
* 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.
|
||||
@@ -33,6 +33,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class RequestHostFilter implements Filter {
|
||||
private static final Log log = Log.getLog(RequestHostFilter.class);
|
||||
|
||||
@@ -74,9 +75,19 @@ public class RequestHostFilter implements Filter {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
|
||||
|
||||
if (CommonUtils.isNotEmpty(originUri.getHost())) {
|
||||
requestAllowed = InetAddresses.isInetAddress(originUri.getHost());
|
||||
} else {
|
||||
log.debug(
|
||||
"Request origin host is null, request URI - " + originUri +
|
||||
", request path - " + httpRequest.getServletPath() +
|
||||
", request url - " + httpRequest.getRequestURL()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
String servletPath = httpRequest.getServletPath();
|
||||
requestAllowed = isIpAddress;
|
||||
if (!requestAllowed) {
|
||||
if (CommonUtils.isNotEmpty(servletPath)) {
|
||||
for (String excludedPath : excludedPaths) {
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* 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.
|
||||
@@ -82,6 +82,7 @@ public class WebDatabaseAuthModel {
|
||||
|
||||
return Arrays.stream(credentialsSource.getProperties())
|
||||
.filter(predicate)
|
||||
.filter(p -> !p.isDesktop())
|
||||
.map(p -> new WebPropertyInfo(webSession, p, credentialsSource)).toArray(WebPropertyInfo[]::new);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* 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.
|
||||
@@ -61,6 +61,7 @@ public class WebNetworkHandlerDescriptor {
|
||||
|
||||
public WebPropertyInfo[] getProperties() {
|
||||
return Arrays.stream(descriptor.getHandlerProperties())
|
||||
.filter(p -> !p.isDesktop() && !p.isHidden())
|
||||
.map(p -> new WebPropertyInfo(session, p, null)).toArray(WebPropertyInfo[]::new);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,21 @@
|
||||
*/
|
||||
package io.cloudbeaver.server;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
|
||||
public class WebAppUtils {
|
||||
|
||||
public static boolean isWebApplication() {
|
||||
return DBWorkbench.getPlatform().getApplication() instanceof WebApplication;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static WebApplication getWebApplication() {
|
||||
return (WebApplication) DBWorkbench.getPlatform().getApplication();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static BaseWebPlatform getWebPlatform() {
|
||||
return (BaseWebPlatform) DBWorkbench.getPlatform();
|
||||
}
|
||||
|
||||
+3
@@ -31,6 +31,9 @@ public class WSDefaultEventHandler<EVENT extends WSEvent> implements WSEventHand
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NotNull EVENT event) {
|
||||
if (!WebAppUtils.isWebApplication()) {
|
||||
return;
|
||||
}
|
||||
log.debug(event.getTopicId() + " event handled");
|
||||
Collection<BaseWebSession> allSessions = WebAppUtils.getWebApplication()
|
||||
.getSessionManager()
|
||||
|
||||
+9
-9
@@ -145,11 +145,6 @@ public class WebDefaultFeatureProvider implements DBWFeatureProvider {
|
||||
return features;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getProjectId(@NotNull DBNNode node) {
|
||||
return node.getOwnerProject().getId();
|
||||
}
|
||||
|
||||
private boolean canCreateConnectionFromFileName(String fileName) {
|
||||
String fileExtension = IOUtils.getFileExtension(fileName);
|
||||
if (CommonUtils.isEmpty(fileExtension)) {
|
||||
@@ -169,11 +164,16 @@ public class WebDefaultFeatureProvider implements DBWFeatureProvider {
|
||||
}
|
||||
|
||||
private boolean hasNodePermission(@NotNull WebSession webSession, @NotNull DBNNode node, @NotNull RMProjectPermission permission) {
|
||||
WebProjectImpl project = webSession.getProjectById(node.getOwnerProject().getId());
|
||||
if (project == null) {
|
||||
return false;
|
||||
RMProject rmProject;
|
||||
if (node instanceof DBNResourceManagerResource rmr) {
|
||||
rmProject = rmr.getRmProject();
|
||||
} else {
|
||||
WebProjectImpl project = webSession.getProjectById(node.getOwnerProject().getId());
|
||||
if (project == null) {
|
||||
return false;
|
||||
}
|
||||
rmProject = project.getRMProject();
|
||||
}
|
||||
RMProject rmProject = project.getRMProject();
|
||||
return SMUtils.hasProjectPermission(webSession, rmProject, permission);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,7 +46,10 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({ exce
|
||||
return (
|
||||
<div className={s(styles, { error: true, icon, inline }, className)}>
|
||||
<div className={s(styles, { errorIcon: true })} title={error.message}>
|
||||
<IconOrImage className={s(styles, { iconOrImage: true })} icon={inline || icon ? '/icons/preload/error_icon_sm.svg' : '/icons/preload/error_icon.svg'} />
|
||||
<IconOrImage
|
||||
className={s(styles, { iconOrImage: true })}
|
||||
icon={inline || icon ? '/icons/preload/error_icon_sm.svg' : '/icons/preload/error_icon.svg'}
|
||||
/>
|
||||
</div>
|
||||
{!icon && (
|
||||
<>
|
||||
@@ -56,7 +59,6 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({ exce
|
||||
</h2>
|
||||
<div className={s(styles, { errorMessage: true })}>
|
||||
{(error.hasDetails && error.message) || translate('core_blocks_exception_message_error_message')}{' '}
|
||||
{onRetry && translate('ui_please_retry')}
|
||||
</div>
|
||||
<div className={s(styles, { errorActions: true })}>
|
||||
<Button type="button" variant="secondary" disabled={error.isOpen} onClick={error.open}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,8 @@ export class ExceptionsCatcherService extends Bootstrap {
|
||||
|
||||
private readonly catcher = (event: Event | string, source?: string, lineno?: number, colno?: number, _error?: Error) => {
|
||||
if (_error) {
|
||||
this.notificationService.logException(_error, this.messageTitle);
|
||||
this.notificationService.logException(_error,
|
||||
_error.name || this.messageTitle);
|
||||
} else {
|
||||
const message = String(event);
|
||||
this.notificationService.logError({
|
||||
|
||||
@@ -31,7 +31,6 @@ export default [
|
||||
['ui_minute_first_form', '{arg:interval} minute'],
|
||||
['ui_minute_second_form', '{arg:interval} minutes'],
|
||||
['ui_minute_third_form', '{arg:interval} minutes'],
|
||||
['ui_please_retry', 'Please retry'],
|
||||
['ui_custom', 'Custom'],
|
||||
['ui_configure', 'Configure'],
|
||||
['ui_apply', 'Apply'],
|
||||
|
||||
@@ -29,7 +29,6 @@ export default [
|
||||
['ui_minute_first_form', '{arg:interval} minute'],
|
||||
['ui_minute_second_form', '{arg:interval} minutes'],
|
||||
['ui_minute_third_form', '{arg:interval} minutes'],
|
||||
['ui_please_retry', 'Veuillez réessayer'],
|
||||
['ui_custom', 'Personnalisé'],
|
||||
['ui_configure', 'Configurer'],
|
||||
['ui_apply', 'Appliquer'],
|
||||
|
||||
@@ -31,7 +31,6 @@ export default [
|
||||
['ui_minute_first_form', '{arg:interval} phút'],
|
||||
['ui_minute_second_form', '{arg:interval} phút'],
|
||||
['ui_minute_third_form', '{arg:interval} phút'],
|
||||
['ui_please_retry', 'Vui lòng thử lại'],
|
||||
['ui_custom', 'Tùy chỉnh'],
|
||||
['ui_configure', 'Cấu hình'],
|
||||
['ui_apply', 'Áp dụng'],
|
||||
|
||||
@@ -1,12 +1,48 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export class NetworkError extends Error {
|
||||
import { DetailsError, GQLError, PlainGQLError } from '@cloudbeaver/core-sdk';
|
||||
import { errorOf } from '@cloudbeaver/core-utils';
|
||||
|
||||
/**
|
||||
* Checks if an error is a network-related fetch failure.
|
||||
* Different browsers throw different error messages for network issues:
|
||||
* - Chrome/Edge: "Failed to fetch"
|
||||
* - Firefox: "NetworkError when attempting to fetch resource"
|
||||
* - Safari: "Load failed"
|
||||
*/
|
||||
export function isNetworkFetchError(error: unknown): error is NetworkError {
|
||||
if (!(error instanceof TypeError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = error.message.toLowerCase();
|
||||
const gqlError = errorOf(error, GQLError) ?? errorOf(error, PlainGQLError);
|
||||
/*
|
||||
GraphQL always returns a 200 status code, regardless of success or failure.
|
||||
The client app proxy (e.g. vite) returns a 500 status code only in dev mode (frontend build),
|
||||
In prod mode it throws a network error with a specific message.
|
||||
*/
|
||||
const isGqlProxyError = gqlError?.response.status === 500 && (!gqlError.response.body || Object.keys(gqlError.response.body).length === 0);
|
||||
/*
|
||||
fetch() API throws a TypeError when a network error occurs.
|
||||
Different browsers use different error messages (Chrome: "Failed to fetch", Firefox: "NetworkError...", Safari: "Load failed").
|
||||
This is a common way to detect network issues in web applications in prod mode (not dev frontend build)
|
||||
*/
|
||||
const isCommonNetworkError =
|
||||
message.includes('failed to fetch') || // Chrome/Edge
|
||||
message.includes('networkerror') || // Firefox
|
||||
message.includes('load failed'); // Safari
|
||||
|
||||
return isGqlProxyError || isCommonNetworkError;
|
||||
}
|
||||
|
||||
export class NetworkError extends DetailsError {
|
||||
constructor(message?: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
this.name = 'Network Error';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -12,7 +12,7 @@ import { Executor, type IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { errorOf } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { NetworkError } from './NetworkError.js';
|
||||
import { isNetworkFetchError, NetworkError } from './NetworkError.js';
|
||||
|
||||
@injectable(() => [GraphQLService])
|
||||
export class NetworkStateService extends Bootstrap {
|
||||
@@ -41,7 +41,7 @@ export class NetworkStateService extends Bootstrap {
|
||||
window.addEventListener('online', () => this.setState(true));
|
||||
window.addEventListener('offline', () => this.setState(false));
|
||||
|
||||
this.graphQLService.registerInterceptor(this.sessionExpiredInterceptor.bind(this));
|
||||
this.graphQLService.registerInterceptor(this.networkIssuesInterceptor.bind(this));
|
||||
}
|
||||
|
||||
private setState(state: boolean) {
|
||||
@@ -60,13 +60,14 @@ export class NetworkStateService extends Bootstrap {
|
||||
this.networkStateExecutor.execute(this.networkState);
|
||||
}
|
||||
|
||||
private async sessionExpiredInterceptor(request: Promise<any>): Promise<any> {
|
||||
private async networkIssuesInterceptor(request: Promise<any>): Promise<any> {
|
||||
try {
|
||||
return await request;
|
||||
} catch (exception: any) {
|
||||
if (exception instanceof TypeError && exception.message === 'Failed to fetch') {
|
||||
throw new NetworkError('Error while processing request', { cause: exception });
|
||||
if (isNetworkFetchError(exception)) {
|
||||
throw new NetworkError('Server is not available. Please check your network connection and try again.', { cause: exception });
|
||||
}
|
||||
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
CbEventTopic as SessionEventTopic,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { isNetworkFetchError, NetworkError } from './NetworkError.js';
|
||||
import type { IBaseServerEvent, IServerEventCallback, IServerEventEmitter, Unsubscribe } from './ServerEventEmitter/IServerEventEmitter.js';
|
||||
import { SessionExpireService } from './SessionExpireService.js';
|
||||
import { TransportSubject } from './TransportSubject.js';
|
||||
@@ -218,6 +219,12 @@ export class SessionEventSource implements IServerEventEmitter<ISessionEvent, IS
|
||||
}
|
||||
|
||||
private errorHandler(error: any): Observable<ISessionEvent> {
|
||||
if (isNetworkFetchError(error)) {
|
||||
const networkError = new NetworkError('Server is not available. Please check your network connection and try again.', { cause: error });
|
||||
this.errorSubject.next(networkError);
|
||||
return throwError(() => networkError);
|
||||
}
|
||||
|
||||
this.errorSubject.next(new ServiceError('Transport error', { cause: error }));
|
||||
return throwError(() => error);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@ export * from './getGQLResponse.js';
|
||||
export * from './getObjectPropertyType.js';
|
||||
export * from './getObjectPropertyValueType.js';
|
||||
export * from './GQLError.js';
|
||||
export * from './PlainGQLError.js';
|
||||
export * from './GQLErrorCatcher.js';
|
||||
export * from './GraphQLService.js';
|
||||
export * from './sdk.js';
|
||||
|
||||
Reference in New Issue
Block a user