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:
Alexander Skoblikov
2023-04-19 14:15:27 +03:00
committed by GitHub
co-authored by Ainur Yaroslav Emelyanov Tatiana Fokina dariamarutkina
parent 48dee829a7
commit 3bd2288564
23 changed files with 323 additions and 120 deletions
@@ -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;
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
@@ -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)");
}
@@ -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");
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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;
@@ -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 {
@@ -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, () => {}, () => {});
}
+1
View File
@@ -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,
],
};
+14
View File
@@ -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;
@@ -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> {