From 1dc6a119c2518d6d8355b07caf886ad653c257bc Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Fri, 12 Jan 2024 15:51:23 +0400 Subject: [PATCH 01/11] CB-4039 build logout redirect link on backend side --- .../auth/SMAuthProviderFederated.java | 17 ++++++++---- .../model/session/WebAuthInfo.java | 2 -- .../cloudbeaver/model/session/WebSession.java | 12 ++++++--- .../WebAuthProviderConfiguration.java | 2 +- .../schema/service.auth.graphqls | 6 ++++- .../service/auth/DBWServiceAuth.java | 6 ++++- .../service/auth/WebLogoutInfo.java | 24 +++++++++++++++++ .../service/auth/impl/WebServiceAuthImpl.java | 26 +++++++++++++++++-- .../CBEmbeddedSecurityController.java | 12 ++++++--- 9 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebLogoutInfo.java diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java index 1747122729..1ec4d407ed 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderFederated.java @@ -19,6 +19,7 @@ import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; import java.util.Map; @@ -28,15 +29,21 @@ */ public interface SMAuthProviderFederated { - /** - * Returns new identifying credentials which can be used to find/create user in database - */ @NotNull String getSignInLink(String id, @NotNull Map providerConfig) throws DBException; - + /** + * @return a common link for logout, not related with the user context + */ @NotNull - String getSignOutLink(String id, @NotNull Map providerConfig) throws DBException; + String getCommonSignOutLink(String id, @NotNull Map providerConfig) throws DBException; + + default String getUserSignOutLink( + @NotNull SMAuthProviderCustomConfiguration providerConfig, + @NotNull Map userCredentials + ) throws DBException { + return getCommonSignOutLink(providerConfig.getId(), providerConfig.getParameters()); + } @Nullable String getMetadataLink(String id, @NotNull Map providerConfig) throws DBException; diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java index 0d2abe7549..5c178215d9 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java @@ -131,8 +131,6 @@ void closeAuth() { authProviderInstance.closeSession(session, authSession); } catch (Exception e) { log.error(e); - } finally { - authSession = null; } } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java index fbff2dbbda..2145ccc568 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java @@ -614,7 +614,7 @@ public void close() { super.close(); } - private void clearAuthTokens() throws DBException { + private List clearAuthTokens() throws DBException { ArrayList tokensCopy; synchronized (authTokens) { tokensCopy = new ArrayList<>(this.authTokens); @@ -623,6 +623,7 @@ private void clearAuthTokens() throws DBException { removeAuthInfo(ai); } resetAuthToken(); + return tokensCopy; } public DBRProgressMonitor getProgressMonitor() { @@ -873,18 +874,23 @@ private void removeAuthInfo(WebAuthInfo oldAuthInfo) { } } - public void removeAuthInfo(String providerId) throws DBException { + public List removeAuthInfo(String providerId) throws DBException { + List oldInfo; if (providerId == null) { - clearAuthTokens(); + oldInfo = clearAuthTokens(); } else { WebAuthInfo authInfo = getAuthInfo(providerId); if (authInfo != null) { removeAuthInfo(authInfo); + oldInfo = List.of(authInfo); + } else { + oldInfo = List.of(); } } if (authTokens.isEmpty()) { resetUserState(); } + return oldInfo; } public List getContextCredentialsProviders() { diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderConfiguration.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderConfiguration.java index 266382b8cc..46712a0a59 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderConfiguration.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderConfiguration.java @@ -86,7 +86,7 @@ private String buildRedirectUrl(String baseUrl) { public String getSignOutLink() throws DBException { SMAuthProvider instance = providerDescriptor.getInstance(); return instance instanceof SMAuthProviderFederated - ? ((SMAuthProviderFederated) instance).getSignOutLink(getId(), config.getParameters()) + ? ((SMAuthProviderFederated) instance).getCommonSignOutLink(getId(), config.getParameters()) : null; } diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index 82530a3bc7..b5e8db47fc 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -86,6 +86,10 @@ type AuthInfo { userTokens: [UserAuthToken!] } +type LogoutInfo { + redirectLinks: [String!]! +} + type UserAuthToken { # Auth provider used for authorization authProvider: ID! @@ -139,7 +143,7 @@ extend type Query { authUpdateStatus(authId: ID!, linkUser: Boolean): AuthInfo! # Logouts user. If provider not specified then all authorizations are revoked from session. - authLogout(provider: ID, configuration: ID): Boolean + authLogout(provider: ID, configuration: ID): LogoutInfo # Active user information. null is no user was authorized within session activeUser: UserInfo 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 777b0dc7b4..c0c9c22c24 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 @@ -45,7 +45,11 @@ WebAuthStatus authLogin( WebAuthStatus authUpdateStatus(@NotNull WebSession webSession, @NotNull String authId, boolean linkWithActiveUser) throws DBWebException; @WebAction(authRequired = false) - void authLogout(@NotNull WebSession webSession, @Nullable String providerId, @Nullable String configurationId) throws DBWebException; + WebLogoutInfo authLogout( + @NotNull WebSession webSession, + @Nullable String providerId, + @Nullable String configurationId + ) throws DBWebException; @WebAction(authRequired = false) WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException; diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebLogoutInfo.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebLogoutInfo.java new file mode 100644 index 0000000000..9e8aaefb3d --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebLogoutInfo.java @@ -0,0 +1,24 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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.service.auth; + +import org.jkiss.code.NotNull; + +import java.util.List; + +public record WebLogoutInfo(@NotNull List redirectLinks) { +} 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 e16245f8dd..0d1f3db208 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 @@ -18,6 +18,7 @@ import io.cloudbeaver.DBWebException; import io.cloudbeaver.WebServiceUtils; +import io.cloudbeaver.auth.SMAuthProviderFederated; import io.cloudbeaver.auth.provider.local.LocalAuthProvider; import io.cloudbeaver.model.WebPropertyInfo; import io.cloudbeaver.model.session.WebAuthInfo; @@ -31,6 +32,7 @@ import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.auth.DBWServiceAuth; import io.cloudbeaver.service.auth.WebAuthStatus; +import io.cloudbeaver.service.auth.WebLogoutInfo; import io.cloudbeaver.service.auth.WebUserInfo; import io.cloudbeaver.service.security.SMUtils; import org.jkiss.code.NotNull; @@ -39,6 +41,7 @@ import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.auth.SMAuthInfo; import org.jkiss.dbeaver.model.auth.SMAuthStatus; +import org.jkiss.dbeaver.model.auth.SMSessionExternal; import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.security.SMController; import org.jkiss.dbeaver.model.security.SMSubjectType; @@ -131,7 +134,7 @@ public WebAuthStatus authUpdateStatus(@NotNull WebSession webSession, @NotNull S } @Override - public void authLogout( + public WebLogoutInfo authLogout( @NotNull WebSession webSession, @Nullable String providerId, @Nullable String configurationId @@ -140,7 +143,26 @@ public void authLogout( throw new DBWebException("Not logged in"); } try { - webSession.removeAuthInfo(providerId); + List removedInfos = webSession.removeAuthInfo(providerId); + List logoutUrls = new ArrayList<>(); + var cbApp = CBApplication.getInstance(); + for (WebAuthInfo removedInfo : removedInfos) { + if (removedInfo.getAuthProviderDescriptor() + .getInstance() instanceof SMAuthProviderFederated federatedProvider + && removedInfo.getAuthSession() instanceof SMSessionExternal externalSession + ) { + var providerConfig = + cbApp.getAuthConfiguration().getAuthProviderConfiguration(removedInfo.getAuthConfiguration()); + if (providerConfig == null) { + log.warn(removedInfo.getAuthConfiguration() + " provider configuration wasn't found"); + continue; + } + String logoutUrl = federatedProvider.getUserSignOutLink(providerConfig, + externalSession.getAuthParameters()); + logoutUrls.add(logoutUrl); + } + } + return new WebLogoutInfo(logoutUrls); } catch (DBException e) { throw new DBWebException("User logout failed", 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 e451dff1c0..c7ab132345 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 @@ -1348,7 +1348,8 @@ public SMAuthInfo authenticate( var authProviderFederated = (SMAuthProviderFederated) authProviderInstance; String signInLink = buildRedirectLink(authProviderFederated.getSignInLink(authProviderConfigurationId, Map.of()), authAttemptId); - String signOutLink = authProviderFederated.getSignOutLink(authProviderConfigurationId, Map.of()); + String signOutLink = authProviderFederated.getCommonSignOutLink(authProviderConfigurationId, + Map.of()); Map authData = Map.of(new SMAuthConfigurationReference(authProviderId, authProviderConfigurationId), filteredUserCreds); return SMAuthInfo.inProgress(authAttemptId, signInLink, signOutLink, authData, isMainSession); @@ -1621,9 +1622,12 @@ private SMAuthInfo getAuthStatus(@NotNull String authId, boolean readExpiredData signInLink = buildRedirectLink(((SMAuthProviderFederated) authProviderInstance).getRedirectLink( authProviderConfiguration, Map.of()), authId); - signOutLink = buildRedirectLink(((SMAuthProviderFederated) authProviderInstance).getSignOutLink( - authProviderConfiguration, - Map.of()), authId); + var userCustomSignOutLink = + ((SMAuthProviderFederated) authProviderInstance).getUserSignOutLink( + application.getAuthConfiguration() + .getAuthProviderConfiguration(authProviderConfiguration), + authProviderData); + signOutLink = userCustomSignOutLink; } } From 5585b077e80e8dea64b1529b3e7fc6f353b72b9f Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Fri, 12 Jan 2024 20:21:21 +0400 Subject: [PATCH 02/11] CB-4039 fix return value --- .../service/auth/WebServiceBindingAuth.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 37d82cc772..6c7d3a418c 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 @@ -42,14 +42,11 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { env.getArgument("configuration"), env.getArgument("credentials"), CommonUtils.toBoolean(env.getArgument("linkUser")))) - .dataFetcher("authLogout", env -> { - getService(env).authLogout( - getWebSession(env, false), - env.getArgument("provider"), - env.getArgument("configuration") - ); - return true; - }) + .dataFetcher("authLogout", env -> getService(env).authLogout( + getWebSession(env, false), + env.getArgument("provider"), + env.getArgument("configuration") + )) .dataFetcher("authUpdateStatus", env -> getService(env).authUpdateStatus( getWebSession(env), env.getArgument("authId"), From 5602f68d56e9cf62d510251abf3528f43c3420d0 Mon Sep 17 00:00:00 2001 From: "s.teleshev" Date: Tue, 16 Jan 2024 12:36:19 +0100 Subject: [PATCH 03/11] CB-4039 adds logout with context for Okta Identity Provider --- .../src/UserInfoResource.ts | 19 +++++++++++- .../src/queries/authentication/authLogout.gql | 14 ++++----- .../core-utils/src/getUniqueId.test.ts | 30 +++++++++++++++++++ webapp/packages/core-utils/src/getUniqueId.ts | 10 +++++++ webapp/packages/core-utils/src/index.ts | 1 + 5 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 webapp/packages/core-utils/src/getUniqueId.test.ts create mode 100644 webapp/packages/core-utils/src/getUniqueId.ts diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index aa268c7f70..b27f24ab1f 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -11,7 +11,9 @@ import { injectable } from '@cloudbeaver/core-di'; import { AutoRunningTask, ISyncExecutor, ITask, SyncExecutor, whileTask } from '@cloudbeaver/core-executor'; import { CachedDataResource, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource'; import { SessionDataResource, SessionResource } from '@cloudbeaver/core-root'; +import { WindowsService } from '@cloudbeaver/core-routing'; import { AuthInfo, AuthStatus, GetActiveUserQueryVariables, GraphQLService, UserInfo } from '@cloudbeaver/core-sdk'; +import { getUniqueId } from '@cloudbeaver/core-utils'; import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID'; import { AuthProviderService } from './AuthProviderService'; @@ -46,6 +48,7 @@ export class UserInfoResource extends CachedDataResource null, undefined, ['customIncludeOriginDetails', 'includeConfigurationParameters']); @@ -152,11 +155,25 @@ export class UserInfoResource extends CachedDataResource { - await this.graphQLService.sdk.authLogout({ + const { authLogout } = await this.graphQLService.sdk.authLogout({ provider, configuration, }); + // TODO handle all redirect links once we know what to do with multiple popups issue + const redirectLinks = authLogout?.redirectLinks || []; + + if (redirectLinks.length) { + const oktaLink = authLogout?.redirectLinks[0]; + const id = `okta-logout-id-${getUniqueId()}`; + + this.windowsService.open(id, { + url: oktaLink, + width: 400, + height: 400, + }); + } + this.resetIncludes(); this.setData(await this.loader()); this.sessionDataResource.markOutdated(); diff --git a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql index 86ec3b1489..cfb6ff30be 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql @@ -1,9 +1,5 @@ -query authLogout( - $provider: ID - $configuration: ID -) { - authLogout( - provider: $provider - configuration: $configuration - ) -} \ No newline at end of file +query authLogout($provider: ID, $configuration: ID) { + authLogout(provider: $provider, configuration: $configuration) { + redirectLinks + } +} diff --git a/webapp/packages/core-utils/src/getUniqueId.test.ts b/webapp/packages/core-utils/src/getUniqueId.test.ts new file mode 100644 index 0000000000..6265873278 --- /dev/null +++ b/webapp/packages/core-utils/src/getUniqueId.test.ts @@ -0,0 +1,30 @@ +import { getUniqueId } from './getUniqueId'; + +describe('getUniqueId', () => { + const uniqueIdsCount = 100; + + it('should return unique ids', () => { + const ids = new Set(); + for (let i = 0; i < uniqueIdsCount; i++) { + ids.add(getUniqueId()); + } + expect(ids.size).toBe(uniqueIdsCount); + }); + + it('should return unique ids in parallel', async () => { + const ids = new Set(); + const promises = []; + for (let i = 0; i < uniqueIdsCount; i++) { + promises.push( + new Promise(resolve => { + setTimeout(() => { + ids.add(getUniqueId()); + resolve(true); + }, Math.random() * 100); + }), + ); + } + await Promise.all(promises); + expect(ids.size).toBe(uniqueIdsCount); + }); +}); diff --git a/webapp/packages/core-utils/src/getUniqueId.ts b/webapp/packages/core-utils/src/getUniqueId.ts new file mode 100644 index 0000000000..f142d2297a --- /dev/null +++ b/webapp/packages/core-utils/src/getUniqueId.ts @@ -0,0 +1,10 @@ +function getNextIdGenerator() { + let id = 0; + return () => id++; +} + +const getNextId = getNextIdGenerator(); + +export function getUniqueId() { + return getNextId(); +} diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index b471a596a2..d3b1c0b94b 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -36,6 +36,7 @@ export * from './isPropertiesEqual'; export * from './isSafari'; export * from './isSameDay'; export * from './isValuesEqual'; +export * from './getUniqueId'; export * from './md5'; export * from './MetadataMap'; export * from './OrderedMap'; From 18be4247baeca5acd4aeb0ccff1a248987dc6fcb Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Tue, 16 Jan 2024 16:51:51 +0400 Subject: [PATCH 04/11] CB-4039 backward compatibility fix --- .../schema/service.auth.graphqls | 6 +++++- .../cloudbeaver/service/auth/WebServiceBindingAuth.java | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index b5e8db47fc..fa7b3b58af 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -143,7 +143,11 @@ extend type Query { authUpdateStatus(authId: ID!, linkUser: Boolean): AuthInfo! # Logouts user. If provider not specified then all authorizations are revoked from session. - authLogout(provider: ID, configuration: ID): LogoutInfo + @deprecated + authLogout(provider: ID, configuration: ID): Boolean! + + # Same as #authLogout, but returns additional information + authLogoutExtended(provider: ID, configuration: ID): LogoutInfo! # Active user information. null is no user was authorized within session activeUser: UserInfo 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 6c7d3a418c..9ecc77b00a 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 @@ -42,11 +42,17 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { env.getArgument("configuration"), env.getArgument("credentials"), CommonUtils.toBoolean(env.getArgument("linkUser")))) - .dataFetcher("authLogout", env -> getService(env).authLogout( + .dataFetcher("authLogoutExtended", env -> getService(env).authLogout( getWebSession(env, false), env.getArgument("provider"), env.getArgument("configuration") )) + .dataFetcher("authLogout", env -> { + getService(env).authLogout(getWebSession(env, false), + env.getArgument("provider"), + env.getArgument("configuration")); + return true; + }) .dataFetcher("authUpdateStatus", env -> getService(env).authUpdateStatus( getWebSession(env), env.getArgument("authId"), From dfea9e73683d6c543ef35243e577f8f87a977c6c Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Tue, 16 Jan 2024 17:10:18 +0400 Subject: [PATCH 05/11] CB-4039 add since annotation --- .../io.cloudbeaver.service.auth/schema/service.auth.graphqls | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index fa7b3b58af..7601ffb332 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -86,7 +86,8 @@ type AuthInfo { userTokens: [UserAuthToken!] } -type LogoutInfo { + +type LogoutInfo @since(version: "23.3.3") { redirectLinks: [String!]! } @@ -147,6 +148,7 @@ extend type Query { authLogout(provider: ID, configuration: ID): Boolean! # Same as #authLogout, but returns additional information + @since(version: "23.3.3") authLogoutExtended(provider: ID, configuration: ID): LogoutInfo! # Active user information. null is no user was authorized within session From a547a29a2b8fd3175df4124f097e8e245fe83f04 Mon Sep 17 00:00:00 2001 From: Aleksandr Skoblikov Date: Tue, 16 Jan 2024 17:14:47 +0400 Subject: [PATCH 06/11] CB-4039 api return type fix --- .../io.cloudbeaver.service.auth/schema/service.auth.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index 7601ffb332..0f4fca656f 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -145,7 +145,7 @@ extend type Query { # Logouts user. If provider not specified then all authorizations are revoked from session. @deprecated - authLogout(provider: ID, configuration: ID): Boolean! + authLogout(provider: ID, configuration: ID): Boolean # Same as #authLogout, but returns additional information @since(version: "23.3.3") From 2d943a54772700a91f05107f5b5a2a4dcc547b41 Mon Sep 17 00:00:00 2001 From: "s.teleshev" Date: Tue, 16 Jan 2024 14:43:25 +0100 Subject: [PATCH 07/11] CB-4039 logout extended --- webapp/packages/core-authentication/src/UserInfoResource.ts | 6 +++--- .../core-sdk/src/queries/authentication/authLogout.gql | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index b27f24ab1f..74df6a7b46 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -155,16 +155,16 @@ export class UserInfoResource extends CachedDataResource { - const { authLogout } = await this.graphQLService.sdk.authLogout({ + const { authLogoutExtended } = await this.graphQLService.sdk.authLogout({ provider, configuration, }); // TODO handle all redirect links once we know what to do with multiple popups issue - const redirectLinks = authLogout?.redirectLinks || []; + const redirectLinks = authLogoutExtended?.redirectLinks || []; if (redirectLinks.length) { - const oktaLink = authLogout?.redirectLinks[0]; + const oktaLink = authLogoutExtended?.redirectLinks[0]; const id = `okta-logout-id-${getUniqueId()}`; this.windowsService.open(id, { diff --git a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql index cfb6ff30be..9420c4e5bf 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql @@ -1,5 +1,5 @@ query authLogout($provider: ID, $configuration: ID) { - authLogout(provider: $provider, configuration: $configuration) { + authLogoutExtended(provider: $provider, configuration: $configuration) { redirectLinks } } From 38341afed594e0b671564133bd9ee14d9b4290ba Mon Sep 17 00:00:00 2001 From: "s.teleshev" Date: Wed, 17 Jan 2024 11:41:21 +0100 Subject: [PATCH 08/11] CB-4039 okta logout redirect moved to AuthenticationService --- .../src/UserInfoResource.ts | 23 ++++---------- .../core-utils/src/getUniqueId.test.ts | 30 ------------------- webapp/packages/core-utils/src/getUniqueId.ts | 10 ------- webapp/packages/core-utils/src/index.ts | 1 - .../src/AuthenticationService.ts | 17 ++++++++++- 5 files changed, 21 insertions(+), 60 deletions(-) delete mode 100644 webapp/packages/core-utils/src/getUniqueId.test.ts delete mode 100644 webapp/packages/core-utils/src/getUniqueId.ts diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index 74df6a7b46..31624b32a6 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -12,8 +12,7 @@ import { AutoRunningTask, ISyncExecutor, ITask, SyncExecutor, whileTask } from ' import { CachedDataResource, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource'; import { SessionDataResource, SessionResource } from '@cloudbeaver/core-root'; import { WindowsService } from '@cloudbeaver/core-routing'; -import { AuthInfo, AuthStatus, GetActiveUserQueryVariables, GraphQLService, UserInfo } from '@cloudbeaver/core-sdk'; -import { getUniqueId } from '@cloudbeaver/core-utils'; +import { AuthInfo, AuthLogoutQuery, AuthStatus, GetActiveUserQueryVariables, GraphQLService, UserInfo } from '@cloudbeaver/core-sdk'; import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID'; import { AuthProviderService } from './AuthProviderService'; @@ -154,29 +153,17 @@ export class UserInfoResource extends CachedDataResource { - const { authLogoutExtended } = await this.graphQLService.sdk.authLogout({ + async logout(provider?: string, configuration?: string): Promise { + const result = await this.graphQLService.sdk.authLogout({ provider, configuration, }); - // TODO handle all redirect links once we know what to do with multiple popups issue - const redirectLinks = authLogoutExtended?.redirectLinks || []; - - if (redirectLinks.length) { - const oktaLink = authLogoutExtended?.redirectLinks[0]; - const id = `okta-logout-id-${getUniqueId()}`; - - this.windowsService.open(id, { - url: oktaLink, - width: 400, - height: 400, - }); - } - this.resetIncludes(); this.setData(await this.loader()); this.sessionDataResource.markOutdated(); + + return result; } async setConfigurationParameter(key: string, value: any): Promise { diff --git a/webapp/packages/core-utils/src/getUniqueId.test.ts b/webapp/packages/core-utils/src/getUniqueId.test.ts deleted file mode 100644 index 6265873278..0000000000 --- a/webapp/packages/core-utils/src/getUniqueId.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getUniqueId } from './getUniqueId'; - -describe('getUniqueId', () => { - const uniqueIdsCount = 100; - - it('should return unique ids', () => { - const ids = new Set(); - for (let i = 0; i < uniqueIdsCount; i++) { - ids.add(getUniqueId()); - } - expect(ids.size).toBe(uniqueIdsCount); - }); - - it('should return unique ids in parallel', async () => { - const ids = new Set(); - const promises = []; - for (let i = 0; i < uniqueIdsCount; i++) { - promises.push( - new Promise(resolve => { - setTimeout(() => { - ids.add(getUniqueId()); - resolve(true); - }, Math.random() * 100); - }), - ); - } - await Promise.all(promises); - expect(ids.size).toBe(uniqueIdsCount); - }); -}); diff --git a/webapp/packages/core-utils/src/getUniqueId.ts b/webapp/packages/core-utils/src/getUniqueId.ts deleted file mode 100644 index f142d2297a..0000000000 --- a/webapp/packages/core-utils/src/getUniqueId.ts +++ /dev/null @@ -1,10 +0,0 @@ -function getNextIdGenerator() { - let id = 0; - return () => id++; -} - -const getNextId = getNextIdGenerator(); - -export function getUniqueId() { - return getNextId(); -} diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index d3b1c0b94b..b471a596a2 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -36,7 +36,6 @@ export * from './isPropertiesEqual'; export * from './isSafari'; export * from './isSameDay'; export * from './isValuesEqual'; -export * from './getUniqueId'; export * from './md5'; export * from './MetadataMap'; export * from './OrderedMap'; diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index c22e53f9b6..ceeb541891 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -27,6 +27,7 @@ import { CachedMapAllKey } from '@cloudbeaver/core-resource'; import { ISessionAction, ServerConfigResource, sessionActionContext, SessionActionService, SessionDataResource } from '@cloudbeaver/core-root'; import { ScreenService, WindowsService } from '@cloudbeaver/core-routing'; import { NavigationService } from '@cloudbeaver/core-ui'; +import { uuid } from '@cloudbeaver/core-utils'; import { AuthDialogService } from './Dialog/AuthDialogService'; import type { IAuthOptions } from './IAuthOptions'; @@ -106,7 +107,21 @@ export class AuthenticationService extends Bootstrap { } try { - await this.userInfoResource.logout(providerId, configurationId); + // TODO handle all redirect links once we know what to do with multiple popups issue + const { + authLogoutExtended: { redirectLinks }, + } = await this.userInfoResource.logout(providerId, configurationId); + + if (redirectLinks.length) { + const oktaLink = redirectLinks[0]; + const id = `okta-logout-id-${uuid()}`; + + this.windowsService.open(id, { + url: oktaLink, + width: 400, + height: 400, + }); + } if (!this.administrationScreenService.isConfigurationMode && !providerId) { this.screenService.navigateToRoot(); From b08cfa1eb4c81d79c7fdf84b96842a8f713f3455 Mon Sep 17 00:00:00 2001 From: "s.teleshev" Date: Wed, 17 Jan 2024 11:43:04 +0100 Subject: [PATCH 09/11] CB-4039 remove windowService dep --- webapp/packages/core-authentication/src/UserInfoResource.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index 31624b32a6..99c0b115a5 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -11,7 +11,6 @@ import { injectable } from '@cloudbeaver/core-di'; import { AutoRunningTask, ISyncExecutor, ITask, SyncExecutor, whileTask } from '@cloudbeaver/core-executor'; import { CachedDataResource, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource'; import { SessionDataResource, SessionResource } from '@cloudbeaver/core-root'; -import { WindowsService } from '@cloudbeaver/core-routing'; import { AuthInfo, AuthLogoutQuery, AuthStatus, GetActiveUserQueryVariables, GraphQLService, UserInfo } from '@cloudbeaver/core-sdk'; import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID'; @@ -47,7 +46,6 @@ export class UserInfoResource extends CachedDataResource null, undefined, ['customIncludeOriginDetails', 'includeConfigurationParameters']); From e20314eef19a72dd84ed23da0d75335b6ba22884 Mon Sep 17 00:00:00 2001 From: "s.teleshev" Date: Wed, 17 Jan 2024 14:43:01 +0100 Subject: [PATCH 10/11] CB-4039 removes old logic of identity provider logout links --- .../src/AuthInfoService.ts | 25 ------------ .../src/AuthenticationService.ts | 40 ++----------------- .../plugin-authentication/src/locales/en.ts | 1 + .../plugin-authentication/src/locales/it.ts | 1 + .../plugin-authentication/src/locales/ru.ts | 1 + .../plugin-authentication/src/locales/zh.ts | 1 + 6 files changed, 7 insertions(+), 62 deletions(-) diff --git a/webapp/packages/core-authentication/src/AuthInfoService.ts b/webapp/packages/core-authentication/src/AuthInfoService.ts index 23ba0d8d66..ca8b6c48c2 100644 --- a/webapp/packages/core-authentication/src/AuthInfoService.ts +++ b/webapp/packages/core-authentication/src/AuthInfoService.ts @@ -25,31 +25,6 @@ export class AuthInfoService { return this.userInfoResource.data; } - get userAuthConfigurations(): IUserAuthConfiguration[] { - const tokens = this.userInfo?.authTokens; - const result: IUserAuthConfiguration[] = []; - - if (!tokens) { - return result; - } - - for (const token of tokens) { - if (token.authConfiguration) { - const provider = this.authProvidersResource.values.find(provider => provider.id === token.authProvider); - - if (provider) { - const configuration = provider.configurations?.find(configuration => configuration.id === token.authConfiguration); - - if (configuration) { - result.push({ providerId: provider.id, configuration }); - } - } - } - } - - return result; - } - constructor( private readonly userInfoResource: UserInfoResource, private readonly authProvidersResource: AuthProvidersResource, diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index ceeb541891..5a596e6a05 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -11,11 +11,9 @@ import { AdministrationScreenService } from '@cloudbeaver/core-administration'; import { AppAuthService, AUTH_PROVIDER_LOCAL_ID, - AuthInfoService, AuthProviderContext, AuthProviderService, AuthProvidersResource, - IUserAuthConfiguration, RequestedProvider, UserInfoResource, } from '@cloudbeaver/core-authentication'; @@ -55,7 +53,6 @@ export class AuthenticationService extends Bootstrap { private readonly authProviderService: AuthProviderService, private readonly authProvidersResource: AuthProvidersResource, private readonly sessionDataResource: SessionDataResource, - private readonly authInfoService: AuthInfoService, private readonly serverConfigResource: ServerConfigResource, private readonly windowsService: WindowsService, private readonly sessionActionService: SessionActionService, @@ -92,20 +89,6 @@ export class AuthenticationService extends Bootstrap { return; } - let userAuthConfiguration: IUserAuthConfiguration | undefined = undefined; - - if (providerId) { - userAuthConfiguration = this.authInfoService.userAuthConfigurations.find( - c => c.providerId === providerId && c.configuration.id === configurationId, - ); - } else if (this.authInfoService.userAuthConfigurations.length > 0) { - userAuthConfiguration = this.authInfoService.userAuthConfigurations[0]; - } - - if (userAuthConfiguration?.configuration.signOutLink) { - this.logoutConfiguration(userAuthConfiguration); - } - try { // TODO handle all redirect links once we know what to do with multiple popups issue const { @@ -113,11 +96,11 @@ export class AuthenticationService extends Bootstrap { } = await this.userInfoResource.logout(providerId, configurationId); if (redirectLinks.length) { - const oktaLink = redirectLinks[0]; + const url = redirectLinks[0]; const id = `okta-logout-id-${uuid()}`; this.windowsService.open(id, { - url: oktaLink, + url, width: 400, height: 400, }); @@ -129,24 +112,7 @@ export class AuthenticationService extends Bootstrap { await this.onLogout.execute('after'); } catch (exception: any) { - this.notificationService.logException(exception, "Can't logout"); - } - } - - private async logoutConfiguration(configuration: IUserAuthConfiguration): Promise { - if (configuration.configuration.signOutLink) { - const id = `${configuration.configuration.id}-sign-out`; - const popup = this.windowsService.open(id, { - url: configuration.configuration.signOutLink, - target: id, - width: 600, - height: 700, - }); - - if (popup) { - popup.blur(); - window.focus(); - } + this.notificationService.logException(exception, 'authentication_logout_error'); } } diff --git a/webapp/packages/plugin-authentication/src/locales/en.ts b/webapp/packages/plugin-authentication/src/locales/en.ts index 935fdc3318..a1c94e382f 100644 --- a/webapp/packages/plugin-authentication/src/locales/en.ts +++ b/webapp/packages/plugin-authentication/src/locales/en.ts @@ -2,6 +2,7 @@ export default [ ['authentication_login_dialog_title', 'Authentication'], ['authentication_login', 'Login'], ['authentication_logout', 'Logout'], + ['authentication_logout_error', "Can't logout"], ['authentication_authenticate', 'Authenticate'], ['authentication_authorizing', 'Authorizing...'], ['authentication_auth_federated', 'Federated'], diff --git a/webapp/packages/plugin-authentication/src/locales/it.ts b/webapp/packages/plugin-authentication/src/locales/it.ts index 00115b741c..d3be9e9de5 100644 --- a/webapp/packages/plugin-authentication/src/locales/it.ts +++ b/webapp/packages/plugin-authentication/src/locales/it.ts @@ -2,6 +2,7 @@ export default [ ['authentication_login_dialog_title', 'Autenticazione'], ['authentication_login', 'Login'], ['authentication_logout', 'Logout'], + ['authentication_logout_error', "Can't logout"], ['authentication_authenticate', 'Autentica'], ['authentication_authorizing', 'Authorizing...'], ['authentication_auth_federated', 'Federated'], diff --git a/webapp/packages/plugin-authentication/src/locales/ru.ts b/webapp/packages/plugin-authentication/src/locales/ru.ts index 228e105af9..576bb05cd1 100644 --- a/webapp/packages/plugin-authentication/src/locales/ru.ts +++ b/webapp/packages/plugin-authentication/src/locales/ru.ts @@ -2,6 +2,7 @@ export default [ ['authentication_login_dialog_title', 'Аутентификация'], ['authentication_login', 'Войти'], ['authentication_logout', 'Выйти'], + ['authentication_logout_error', 'Не удалось выйти'], ['authentication_authenticate', 'Аутентифицироваться'], ['authentication_authorizing', 'Авторизация...'], ['authentication_auth_federated', 'Федеративная'], diff --git a/webapp/packages/plugin-authentication/src/locales/zh.ts b/webapp/packages/plugin-authentication/src/locales/zh.ts index 97476a707d..4af3f890b7 100644 --- a/webapp/packages/plugin-authentication/src/locales/zh.ts +++ b/webapp/packages/plugin-authentication/src/locales/zh.ts @@ -2,6 +2,7 @@ export default [ ['authentication_login_dialog_title', '认证'], ['authentication_login', '登录'], ['authentication_logout', '登出'], + ['authentication_logout_error', "Can't logout"], ['authentication_authenticate', '认证'], ['authentication_authorizing', 'Authorizing...'], ['authentication_auth_federated', '联合认证'], From 3e4587c3f3494b03a901078fae0a225a0df3e68d Mon Sep 17 00:00:00 2001 From: "s.teleshev" Date: Wed, 17 Jan 2024 16:38:56 +0100 Subject: [PATCH 11/11] CB-4039 okta review changes --- .../src/UserInfoResource.ts | 2 + .../src/queries/authentication/authLogout.gql | 2 +- .../src/AuthenticationService.ts | 41 ++++++++++++------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index 99c0b115a5..e11390d484 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -20,6 +20,8 @@ import type { IAuthCredentials } from './IAuthCredentials'; export type UserInfoIncludes = GetActiveUserQueryVariables; +export type UserLogoutInfo = AuthLogoutQuery['result']; + export interface ILoginOptions { credentials?: IAuthCredentials; configurationId?: string; diff --git a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql index 9420c4e5bf..4997dbbd56 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql @@ -1,5 +1,5 @@ query authLogout($provider: ID, $configuration: ID) { - authLogoutExtended(provider: $provider, configuration: $configuration) { + result: authLogoutExtended(provider: $provider, configuration: $configuration) { redirectLinks } } diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index 5a596e6a05..91ec32a7f3 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -16,6 +16,7 @@ import { AuthProvidersResource, RequestedProvider, UserInfoResource, + UserLogoutInfo, } from '@cloudbeaver/core-authentication'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import type { DialogueStateResult } from '@cloudbeaver/core-dialogs'; @@ -90,21 +91,9 @@ export class AuthenticationService extends Bootstrap { } try { - // TODO handle all redirect links once we know what to do with multiple popups issue - const { - authLogoutExtended: { redirectLinks }, - } = await this.userInfoResource.logout(providerId, configurationId); - - if (redirectLinks.length) { - const url = redirectLinks[0]; - const id = `okta-logout-id-${uuid()}`; - - this.windowsService.open(id, { - url, - width: 400, - height: 400, - }); - } + const logoutResult = await this.userInfoResource.logout(providerId, configurationId); + + this.handleRedirectLinks(logoutResult.result); if (!this.administrationScreenService.isConfigurationMode && !providerId) { this.screenService.navigateToRoot(); @@ -116,6 +105,28 @@ export class AuthenticationService extends Bootstrap { } } + // TODO handle all redirect links once we know what to do with multiple popups issue + private handleRedirectLinks(userLogoutInfo: UserLogoutInfo) { + const redirectLinks = userLogoutInfo.redirectLinks; + + if (redirectLinks.length) { + const url = redirectLinks[0]; + const id = `okta-logout-id-${uuid()}`; + + const popup = this.windowsService.open(id, { + url, + target: id, + width: 600, + height: 700, + }); + + if (popup) { + popup.blur(); + window.focus(); + } + } + } + private async auth(persistent: boolean, options: IAuthOptions) { if (this.authPromise) { await this.waitAuth();