From d93d7748cf7f27a96a957777b27f92a206d3b81d Mon Sep 17 00:00:00 2001 From: Alexander Skoblikov Date: Fri, 28 Jul 2023 14:20:47 +0400 Subject: [PATCH] Cb 3738 exception when logout invalid update token (#1853) * CB-3738 log all gql errors * CB-3310 do not update token for child sessions * CB-3738 skip headless session on refresh --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> --- .../model/session/WebUserContext.java | 24 ++++++--- .../server/jobs/SessionStateJob.java | 2 +- .../service/WebServiceBindingBase.java | 1 + .../service/session/WebSessionManager.java | 13 +++-- .../CBEmbeddedSecurityController.java | 54 ++++++++++++------- 5 files changed, 64 insertions(+), 30 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java index b118e206f2..9195b54ef5 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java @@ -80,16 +80,21 @@ public class WebUserContext implements SMCredentialsProvider { * refresh context state based on new token from security manager * * @param smAuthInfo - auth info from security manager + * @return - true if context changed * @throws DBException - if user already authorized and new token come from another user */ public synchronized boolean refresh(SMAuthInfo smAuthInfo) throws DBException { - if (smAuthInfo.getAuthStatus() != SMAuthStatus.SUCCESS || smAuthInfo.getSmAccessToken() == null) { - throw new DBCException("Authorization did not complete successfully"); - } - if (smAuthInfo.getAuthPermissions() == null) { + if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) { throw new DBCException("Required information about session permissions is missing"); } - return refresh(smAuthInfo.getSmAccessToken(), smAuthInfo.getSmRefreshToken(), smAuthInfo.getAuthPermissions()); + boolean sessionChanged = !CommonUtils.equalObjects(smSessionId, smAuthInfo.getAuthPermissions().getSessionId()); + if (smAuthInfo.getAuthStatus() != SMAuthStatus.SUCCESS || (sessionChanged && smAuthInfo.getSmAccessToken() == null)) { + throw new DBCException("Authorization did not complete successfully"); + } + if (sessionChanged) { + return refresh(smAuthInfo.getSmAccessToken(), smAuthInfo.getSmRefreshToken(), smAuthInfo.getAuthPermissions()); + } + return false; } public synchronized boolean refresh( @@ -108,7 +113,7 @@ public class WebUserContext implements SMCredentialsProvider { smAuthPermissions.getSessionId(), smAuthPermissions.getPermissions() ); - this.refreshToken = smRefreshToken; + setRefreshToken(smRefreshToken); setUserPermissions(smAuthPermissions.getPermissions()); this.securityController = application.createSecurityController(this); this.adminSecurityController = application.getAdminSecurityController(this); @@ -131,7 +136,7 @@ public class WebUserContext implements SMCredentialsProvider { return; } var newTokens = securityController.refreshSession(refreshToken); - this.refreshToken = newTokens.getSmRefreshToken(); + setRefreshToken(newTokens.getSmRefreshToken()); this.smCredentials = new SMCredentials( newTokens.getSmAccessToken(), smCredentials.getUserId(), @@ -204,6 +209,7 @@ public class WebUserContext implements SMCredentialsProvider { */ public synchronized void refreshPermissions() throws DBException { if (isAuthorizedInSecurityManager()) { + log.debug("refresh permissions " + getUserId() + " " + getSmSessionId()); setUserPermissions(securityController.getTokenPermissions().getPermissions()); } else { setUserPermissions(getDefaultPermissions()); @@ -238,4 +244,8 @@ public class WebUserContext implements SMCredentialsProvider { public Set getAccessibleProjectIds() { return accessibleProjectIds; } + + private void setRefreshToken(@Nullable String refreshToken) { + this.refreshToken = refreshToken; + } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/SessionStateJob.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/SessionStateJob.java index 15ffe85b79..fd3c72e29d 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/SessionStateJob.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/SessionStateJob.java @@ -23,7 +23,7 @@ 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 + private static final int PERIOD_MS = 30_000; // once per 60 seconds public SessionStateJob(@NotNull CBPlatform platform) { super("Session state sender", platform, PERIOD_MS); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index 19b094e3fb..220389e12f 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -174,6 +174,7 @@ public abstract class WebServiceBindingBase impleme throw e.getTargetException(); } } catch (Throwable ex) { + log.error("Unexpected error during gql request", ex); if (SMUtils.isTokenExpiredExceptionWasHandled(ex)) { WebSession webSession = findWebSession(env); if (webSession != null) { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java index fc4487f6d9..25f00b8b64 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/session/WebSessionManager.java @@ -326,11 +326,18 @@ public class WebSessionManager { /** * Send session state with remaining alive time to all cached session */ - public void sendSessionsStates() throws DBException { + public void sendSessionsStates() { synchronized (sessionMap) { for (var session : sessionMap.values()) { - session.getUserContext().refreshPermissions(); - session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid())); + if (session instanceof WebHeadlessSession) { + continue; + } + try { + session.getUserContext().refreshPermissions(); + session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid())); + } catch (Exception e) { + log.error("Failed to refresh session state: " + session.getSessionId(), e); + } } } } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index 75b3505e2f..01e6fdc600 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -1183,7 +1183,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen var smTokens = generateNewSessionToken(smSessionId, null, null, dbCon); var permissions = getAnonymousUserPermissions(); txn.commit(); - return SMAuthInfo.success( + return SMAuthInfo.successMainSession( UUID.randomUUID().toString(), smTokens.getSmAccessToken(), smTokens.getSmRefreshToken(), @@ -1215,6 +1215,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen var authProgressMonitor = new LoggingProgressMonitor(log); try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { + boolean isMainSession = previousSmSessionId == null; Map securedUserIdentifyingCredentials = userCredentials; WebAuthProviderDescriptor authProviderDescriptor = getAuthProvider(authProviderId); var authProviderInstance = authProviderDescriptor.getInstance(); @@ -1236,7 +1237,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen appSessionId, previousSmSessionId, sessionType, - sessionParameters + sessionParameters, + isMainSession ); if (SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) { @@ -1252,7 +1254,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen return finishAuthentication( SMAuthInfo.inProgress( authAttemptId, - null, Map.of(new SMAuthConfigurationReference(authProviderId, null), securedUserIdentifyingCredentials) + null, + Map.of(new SMAuthConfigurationReference(authProviderId, null), securedUserIdentifyingCredentials) ), true, false @@ -1286,7 +1289,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen String appSessionId, String prevSessionId, SMSessionType sessionType, - Map sessionParameters + Map sessionParameters, + boolean isMainSession ) throws DBException { String authAttemptId = UUID.randomUUID().toString(); try (Connection dbCon = database.openConnection()) { @@ -1489,7 +1493,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen SMTokens smTokens = findTokenBySmSession(smSessionId); SMAuthPermissions authPermissions = getTokenPermissions(smTokens.getSmAccessToken()); String authRole = readTokenAuthRole(smTokens.getSmAccessToken()); - var successAuthStatus = SMAuthInfo.success( + var successAuthStatus = SMAuthInfo.successMainSession( authId, smTokens.getSmAccessToken(), smTokens.getSmRefreshToken(), @@ -1517,7 +1521,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen for (SMAuthInfo authData : allLatestAuthData) { mergedData.putAll(authData.getAuthData()); } - return SMAuthInfo.success( + return SMAuthInfo.successMainSession( "restore_session_attempt_" + UUID.randomUUID(), latestActiveSmTokens.getAccessToken(), latestActiveSmTokens.getRefreshToken(), @@ -1597,6 +1601,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen return currentUserCreds; } + @NotNull private SMTokens findTokenBySmSession(String smSessionId) throws DBException { try (Connection dbCon = database.openConnection(); PreparedStatement dbStat = dbCon.prepareStatement( @@ -1697,12 +1702,13 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen SMTokens smTokens = null; SMAuthPermissions permissions = null; + String activeUserId = null; if (!isMainAuthSession) { + var accessToken = findTokenBySmSession(authAttemptSessionInfo.getSmSessionId()).getSmAccessToken(); //this is an additional authorization and we should to return the original permissions and userId - smTokens = findTokenBySmSession(authAttemptSessionInfo.getSmSessionId()); - permissions = getTokenPermissions(smTokens.getSmAccessToken()); + permissions = getTokenPermissions(accessToken); + activeUserId = permissions.getUserId(); } - String activeUserId = permissions == null ? null : permissions.getUserId(); Map storedUserData = new LinkedHashMap<>(); SMTeam[] allTeams = null; @@ -1768,7 +1774,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } String tokenAuthRole = updateUserAuthRoleIfNeeded(activeUserId, detectedAuthRole); - if (smTokens == null && permissions == null) { + if (isMainAuthSession) { try (Connection dbCon = database.openConnection()) { try (JDBCTransaction txn = new JDBCTransaction(dbCon)) { String smSessionId; @@ -1798,15 +1804,24 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } var authStatus = forceExpireAuthAfterSuccess ? SMAuthStatus.EXPIRED : SMAuthStatus.SUCCESS; updateAuthStatus(authId, authStatus, storedUserData, null, permissions.getSessionId()); - return SMAuthInfo.success( - authId, - smTokens.getSmAccessToken(), - //refresh token must be sent only once - isMainAuthSession ? smTokens.getSmRefreshToken() : null, - permissions, - authInfo.getAuthData(), - tokenAuthRole - ); + + if (isMainAuthSession) { + return SMAuthInfo.successMainSession( + authId, + smTokens.getSmAccessToken(), + //refresh token must be sent only from main session + smTokens.getSmRefreshToken(), + permissions, + authInfo.getAuthData(), + tokenAuthRole + ); + } else { + return SMAuthInfo.successChildSession( + authId, + permissions, + authInfo.getAuthData() + ); + } } private void autoUpdateUserTeams( @@ -2057,6 +2072,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen return getTokenPermissions(activeUserCredentials.getSmAccessToken()); } + @NotNull private SMAuthPermissions getTokenPermissions(@NotNull String token) throws DBException { String userId; String sessionId;