diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/BaseWebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/BaseWebSession.java index 5bc215a7d4d..d899f59652b 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/BaseWebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/BaseWebSession.java @@ -116,6 +116,13 @@ public void removeEventHandler(@NotNull CBWebSessionEventHandler handler) { } } + public void migrateEventHandlersTo(@NotNull BaseWebSession target) { + synchronized (sessionEventHandlers) { + sessionEventHandlers.forEach(target::addEventHandler); + sessionEventHandlers.clear(); + } + } + public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException { return userContext.refresh(smAuthInfo); } diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/service/session/CBSessionManager.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/service/session/CBSessionManager.java index 3f7cc846836..4413e0841a3 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/service/session/CBSessionManager.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/service/session/CBSessionManager.java @@ -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. @@ -199,6 +199,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 webSession + ) throws DBWebException { + HttpSession oldHttpSession = request.getSession(false); + if (oldHttpSession != null) { + oldHttpSession.invalidate(); + } + String newSessionId = request.getSession(true).getId(); + + String locale = webSession.getLocale(); + String remoteAddr = webSession.getLastRemoteAddr(); + String remoteUserAgent = webSession.getLastRemoteUserAgent(); + var requestInfo = new WebHttpRequestInfo(newSessionId, locale, remoteAddr, remoteUserAgent); + WebSession newWebSession; + try { + newWebSession = createWebSessionImpl(requestInfo); + } catch (DBException e) { + throw new DBWebException(e); + } + webSession.migrateEventHandlersTo(newWebSession); + String oldSessionId = webSession.getSessionId(); + synchronized (sessionMap) { + sessionMap.remove(oldSessionId); + sessionMap.put(newSessionId, newWebSession); + } + webSession.close(false, false); + + log.debug("Session rotated '" + oldSessionId + "' -> '" + newSessionId + "'"); + return newWebSession; + } + /** * Returns not expired session from cache, or restore it. * @@ -283,15 +319,14 @@ public BaseWebSession getSession(@NotNull String sessionId) { @Override @Nullable - public WebSession findWebSession(HttpServletRequest request) { + 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; } @Override diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java index a5c5371af20..67a9df0d602 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java @@ -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. @@ -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, diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebAsyncAuthJob.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebAsyncAuthJob.java index 00105a7daa3..665ba78c6cc 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebAsyncAuthJob.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebAsyncAuthJob.java @@ -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. diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java index e788df0c5fc..c0b3ebc5539 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java @@ -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. @@ -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"), diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/handler/WSAuthSessionEventHandler.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/handler/WSAuthSessionEventHandler.java index 753140bf380..f7de998fef2 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/handler/WSAuthSessionEventHandler.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/handler/WSAuthSessionEventHandler.java @@ -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. diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index 7df05d3c467..af8854421ca 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -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. @@ -67,7 +67,8 @@ public class WebServiceAuthImpl implements DBWServiceAuth { @Override public WebAuthStatus authLogin( - @NotNull WebSession webSession, + @NotNull HttpServletRequest httpRequest, + @NotNull WebSession inputWebSession, @NotNull String providerId, @Nullable String providerConfigurationId, @Nullable Map authParameters, @@ -75,6 +76,13 @@ public WebAuthStatus authLogin( 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() @@ -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 authInfos = authProcessor.authenticateSession(); + return new WebAuthStatus(smAuthInfo.getAuthStatus(), authInfos); } } catch (SMTooManySessionsException e) { throw new DBWebException("User authentication failed", e.getErrorType(), e); @@ -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");