mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-2773-TE-event-pulling-send-session-status-as-an-event (#1596)
* CB-3235-Send-event-about-session-expire (#1576) * CB-3235 Send event about session expire * CB-3235 Send event about session expire Fixes WSSessionExpiredEvent description in comment * CB-3235 Send event about session expire Moves WSSocketConnectedEvent to session package * CB-3235 Send event about session expire Implements BaseWebSession#sendSystemEvent * CB-3235 Send event about session expire Implements a WSSessionStateEvent and a job to send it periodically * CB-3235 Send event about session expire Fixes code style issues * CB-2997 jetty server bundle (#1587) * CB-2997 jetty server bundle * CB-2997 remove jetty dependencies * CB-3235 review fixes --------- Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com> Co-authored-by: Aleksandr Skoblikov <aleksandr.skoblikov@dbeaver.com> * CB-2773 handle session expire and session state events * CB-2773 fix * CB-2773 subscribe to topic, handle session state event --------- Co-authored-by: Yaroslav Emelyanov <iaroslav.emelianov@dbeaver.com> Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com> Co-authored-by: Tatiana Fokina <tatiana.fokina@dbeaver.com> Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
co-authored by
Ainur
Yaroslav Emelyanov
Tatiana Fokina
dariamarutkina
parent
48dee829a7
commit
3bd2288564
@@ -25,4 +25,7 @@ public interface CBAuthConstants {
|
||||
String CB_AUTH_ID_REQUEST_PARAM = "authId";
|
||||
String CB_AUTO_LOGIN_REQUEST_PARAM = "autoLogin";
|
||||
String CB_REDIRECT_URL_REQUEST_PARAM = "redirectUrl";
|
||||
|
||||
// Default max idle time (10 minutes)
|
||||
long MAX_SESSION_IDLE_TIME = 10 * 60 * 1000;
|
||||
}
|
||||
|
||||
+6
@@ -17,8 +17,14 @@
|
||||
|
||||
package io.cloudbeaver.model.app;
|
||||
|
||||
import io.cloudbeaver.auth.CBAuthConstants;
|
||||
|
||||
public interface WebAuthApplication extends WebApplication {
|
||||
WebAuthConfiguration getAuthConfiguration();
|
||||
|
||||
String getAuthServiceURL();
|
||||
|
||||
default long getMaxSessionIdleTime() {
|
||||
return CBAuthConstants.MAX_SESSION_IDLE_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-2
@@ -17,6 +17,7 @@
|
||||
package io.cloudbeaver.model.session;
|
||||
|
||||
import io.cloudbeaver.model.app.WebApplication;
|
||||
import io.cloudbeaver.model.app.WebAuthApplication;
|
||||
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -28,6 +29,7 @@ import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -47,14 +49,14 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
@NotNull
|
||||
protected final WebUserContext userContext;
|
||||
@NotNull
|
||||
protected final WebApplication application;
|
||||
protected final WebAuthApplication application;
|
||||
protected volatile long lastAccessTime;
|
||||
|
||||
private final List<CBWebSessionEventHandler> sessionEventHandlers = new CopyOnWriteArrayList<>();
|
||||
private WebSessionEventsFilter eventsFilter = new WebSessionEventsFilter();
|
||||
private final WebSessionWorkspace workspace;
|
||||
|
||||
public BaseWebSession(@NotNull String id, @NotNull WebApplication application) throws DBException {
|
||||
public BaseWebSession(@NotNull String id, @NotNull WebAuthApplication application) throws DBException {
|
||||
this.id = id;
|
||||
this.application = application;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
@@ -152,8 +154,14 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
var sessionExpiredEvent = new WSSessionExpiredEvent();
|
||||
synchronized (sessionEventHandlers) {
|
||||
for (CBWebSessionEventHandler sessionEventHandler : sessionEventHandlers) {
|
||||
try {
|
||||
sessionEventHandler.handeWebSessionEvent(sessionExpiredEvent);
|
||||
} catch (DBException e) {
|
||||
log.warn("Failed to send session expiration event", e);
|
||||
}
|
||||
sessionEventHandler.close();
|
||||
}
|
||||
sessionEventHandlers.clear();
|
||||
@@ -182,4 +190,14 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
public void removeSessionProject(@Nullable String projectId) throws DBException {
|
||||
userContext.getAccessibleProjectIds().remove(projectId);
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isValid() {
|
||||
return getRemainingTime() > 0;
|
||||
}
|
||||
|
||||
@Property
|
||||
public long getRemainingTime() {
|
||||
return application.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.session;
|
||||
|
||||
import io.cloudbeaver.model.app.WebApplication;
|
||||
import io.cloudbeaver.model.app.WebAuthApplication;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionPrincipal;
|
||||
@@ -27,7 +27,7 @@ import org.jkiss.dbeaver.model.auth.SMSessionPrincipal;
|
||||
public class WebHeadlessSession extends BaseWebSession {
|
||||
public WebHeadlessSession(
|
||||
@NotNull String id,
|
||||
@NotNull WebApplication application
|
||||
@NotNull WebAuthApplication application
|
||||
) throws DBException {
|
||||
super(id, application);
|
||||
}
|
||||
|
||||
+7
-27
@@ -26,7 +26,7 @@ import io.cloudbeaver.WebProjectImpl;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.WebServerMessage;
|
||||
import io.cloudbeaver.model.app.WebApplication;
|
||||
import io.cloudbeaver.model.app.WebAuthApplication;
|
||||
import io.cloudbeaver.model.rm.RMUtils;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import io.cloudbeaver.service.DBWSessionHandler;
|
||||
@@ -71,14 +71,14 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* Web session.
|
||||
@@ -97,7 +97,6 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
private final AtomicInteger taskCount = new AtomicInteger();
|
||||
|
||||
private long maxSessionIdleTime;
|
||||
private String lastRemoteAddr;
|
||||
private String lastRemoteUserAgent;
|
||||
|
||||
@@ -121,15 +120,13 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
public WebSession(
|
||||
@NotNull HttpSession httpSession,
|
||||
@NotNull WebApplication application,
|
||||
@NotNull Map<String, DBWSessionHandler> sessionHandlers,
|
||||
long maxSessionIdleTime
|
||||
@NotNull WebAuthApplication application,
|
||||
@NotNull Map<String, DBWSessionHandler> sessionHandlers
|
||||
) throws DBException {
|
||||
super(httpSession.getId(), application);
|
||||
this.lastAccessTime = this.createTime;
|
||||
setLocale(CommonUtils.toString(httpSession.getAttribute(ATTR_LOCALE), this.locale));
|
||||
this.sessionHandlers = sessionHandlers;
|
||||
this.maxSessionIdleTime = maxSessionIdleTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -483,14 +480,12 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
public synchronized void updateInfo(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
long maxSessionIdleTime
|
||||
HttpServletResponse response
|
||||
) throws DBWebException {
|
||||
touchSession();
|
||||
HttpSession httpSession = request.getSession();
|
||||
this.lastRemoteAddr = request.getRemoteAddr();
|
||||
this.lastRemoteUserAgent = request.getHeader("User-Agent");
|
||||
this.maxSessionIdleTime = maxSessionIdleTime;
|
||||
this.cacheExpired = false;
|
||||
if (!httpSession.isNew()) {
|
||||
try {
|
||||
@@ -996,21 +991,6 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isValid() {
|
||||
return getSessionActiveTimeLeft() > 0;
|
||||
}
|
||||
|
||||
@Property
|
||||
public long getRemainingTime() {
|
||||
return getSessionActiveTimeLeft();
|
||||
}
|
||||
|
||||
private long getSessionActiveTimeLeft() {
|
||||
return maxSessionIdleTime + lastAccessTime - System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
private class SessionProgressMonitor extends BaseProgressMonitor {
|
||||
@Override
|
||||
public void beginTask(String name, int totalWork) {
|
||||
|
||||
@@ -9,6 +9,8 @@ enum CBServerEventId {
|
||||
cb_session_log_updated,
|
||||
|
||||
cb_session_websocket_connected,
|
||||
cb_session_state,
|
||||
cb_session_expired,
|
||||
|
||||
cb_datasource_created,
|
||||
cb_datasource_updated,
|
||||
@@ -99,6 +101,20 @@ type WSSocketConnectedEvent implements CBServerEvent {
|
||||
applicationRunId: String!
|
||||
}
|
||||
|
||||
# Session state info event
|
||||
type WSSessionStateEvent implements CBServerEvent {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
remainingTime: Int!
|
||||
isValid: Boolean
|
||||
}
|
||||
|
||||
# Session expired event
|
||||
type WSSessionExpiredEvent implements CBServerEvent {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
}
|
||||
|
||||
# RM project update event
|
||||
type CBProjectUpdateEvent implements CBServerEvent {
|
||||
id: CBServerEventId!
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.auth.CBAuthConstants;
|
||||
import io.cloudbeaver.model.app.BaseWebApplication;
|
||||
import io.cloudbeaver.model.app.WebAuthApplication;
|
||||
import io.cloudbeaver.model.app.WebAuthConfiguration;
|
||||
@@ -85,6 +86,11 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
private static final Log log = Log.getLog(CBApplication.class);
|
||||
|
||||
private static final boolean RECONFIGURATION_ALLOWED = true;
|
||||
/**
|
||||
* In configuration mode sessions expire after a week
|
||||
*/
|
||||
private static final long CONFIGURATION_MODE_SESSION_IDLE_TIME = 60 * 60 * 1000 * 24 * 7;
|
||||
|
||||
|
||||
static {
|
||||
Log.setDefaultDebugStream(System.out);
|
||||
@@ -119,7 +125,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
// Persistence
|
||||
protected SMAdminController securityController;
|
||||
|
||||
private long maxSessionIdleTime = CBConstants.MAX_SESSION_IDLE_TIME;
|
||||
private long maxSessionIdleTime = CBAuthConstants.MAX_SESSION_IDLE_TIME;
|
||||
|
||||
private boolean develMode = false;
|
||||
private boolean configurationMode = false;
|
||||
@@ -179,6 +185,9 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
}
|
||||
|
||||
public long getMaxSessionIdleTime() {
|
||||
if (isConfigurationMode()) {
|
||||
return CONFIGURATION_MODE_SESSION_IDLE_TIME;
|
||||
}
|
||||
return maxSessionIdleTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,6 @@ public class CBConstants {
|
||||
public static final String DEFAULT_ADMIN_NAME = "cbadmin";
|
||||
public static final String DEFAULT_ADMIN_TEAM = "admin";
|
||||
|
||||
// Default max idle time (10 minutes)
|
||||
public static final long MAX_SESSION_IDLE_TIME = 10 * 60 * 1000;
|
||||
|
||||
public static final String ENV_CB_HOME = "CLOUDBEAVER_HOME";
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package io.cloudbeaver.server;
|
||||
|
||||
import io.cloudbeaver.server.jobs.SessionStateJob;
|
||||
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
|
||||
import io.cloudbeaver.service.session.WebSessionManager;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
@@ -148,7 +150,11 @@ public class CBPlatform extends BasePlatformImpl {
|
||||
|
||||
refreshApplicableDrivers();
|
||||
|
||||
new WebSessionMonitorJob(this).scheduleMonitor();
|
||||
new WebSessionMonitorJob(this)
|
||||
.scheduleMonitor();
|
||||
|
||||
new SessionStateJob(this)
|
||||
.scheduleMonitor();
|
||||
|
||||
log.info("Web platform initialized (" + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
}
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ public class CBJettyServer {
|
||||
return 1;
|
||||
}
|
||||
}*/;
|
||||
var maxIdleSeconds = application.getSessionManager().getMaxSessionIdleTime();
|
||||
var maxIdleSeconds = application.getMaxSessionIdleTime();
|
||||
int intMaxIdleSeconds;
|
||||
if (maxIdleSeconds > Integer.MAX_VALUE) {
|
||||
log.warn("Max session idle time value is greater than Integer.MAX_VALUE. Integer.MAX_VALUE will be used instead");
|
||||
|
||||
+21
-22
@@ -14,50 +14,49 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.server;
|
||||
package io.cloudbeaver.server.jobs;
|
||||
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
|
||||
/**
|
||||
* WebSessionMonitorJob
|
||||
*/
|
||||
class WebSessionMonitorJob extends AbstractJob {
|
||||
private static final Log log = Log.getLog(WebSessionMonitorJob.class);
|
||||
private static final int MONITOR_INTERVAL = 10000; // once per 10 seconds
|
||||
public abstract class PeriodicSystemJob extends AbstractJob {
|
||||
|
||||
private final CBPlatform platform;
|
||||
@NotNull
|
||||
protected final CBPlatform platform;
|
||||
private final long periodMs;
|
||||
|
||||
public PeriodicSystemJob(@NotNull String name, @NotNull CBPlatform platform, long periodMs) {
|
||||
super(name);
|
||||
this.platform = platform;
|
||||
this.periodMs = periodMs;
|
||||
|
||||
WebSessionMonitorJob(CBPlatform platform) {
|
||||
super("Web session monitor");
|
||||
setUser(false);
|
||||
setSystem(true);
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IStatus run(DBRProgressMonitor monitor) {
|
||||
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
|
||||
if (platform.isShuttingDown()) {
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
|
||||
try {
|
||||
platform.getSessionManager().expireIdleSessions();
|
||||
} catch (Exception e) {
|
||||
log.error("Error on expire idle sessions", e);
|
||||
}
|
||||
doJob(monitor);
|
||||
|
||||
// If the platform is still running after the job is completed, reschedule the job
|
||||
if (!platform.isShuttingDown()) {
|
||||
scheduleMonitor();
|
||||
}
|
||||
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
|
||||
void scheduleMonitor() {
|
||||
schedule(MONITOR_INTERVAL);
|
||||
}
|
||||
protected abstract void doJob(@NotNull DBRProgressMonitor monitor);
|
||||
|
||||
}
|
||||
public void scheduleMonitor() {
|
||||
schedule(periodMs);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2023 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.server.jobs;
|
||||
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
|
||||
public class SessionStateJob extends PeriodicSystemJob {
|
||||
private static final Log log = Log.getLog(SessionStateJob.class);
|
||||
private static final int PERIOD_MS = 60_000; // once per 60 seconds
|
||||
|
||||
public SessionStateJob(@NotNull CBPlatform platform) {
|
||||
super("Session state sender", platform, PERIOD_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doJob(@NotNull DBRProgressMonitor monitor) {
|
||||
try {
|
||||
platform.getSessionManager().sendSessionsStates();
|
||||
} catch (Exception e) {
|
||||
log.error("Error sending session state", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2023 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.server.jobs;
|
||||
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
|
||||
/**
|
||||
* WebSessionMonitorJob
|
||||
*/
|
||||
public class WebSessionMonitorJob extends PeriodicSystemJob {
|
||||
private static final Log log = Log.getLog(WebSessionMonitorJob.class);
|
||||
private static final int MONITOR_INTERVAL = 10000; // once per 10 seconds
|
||||
|
||||
public WebSessionMonitorJob(@NotNull CBPlatform platform) {
|
||||
super("Web session monitor", platform, MONITOR_INTERVAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doJob(@NotNull DBRProgressMonitor monitor) {
|
||||
try {
|
||||
platform.getSessionManager().expireIdleSessions();
|
||||
} catch (Exception e) {
|
||||
log.error("Error on expire idle sessions", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -29,8 +29,8 @@ import org.jkiss.dbeaver.model.websocket.WSUtils;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEventType;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSSocketConnectedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.client.WSUpdateActiveProjectsClientEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSocketConnectedEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
+8
-3
@@ -50,7 +50,6 @@ import org.jkiss.dbeaver.model.net.DBWTunnel;
|
||||
import org.jkiss.dbeaver.model.net.ssh.SSHImplementation;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.secret.DBSSecretController;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
@@ -61,13 +60,13 @@ import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Web service implementation
|
||||
@@ -235,6 +234,12 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
return webSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's permissions
|
||||
*
|
||||
* @deprecated CB-2773. The actual way to get session state is {@code WSSessionStateEvent} which sends periodically via web socket.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public WebSession getSessionState(@NotNull WebSession webSession) throws DBWebException {
|
||||
try {
|
||||
|
||||
+17
-18
@@ -25,7 +25,6 @@ import io.cloudbeaver.model.session.WebSessionAuthProcessor;
|
||||
import io.cloudbeaver.registry.WebHandlerRegistry;
|
||||
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import io.cloudbeaver.service.DBWSessionHandler;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -34,7 +33,7 @@ import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
|
||||
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionStateEvent;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.*;
|
||||
@@ -50,11 +49,6 @@ public class WebSessionManager {
|
||||
|
||||
private static final Log log = Log.getLog(WebSessionManager.class);
|
||||
|
||||
/**
|
||||
* In configuration mode sessions expire after a week
|
||||
*/
|
||||
private static final long CONFIGURATION_MODE_SESSION_IDLE_TIME = 60 * 60 * 1000 * 24 * 7;
|
||||
|
||||
private final CBApplication application;
|
||||
private final Map<String, BaseWebSession> sessionMap = new HashMap<>();
|
||||
|
||||
@@ -88,8 +82,7 @@ public class WebSessionManager {
|
||||
public boolean touchSession(@NotNull HttpServletRequest request,
|
||||
@NotNull HttpServletResponse response) throws DBWebException {
|
||||
WebSession webSession = getWebSession(request, response, false);
|
||||
long maxSessionIdleTime = CBApplication.getInstance().getMaxSessionIdleTime();
|
||||
webSession.updateInfo(request, response, maxSessionIdleTime);
|
||||
webSession.updateInfo(request, response);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -149,7 +142,7 @@ public class WebSessionManager {
|
||||
if (updateInfo) {
|
||||
// Update only once per request
|
||||
if (!CommonUtils.toBoolean(request.getAttribute("sessionUpdated"))) {
|
||||
webSession.updateInfo(request, response, application.getMaxSessionIdleTime());
|
||||
webSession.updateInfo(request, response);
|
||||
request.setAttribute("sessionUpdated", true);
|
||||
}
|
||||
}
|
||||
@@ -224,7 +217,7 @@ public class WebSessionManager {
|
||||
|
||||
@NotNull
|
||||
protected WebSession createWebSessionImpl(@NotNull HttpSession httpSession) throws DBException {
|
||||
return new WebSession(httpSession, application, getSessionHandlers(), application.getMaxSessionIdleTime());
|
||||
return new WebSession(httpSession, application, getSessionHandlers());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -264,14 +257,8 @@ public class WebSessionManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getMaxSessionIdleTime() {
|
||||
return CBApplication.getInstance().isConfigurationMode()
|
||||
? CONFIGURATION_MODE_SESSION_IDLE_TIME
|
||||
: DBWorkbench.getPlatform(CBPlatform.class).getApplication().getMaxSessionIdleTime();
|
||||
}
|
||||
|
||||
public void expireIdleSessions() {
|
||||
long maxSessionIdleTime = getMaxSessionIdleTime();
|
||||
long maxSessionIdleTime = application.getMaxSessionIdleTime();
|
||||
|
||||
List<BaseWebSession> expiredList = new ArrayList<>();
|
||||
synchronized (sessionMap) {
|
||||
@@ -335,4 +322,16 @@ public class WebSessionManager {
|
||||
return headlessSession;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send session state with remaining alive time to all cached session
|
||||
*/
|
||||
public void sendSessionsStates() throws DBException {
|
||||
synchronized (sessionMap) {
|
||||
for (var session : sessionMap.values()) {
|
||||
session.getUserContext().refreshPermissions();
|
||||
session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import type { WsSessionExpiredEvent } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { ServerEventId, SessionEventSource } from './SessionEventSource';
|
||||
import { SessionExpireService } from './SessionExpireService';
|
||||
|
||||
@injectable()
|
||||
export class SessionExpireEventService extends Dependency {
|
||||
constructor(
|
||||
private readonly sessionEventSource: SessionEventSource,
|
||||
private readonly sessionExpireService: SessionExpireService,
|
||||
) {
|
||||
super();
|
||||
this.sessionEventSource.onEvent<WsSessionExpiredEvent>(ServerEventId.CbSessionExpired, () => {
|
||||
this.onSessionExpireEvent();
|
||||
});
|
||||
}
|
||||
|
||||
private onSessionExpireEvent(): void {
|
||||
this.sessionExpireService.sessionExpired();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import type { WsSessionStateEvent as ISessionStateEvent } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { TopicEventHandler } from './ServerEventEmitter/TopicEventHandler';
|
||||
import { ISessionEvent, SessionEventId, SessionEventSource, SessionEventTopic } from './SessionEventSource';
|
||||
|
||||
export { type ISessionStateEvent };
|
||||
|
||||
@injectable()
|
||||
export class SessionInfoEventHandler
|
||||
extends TopicEventHandler<ISessionStateEvent, ISessionEvent, SessionEventId, SessionEventTopic> {
|
||||
constructor(
|
||||
sessionEventSource: SessionEventSource
|
||||
) {
|
||||
super(SessionEventTopic.CbSession, sessionEventSource);
|
||||
}
|
||||
|
||||
map(event: any): ISessionStateEvent {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedDataResource,
|
||||
@@ -14,6 +15,8 @@ import {
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { ServerConfigResource } from './ServerConfigResource';
|
||||
import { ServerEventId } from './SessionEventSource';
|
||||
import { SessionInfoEventHandler } from './SessionInfoEventHandler';
|
||||
|
||||
export type SessionState = SessionStateFragment;
|
||||
export interface ISessionAction {
|
||||
@@ -21,17 +24,29 @@ export interface ISessionAction {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface SessionStateData {
|
||||
isValid?: boolean;
|
||||
remainingTime: number;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SessionResource extends CachedDataResource<SessionState | null> {
|
||||
private action: ISessionAction | null;
|
||||
private defaultLocale: string | undefined;
|
||||
readonly onStatusUpdate: ISyncExecutor<SessionStateData>;
|
||||
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
sessionInfoEventHandler: SessionInfoEventHandler,
|
||||
serverConfigResource: ServerConfigResource
|
||||
) {
|
||||
super(() => null);
|
||||
|
||||
this.onStatusUpdate = new SyncExecutor();
|
||||
sessionInfoEventHandler.onEvent(ServerEventId.CbSessionState, event => {
|
||||
this.onStatusUpdate.execute(event);
|
||||
}, undefined, this);
|
||||
|
||||
this.action = null;
|
||||
this.sync(serverConfigResource, () => {}, () => {});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export * from './SessionActionService';
|
||||
export * from './SessionDataResource';
|
||||
export * from './SessionSettingsService';
|
||||
export * from './SessionExpireService';
|
||||
export * from './SessionExpireEventService';
|
||||
export * from './ServerNodeService';
|
||||
export * from './WindowEventsService';
|
||||
export { manifest as coreRootManifest } from './manifest';
|
||||
|
||||
@@ -21,7 +21,9 @@ import { ServerSettingsService } from './ServerSettingsService';
|
||||
import { SessionActionService } from './SessionActionService';
|
||||
import { SessionDataResource } from './SessionDataResource';
|
||||
import { SessionEventSource } from './SessionEventSource';
|
||||
import { SessionExpireEventService } from './SessionExpireEventService';
|
||||
import { SessionExpireService } from './SessionExpireService';
|
||||
import { SessionInfoEventHandler } from './SessionInfoEventHandler';
|
||||
import { SessionPermissionsResource } from './SessionPermissionsResource';
|
||||
import { SessionResource } from './SessionResource';
|
||||
import { SessionService } from './SessionService';
|
||||
@@ -45,6 +47,7 @@ export const manifest: PluginManifest = {
|
||||
SessionActionService,
|
||||
SessionDataResource,
|
||||
SessionExpireService,
|
||||
SessionExpireEventService,
|
||||
ServerNodeService,
|
||||
SessionResource,
|
||||
SessionService,
|
||||
@@ -53,6 +56,7 @@ export const manifest: PluginManifest = {
|
||||
QuotasService,
|
||||
ServerConfigEventHandler,
|
||||
SessionEventSource,
|
||||
SessionInfoEventHandler,
|
||||
DataSynchronizationService,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -249,7 +249,9 @@ export enum CbServerEventId {
|
||||
CbRmResourceCreated = 'cb_rm_resource_created',
|
||||
CbRmResourceDeleted = 'cb_rm_resource_deleted',
|
||||
CbRmResourceUpdated = 'cb_rm_resource_updated',
|
||||
CbSessionExpired = 'cb_session_expired',
|
||||
CbSessionLogUpdated = 'cb_session_log_updated',
|
||||
CbSessionState = 'cb_session_state',
|
||||
CbSessionWebsocketConnected = 'cb_session_websocket_connected'
|
||||
}
|
||||
|
||||
@@ -1732,6 +1734,18 @@ export interface UserInfo {
|
||||
userId: Scalars['ID'];
|
||||
}
|
||||
|
||||
export interface WsSessionExpiredEvent extends CbServerEvent {
|
||||
id: CbServerEventId;
|
||||
topicId?: Maybe<CbEventTopic>;
|
||||
}
|
||||
|
||||
export interface WsSessionStateEvent extends CbServerEvent {
|
||||
id: CbServerEventId;
|
||||
isValid?: Maybe<Scalars['Boolean']>;
|
||||
remainingTime: Scalars['Int'];
|
||||
topicId?: Maybe<CbEventTopic>;
|
||||
}
|
||||
|
||||
export interface WsSocketConnectedEvent extends CbServerEvent {
|
||||
applicationRunId: Scalars['String'];
|
||||
id: CbServerEventId;
|
||||
|
||||
+28
-39
@@ -15,7 +15,6 @@ import { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { SessionExpireWarningDialog } from './SessionExpireWarningDialog';
|
||||
|
||||
const WARN_IN = 5 * 1000 * 60;
|
||||
const POLL_INTERVAL = 1 * 1000 * 60;
|
||||
|
||||
@injectable()
|
||||
export class SessionExpireWarningDialogService extends Bootstrap {
|
||||
@@ -27,7 +26,7 @@ export class SessionExpireWarningDialogService extends Bootstrap {
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly sessionResource: SessionResource,
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
private readonly graphQLService: GraphQLService
|
||||
private readonly graphQLService: GraphQLService,
|
||||
) {
|
||||
super();
|
||||
this.dialogInternalPromise = null;
|
||||
@@ -35,50 +34,40 @@ export class SessionExpireWarningDialogService extends Bootstrap {
|
||||
|
||||
register(): void {
|
||||
this.sessionExpireService.onSessionExpire.addHandler(this.close.bind(this));
|
||||
this.sessionResource.onStatusUpdate.addHandler((data, contexts) => {
|
||||
this.handleStateChange(data.isValid, data.remainingTime);
|
||||
});
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.startSessionPolling();
|
||||
}
|
||||
load(): void { }
|
||||
|
||||
private startSessionPolling() {
|
||||
const checkSessionStatus = async () => {
|
||||
if (
|
||||
!this.serverConfigResource.anonymousAccessEnabled
|
||||
&& !this.userInfoResource.data
|
||||
&& !this.serverConfigResource.configurationMode
|
||||
) {
|
||||
return;
|
||||
}
|
||||
private handleStateChange(isValid?: boolean, remainingTime?: number) {
|
||||
if (
|
||||
!this.serverConfigResource.anonymousAccessEnabled
|
||||
&& !this.userInfoResource.data
|
||||
&& !this.serverConfigResource.configurationMode
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { sessionState } = await this.graphQLService.sdk.sessionState();
|
||||
if (!isValid) {
|
||||
this.close();
|
||||
this.sessionExpireService.sessionExpired();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sessionState.valid) {
|
||||
this.close();
|
||||
this.sessionExpireService.sessionExpired();
|
||||
return;
|
||||
}
|
||||
const sessionDuration = this.serverConfigResource.data?.sessionExpireTime;
|
||||
|
||||
const sessionDuration = this.serverConfigResource.data?.sessionExpireTime;
|
||||
if (this.sessionExpireService.expired || !sessionDuration || sessionDuration < WARN_IN) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sessionExpireService.expired || !sessionDuration || sessionDuration < WARN_IN) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionState.remainingTime < WARN_IN) {
|
||||
this.open();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
const poll = async () => {
|
||||
await checkSessionStatus();
|
||||
setTimeout(poll, POLL_INTERVAL);
|
||||
};
|
||||
|
||||
setTimeout(poll, POLL_INTERVAL);
|
||||
if (remainingTime !== undefined && remainingTime < WARN_IN) {
|
||||
this.open();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
private async open(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user