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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ public void removeEventHandler(@NotNull CBWebSessionEventHandler handler) {
}
}

public void migrateEventHandlersTo(@NotNull BaseWebSession target) {
synchronized (sessionEventHandlers) {
for (CBWebSessionEventHandler handler : sessionEventHandlers) {
handler.migrateToSession(target);
target.addEventHandler(handler);
}
sessionEventHandlers.clear();
}
}

public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
return userContext.refresh(smAuthInfo);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,6 +16,8 @@
*/
package io.cloudbeaver.websocket;

import io.cloudbeaver.model.session.BaseWebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;

Expand All @@ -24,4 +26,6 @@ public interface CBWebSessionEventHandler {

void close();

void migrateToSession(@NotNull BaseWebSession newSession);

}
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,42 @@ protected String getSessionId(@NotNull HttpServletRequest request) {
return httpSession.getId();
}

/**
* Invalidates the current HTTP session, creates a new one, and binds a new {@link WebSession} to it.
*/
@NotNull
public WebSession rotateSession(
@NotNull HttpServletRequest request,
@NotNull WebSession oldWebSession
) throws DBWebException {
HttpSession oldHttpSession = request.getSession(false);
if (oldHttpSession != null) {
oldHttpSession.invalidate();
}
String newSessionId = request.getSession(true).getId();

String locale = oldWebSession.getLocale();
String remoteAddr = oldWebSession.getLastRemoteAddr();
String remoteUserAgent = oldWebSession.getLastRemoteUserAgent();
var requestInfo = new WebHttpRequestInfo(newSessionId, locale, remoteAddr, remoteUserAgent);
WebSession newWebSession;
try {
newWebSession = createWebSessionImpl(requestInfo);
} catch (DBException e) {
throw new DBWebException(e);
}
oldWebSession.migrateEventHandlersTo(newWebSession);
String oldSessionId = oldWebSession.getSessionId();
synchronized (sessionMap) {
sessionMap.remove(oldSessionId);
sessionMap.put(newSessionId, newWebSession);
}
oldWebSession.close(false, false);

log.debug("Session rotated '" + oldSessionId + "' -> '" + newSessionId + "'");
return newWebSession;
}

/**
* Returns not expired session from cache, or restore it.
*
Expand Down Expand Up @@ -288,13 +324,12 @@ public BaseWebSession getSession(@NotNull String sessionId) {
@Nullable
public WebSession findWebSession(@NotNull HttpServletRequest request) {
String sessionId = getSessionId(request);
WebSession webSession;
synchronized (sessionMap) {
var session = sessionMap.get(sessionId);
if (session instanceof WebSession) {
return (WebSession) session;
}
return null;
webSession = (session instanceof WebSession) ? (WebSession) session : null;
}
return webSession;
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ public class CBClientEventProcessor {

private static final Log log = Log.getLog(CBClientEventProcessor.class);

final BaseWebSession webSession;
volatile BaseWebSession webSession;

public CBClientEventProcessor(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}

void setWebSession(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}

public void process(@Nullable String message) {
if (CommonUtils.isEmpty(message)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {

private static final int QUEUE_CAPACITY = 1000;

private final BaseWebSession webSession;
private volatile BaseWebSession webSession;
private final BlockingQueue<WSEvent> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
private final CBClientEventProcessor processor;
private volatile long lastPoll;
Expand Down Expand Up @@ -88,6 +88,12 @@ public List<WSEvent> pollEvents(long timeoutSec) throws InterruptedException {
return result;
}

@Override
public void migrateToSession(@NotNull BaseWebSession newSession) {
this.webSession = newSession;
this.processor.setWebSession(newSession);
}

@Override
public void handleWebSessionEvent(@NotNull WSEvent event) {
if (!queue.offer(event)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
private static final Log log = Log.getLog(CBEventsWebSocket.class);

@Nullable
private BaseWebSession webSession;
private volatile BaseWebSession webSession;
@Nullable
private FromUserEventHandler eventProcessor;
@Nullable
private WebSocketPingPongCallback pingPongCallback;

@Override
public void onOpen(Session session, EndpointConfig config) {
Expand All @@ -48,13 +52,31 @@ public void onOpen(Session session, EndpointConfig config) {
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");

session.setMaxIdleTimeout(Duration.ofMinutes(5).toMillis());
session.addMessageHandler(String.class, new FromUserEventHandler(webSession));
session.addMessageHandler(PongMessage.class, new WebSocketPingPongCallback(webSession));
this.eventProcessor = new FromUserEventHandler(webSession);
session.addMessageHandler(String.class, eventProcessor);
this.pingPongCallback = new WebSocketPingPongCallback(webSession);
session.addMessageHandler(PongMessage.class, pingPongCallback);

CBJettyWebSocketManager.registerWebSocket(webSession.getSessionId(), this);
}
}

@Override
public void migrateToSession(@NotNull BaseWebSession newSession) {
BaseWebSession oldSession = this.webSession;
this.webSession = newSession;
if (eventProcessor != null) {
eventProcessor.setWebSession(newSession);
}
if (pingPongCallback != null) {
pingPongCallback.setWebSession(newSession);
}
if (oldSession != null) {
CBJettyWebSocketManager.migrateWebSocket(oldSession.getSessionId(), newSession.getSessionId(), this);
}
log.debug("EventWebSocket migrated to the " + newSession.getSessionId() + " session");
}

private static class FromUserEventHandler extends CBClientEventProcessor implements MessageHandler.Whole<String> {

private FromUserEventHandler(@NotNull BaseWebSession webSession) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -39,6 +39,25 @@ public static void registerWebSocket(@NotNull String webSessionId, @NotNull CBEv
socketBySessionId.computeIfAbsent(webSessionId, key -> new CopyOnWriteArrayList<>()).add(webSocket);
}

/**
* Re-keys a live web socket from the old session id to the new one after a session rotation,
* so that keep-alive pings keep being delivered to it.
*/
public static void migrateWebSocket(
@NotNull String oldSessionId,
@NotNull String newSessionId,
@NotNull CBEventsWebSocket webSocket
) {
List<CBEventsWebSocket> oldSockets = socketBySessionId.get(oldSessionId);
if (oldSockets != null) {
oldSockets.remove(webSocket);
if (oldSockets.isEmpty()) {
socketBySessionId.remove(oldSessionId);
}
}
registerWebSocket(newSessionId, webSocket);
}

public static void sendPing() {
//remove expired sessions
WebAppSessionManager webSessionManager = WebAppUtils.getWebApplication().getSessionManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@

public class WebSocketPingPongCallback implements MessageHandler.Whole<PongMessage> {
@NotNull
private final BaseWebSession webSession;
private volatile BaseWebSession webSession;

public WebSocketPingPongCallback(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}

void setWebSession(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
}

@Override
public void onMessage(PongMessage message) {
if (webSession instanceof WebHeadlessSession) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -36,6 +36,7 @@ public interface DBWServiceAuth extends DBWService {

@WebAction(authRequired = false)
WebAuthStatus authLogin(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -37,6 +37,7 @@ public WebServiceBindingAuth() {
public void bindWiring(DBWBindingContext model) {
model.getQueryType()
.dataFetcher("authLogin", env -> getService(env).authLogin(
GraphQLEndpoint.getServletRequestOrThrow(env),
getWebSession(env, false),
getArgumentVal(env, "provider"),
getArgument(env, "configuration"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -67,14 +67,22 @@ public class WebServiceAuthImpl implements DBWServiceAuth {

@Override
public WebAuthStatus authLogin(
@NotNull WebSession webSession,
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession inputWebSession,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

What is inputWebSession?
Is it OLD session for the same cookie?
Currently name is confusing as you use inputWebSession in other functions with different meaning.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

authLogin knows nothing about the rotating session process. It has only current web session. I think that naming is ok for this method

@NotNull String providerId,
@Nullable String providerConfigurationId,
@Nullable Map<String, Object> authParameters,
boolean linkWithActiveUser,
boolean forceSessionsLogout
) throws DBWebException {
try {
WebSession webSession = inputWebSession;
if (inputWebSession.getUser() == null) {
// Rotate anonymous web sessions during login attempts to prevent session fixation attacks.
webSession = CBApplication.getInstance().getSessionManager()
.rotateSession(httpRequest, inputWebSession);
}

var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
//TODO deprecated, use asyncAuthLogin for federated auth, exits for backward compatibility
linkWithActiveUser = linkWithActiveUser && CBApplication.getInstance().getAppConfiguration()
Expand All @@ -85,7 +93,8 @@ public WebAuthStatus authLogin(
} else {
//run it sync
var authProcessor = new WebSessionAuthProcessor(webSession, smAuthInfo, linkWithActiveUser);
return new WebAuthStatus(smAuthInfo.getAuthStatus(), authProcessor.authenticateSession());
List<WebAuthInfo> authInfos = authProcessor.authenticateSession();
return new WebAuthStatus(smAuthInfo.getAuthStatus(), authInfos);
}
} catch (SMTooManySessionsException e) {
throw new DBWebException("User authentication failed", e.getErrorType(), e);
Expand All @@ -95,14 +104,22 @@ public WebAuthStatus authLogin(
}

@Override
@NotNull
public WebAsyncAuthStatus federatedLogin(
@NotNull HttpServletRequest httpRequest,
@NotNull WebSession webSession,
@NotNull WebSession inputWebSession,
@NotNull String providerId,
@Nullable String providerConfigurationId,
boolean linkWithActiveUser,
boolean forceSessionsLogout
) throws DBWebException {
WebSession webSession = inputWebSession;
if (inputWebSession.getUser() == null) {
// Rotate anonymous web sessions during login attempts to prevent session fixation attacks.
webSession = CBApplication.getInstance().getSessionManager()
.rotateSession(httpRequest, inputWebSession);
}

WebAuthProviderDescriptor providerDescriptor = WebAuthProviderRegistry.getInstance().getAuthProvider(providerId);
if (providerDescriptor == null) {
throw new DBWebException("Provider '" + providerId + "' not found");
Expand Down
Loading