From 35668e69b476ee4714966ec2463c57b23645dd90 Mon Sep 17 00:00:00 2001 From: Alexander Skoblikov Date: Wed, 16 Aug 2023 23:41:02 +0400 Subject: [PATCH] CB-3452 NTLM (#1919) * CB-3837 separate flag for federated providers * CB-3452 feat: support configurable auth providers * CB-3452 fix: auth tab switching * CB-3452 fix: auth dialog credentials reset * CB-3837 separate flag for federated providers * CB-3452 feat: support configurable auth providers * CB-3452 fix: auth tab switching * CB-3452 fix: auth dialog credentials reset * CB-3836 ntlm auth provider * CB-3452 fix: provider configuration tab id * CB-3836 fix ntlm flow * CB-3452 chore: add Integer type mapping --------- Co-authored-by: Aleksei Potsetsuev Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> --- .../auth/SMAuthProviderAssigner.java | 3 +- .../model/user/WebAuthProviderInfo.java | 5 ++ .../schema/service.auth.graphqls | 2 + .../provider/local/LocalAuthProvider.java | 2 +- .../auth/provider/rp/RPAuthProvider.java | 2 +- .../CBEmbeddedSecurityController.java | 10 +-- .../queries/fragments/AuthProviderInfo.gql | 1 + .../src/Dialog/AuthDialog.tsx | 67 +++++++++----- .../AuthProviderForm/AuthProviderForm.tsx | 3 +- .../src/Dialog/useAuthDialogState.ts | 90 ++++++++++++------- .../src/ObjectPropertiesPage/helpers.ts | 1 + 11 files changed, 126 insertions(+), 60 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderAssigner.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderAssigner.java index f8a95768b8..9c3f432b39 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderAssigner.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/SMAuthProviderAssigner.java @@ -19,6 +19,7 @@ package io.cloudbeaver.auth; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; import java.util.Map; @@ -31,7 +32,7 @@ public interface SMAuthProviderAssigner { @NotNull SMAutoAssign detectAutoAssignments( @NotNull DBRProgressMonitor monitor, - @NotNull Map providerConfig, + @NotNull SMAuthProviderCustomConfiguration providerConfig, @NotNull Map authParameters ) throws DBException; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebAuthProviderInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebAuthProviderInfo.java index ec5a4591df..d5f2dee003 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebAuthProviderInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/user/WebAuthProviderInfo.java @@ -17,6 +17,7 @@ package io.cloudbeaver.model.user; import io.cloudbeaver.WebServiceUtils; +import io.cloudbeaver.auth.SMAuthProviderFederated; import io.cloudbeaver.auth.provisioning.SMProvisioner; import io.cloudbeaver.registry.WebAuthProviderConfiguration; import io.cloudbeaver.registry.WebAuthProviderDescriptor; @@ -70,6 +71,10 @@ public class WebAuthProviderInfo { return descriptor.isConfigurable(); } + public boolean isFederated() { + return descriptor.getInstance() instanceof SMAuthProviderFederated; + } + public boolean isTrusted() { return descriptor.isTrusted(); } 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 42eb05d2e8..dd4f55c03e 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -62,6 +62,8 @@ type AuthProviderInfo { # Configurable providers must be configured first. See configurations field. configurable: Boolean! + # Federated providers means authorization must occur asynchronously through redirects. + federated: Boolean! # Provider configurations (applicable only if configurable=true) configurations: [AuthProviderConfiguration!] diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java index 6555680c49..9559f513b4 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/local/LocalAuthProvider.java @@ -45,7 +45,7 @@ public class LocalAuthProvider implements SMAuthProvider { @Override public String validateLocalAuth(@NotNull DBRProgressMonitor monitor, @NotNull SMController securityController, - @NotNull Map providerConfig, + @NotNull SMAuthProviderCustomConfiguration providerConfig, @NotNull Map userCredentials, @Nullable String activeUserId) throws DBException { diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java index 38d4c99b24..7f799aca2d 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/auth/provider/rp/RPAuthProvider.java @@ -52,7 +52,7 @@ public class RPAuthProvider implements SMAuthProviderExternal { public String validateLocalAuth( @NotNull DBRProgressMonitor monitor, @NotNull SMController securityController, - @NotNull Map providerConfig, + @NotNull SMAuthProviderCustomConfiguration providerConfig, @NotNull Map userCredentials, @Nullable String activeUserId ) throws DBException { 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 771fca6c2d..1e8f2d1d52 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 @@ -1285,7 +1285,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen SMAuthInfo.inProgress( authAttemptId, null, - Map.of(new SMAuthConfigurationReference(authProviderId, null), securedUserIdentifyingCredentials) + Map.of(new SMAuthConfigurationReference(authProviderId, authProviderConfigurationId), securedUserIdentifyingCredentials) ), true, false @@ -1745,8 +1745,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen Map storedUserData = new LinkedHashMap<>(); SMTeam[] allTeams = null; + SMAuthProviderCustomConfiguration providerConfig = null; String detectedAuthRole = null; - Map providerConfig = new LinkedHashMap<>(); Map userAuthData = new LinkedHashMap<>(); for (SMAuthConfigurationReference authConfiguration : authProviderIds) { String authProviderId = authConfiguration.getAuthProviderId(); @@ -1761,7 +1761,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen var providerCustomConfig = application.getAuthConfiguration().getAuthProviderConfiguration(providerConfigId); if (providerCustomConfig != null) { - providerConfig.putAll(providerCustomConfig.getParameters()); + providerConfig = providerCustomConfig; } } } @@ -1893,7 +1893,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen @Nullable private SMAutoAssign getAutoAssignUserData( WebAuthProviderDescriptor authProvider, - Map providerConfig, + SMAuthProviderCustomConfiguration providerConfig, Map userData, DBRProgressMonitor monitor ) throws DBException { @@ -2005,7 +2005,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen @Nullable String activeUserId, boolean createNewUserIfNotExist, String authRole, - Map providerConfig + SMAuthProviderCustomConfiguration providerConfig ) throws DBException { SMAuthProvider smAuthProviderInstance = authProvider.getInstance(); diff --git a/webapp/packages/core-sdk/src/queries/fragments/AuthProviderInfo.gql b/webapp/packages/core-sdk/src/queries/fragments/AuthProviderInfo.gql index 5af68d5406..71f6637859 100644 --- a/webapp/packages/core-sdk/src/queries/fragments/AuthProviderInfo.gql +++ b/webapp/packages/core-sdk/src/queries/fragments/AuthProviderInfo.gql @@ -20,6 +20,7 @@ fragment AuthProviderInfo on AuthProviderInfo { # features #} + federated configurable configurations { ...AuthProviderConfigurationInfo diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx index 4769f335d9..e2405a542c 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx @@ -27,7 +27,7 @@ import { AuthDialogFooter } from './AuthDialogFooter'; import { AuthProviderForm } from './AuthProviderForm/AuthProviderForm'; import { ConfigurationsList } from './AuthProviderForm/ConfigurationsList'; import { FEDERATED_AUTH } from './FEDERATED_AUTH'; -import { useAuthDialogState } from './useAuthDialogState'; +import { getAuthProviderTabId, useAuthDialogState } from './useAuthDialogState'; const styles = css` CommonDialogWrapper { @@ -87,7 +87,7 @@ export const AuthDialog: DialogComponent = observer(function const additional = userInfo.data !== null && state.activeProvider?.id !== undefined && !userInfo.hasToken(state.activeProvider.id); - const showTabs = dialogData.providers.length + dialogData.configurations.length > 1; + const showTabs = dialogData.providers.length + dialogData.federatedProviders.length > 1; const federate = state.tabId === FEDERATED_AUTH; let dialogTitle = translate('authentication_login_dialog_title'); @@ -124,7 +124,7 @@ export const AuthDialog: DialogComponent = observer(function authenticationService.configureAuthProvider?.(); } - function renderForm(provider: AuthProvider | null) { + function renderForm(provider: AuthProvider | null, configuration: AuthProviderConfiguration | null) { if (!provider) { return {translate('authentication_select_provider')}; } @@ -146,7 +146,9 @@ export const AuthDialog: DialogComponent = observer(function ); } - return ; + return ( + + ); } return styled(useStyles(BASE_TAB_STYLES, styles, UNDERLINE_TAB_STYLES, UNDERLINE_TAB_BIG_STYLES))( @@ -161,27 +163,50 @@ export const AuthDialog: DialogComponent = observer(function {showTabs && ( - {dialogData.providers.map(provider => ( - { - state.setActiveProvider(provider); - }} - > - {provider.label} - - ))} - {dialogData.configurations.length > 0 && ( + {dialogData.providers + .map(provider => { + if (provider.configurable) { + return provider.configurations?.map(configuration => { + const tabId = getAuthProviderTabId(provider, configuration); + return ( + { + state.setActiveProvider(provider, configuration); + }} + > + {configuration.displayName} + + ); + }); + } + return ( + { + state.setActiveProvider(provider, null); + }} + > + {provider.label} + + ); + }) + .flat()} + {dialogData.federatedProviders.length > 0 && ( { - state.setActiveProvider(null); + state.setActiveProvider(null, null); + state.setTabId(FEDERATED_AUTH); }} > {translate('authentication_auth_federated')} @@ -193,13 +218,13 @@ export const AuthDialog: DialogComponent = observer(function ) : ( - login(linkUser)}>{renderForm(state.activeProvider)} + login(linkUser)}>{renderForm(state.activeProvider, state.activeConfiguration)} )} {!federate && ( diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx index 0738033147..2d032aac33 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx @@ -7,11 +7,12 @@ */ import { observer } from 'mobx-react-lite'; -import type { AuthProvider, IAuthCredentials } from '@cloudbeaver/core-authentication'; +import type { AuthProvider, AuthProviderConfiguration, IAuthCredentials } from '@cloudbeaver/core-authentication'; import { Combobox, Group, InputField, useFocus } from '@cloudbeaver/core-blocks'; interface Props { provider: AuthProvider; + configuration: AuthProviderConfiguration | null; credentials: IAuthCredentials; authenticate: boolean; } diff --git a/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts b/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts index f5fb85fbf3..a50409deea 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts @@ -27,7 +27,7 @@ interface IData { configure: boolean; adminPageActive: boolean; providers: AuthProvider[]; - configurations: AuthProvider[]; + federatedProviders: AuthProvider[]; login: (linkUser: boolean, provider?: AuthProvider, configuration?: AuthProviderConfiguration) => Promise; loginFederated: (provider: AuthProvider, configuration: AuthProviderConfiguration, onClose?: () => void) => Promise; @@ -39,9 +39,8 @@ interface IState { activeConfiguration: AuthProviderConfiguration | null; credentials: IAuthCredentials; - setTabId: (tabId: string) => void; - setActiveProvider: (provider: AuthProvider | null) => void; - setActiveConfiguration: (provider: AuthProvider | null, configuration: AuthProviderConfiguration | null) => void; + setTabId: (tabId: string | null) => void; + setActiveProvider: (provider: AuthProvider | null, configuration: AuthProviderConfiguration | null) => void; } export function useAuthDialogState(accessRequest: boolean, providerId: string | null, configurationId?: string): IData { @@ -64,18 +63,30 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | credentials: {}, }, - setTabId(tabId: string): void { + setTabId(tabId: string | null): void { this.tabId = tabId; }, - setActiveProvider(provider: AuthProvider | null): void { + setActiveProvider(provider: AuthProvider | null, configuration: AuthProviderConfiguration | null): void { + const providerChanged = this.activeProvider?.id !== provider?.id; + const configurationChanged = this.activeConfiguration?.id !== configuration?.id; + this.activeProvider = provider; - this.credentials.profile = '0'; - this.credentials.credentials = {}; - this.activeConfiguration = null; - }, - setActiveConfiguration(provider: AuthProvider | null, configuration: AuthProviderConfiguration | null): void { - this.setActiveProvider(provider); this.activeConfiguration = configuration; + + if (providerChanged || configurationChanged) { + this.credentials.profile = '0'; + this.credentials.credentials = {}; + } + + if (provider) { + if (provider.federated) { + this.setTabId(FEDERATED_AUTH); + } else { + this.setTabId(getAuthProviderTabId(provider, configuration)); + } + } else { + this.setTabId(null); + } }, }), { @@ -84,7 +95,6 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | activeConfiguration: observable.ref, credentials: observable, setActiveProvider: action, - setActiveConfiguration: action, }, false, ); @@ -94,7 +104,11 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | return true; } - if (provider.configurable || provider.trusted || provider.private) { + if (provider.federated || provider.trusted || provider.private) { + return false; + } + + if (provider.configurable && (provider.configurations?.length ?? 0) === 0) { return false; } @@ -111,13 +125,25 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | return false; }); - const configurations = providers.filter( - provider => provider.configurable && (provider.configurations?.length || 0) > 0 && authProvidersResource.resource.isAuthEnabled(provider.id), + const federatedProviders = providers.filter( + provider => + provider.federated && + provider.configurable && + (provider.configurations?.length || 0) > 0 && + authProvidersResource.resource.isAuthEnabled(provider.id), ); - const tabIds = activeProviders.map(provider => provider.id); + const tabIds = activeProviders + .map(provider => { + if (provider.configurable) { + return provider.configurations?.map(configuration => getAuthProviderTabId(provider, configuration)) ?? []; + } - if (configurations.length > 0) { + return provider.id; + }) + .flat(); + + if (federatedProviders.length > 0) { tabIds.push(FEDERATED_AUTH); } @@ -147,9 +173,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | this.authenticating = true; try { - if (configuration) { - this.state.setActiveConfiguration(provider, configuration ?? null); - } + this.state.setActiveProvider(provider, configuration ?? null); const loginTask = authInfoService.login(provider.id, { configurationId: configuration?.id, @@ -170,8 +194,9 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | this.authTask = null; this.authenticating = false; - if (configuration) { - this.state.setActiveConfiguration(null, null); + if (provider.federated) { + this.state.setActiveProvider(null, null); + this.state.setTabId(FEDERATED_AUTH); } } }, @@ -188,7 +213,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | state, adminPageActive, providers: activeProviders, - configurations, + federatedProviders, }, ); @@ -203,13 +228,11 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string | ); if (tabIds.length > 0 && (state.tabId === null || !tabIds.includes(state.tabId))) { - const tabId = tabIds[0]; - state.setTabId(tabId); + const provider = providers.find(provider => provider.id === providerId) || activeProviders[0] || null; + const configuration = + provider?.configurations?.find(configuration => configuration.id === configurationId) || provider?.configurations?.[0] || null; - const provider = activeProviders.find(provider => provider.id === tabId) || providers.find(provider => provider.id === providerId) || null; - const configuration = provider?.configurations?.find(configuration => configuration.id === configurationId) ?? null; - - state.setActiveConfiguration(provider, configuration); + state.setActiveProvider(provider, configuration); } return data; @@ -229,3 +252,10 @@ function compareProviders(providerA: AuthProvider, providerB: AuthProvider): num } return 1; } + +export function getAuthProviderTabId(provider: AuthProvider, configuration?: AuthProviderConfiguration | null): string { + if (!configuration) { + return provider.id; + } + return provider.id + '_' + configuration.id; +} diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts index cb24e6199c..be38c8f2e4 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/helpers.ts @@ -31,6 +31,7 @@ export function matchType(type?: string) { case 'int': case 'double': case 'long': + case 'Integer': return 'number'; default: return 'text';