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 <wrouds@gmail.com>
Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
Alexander Skoblikov
2023-08-16 22:41:02 +03:00
committed by GitHub
co-authored by Aleksei Potsetsuev EvgeniaBzzz Daria Marutkina
parent aa1edf0e29
commit 35668e69b4
11 changed files with 126 additions and 60 deletions
@@ -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<String, Object> providerConfig,
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> authParameters
) throws DBException;
@@ -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();
}
@@ -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!]
@@ -45,7 +45,7 @@ public class LocalAuthProvider implements SMAuthProvider<LocalAuthSession> {
@Override
public String validateLocalAuth(@NotNull DBRProgressMonitor monitor,
@NotNull SMController securityController,
@NotNull Map<String, Object> providerConfig,
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials,
@Nullable String activeUserId) throws DBException {
@@ -52,7 +52,7 @@ public class RPAuthProvider implements SMAuthProviderExternal<SMSession> {
public String validateLocalAuth(
@NotNull DBRProgressMonitor monitor,
@NotNull SMController securityController,
@NotNull Map<String, Object> providerConfig,
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> userCredentials,
@Nullable String activeUserId
) throws DBException {
@@ -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<SMAuthConfigurationReference, Object> storedUserData = new LinkedHashMap<>();
SMTeam[] allTeams = null;
SMAuthProviderCustomConfiguration providerConfig = null;
String detectedAuthRole = null;
Map<String, Object> providerConfig = new LinkedHashMap<>();
Map<String, Object> 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<String, Object> providerConfig,
SMAuthProviderCustomConfiguration providerConfig,
Map<String, Object> userData,
DBRProgressMonitor monitor
) throws DBException {
@@ -2005,7 +2005,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
@Nullable String activeUserId,
boolean createNewUserIfNotExist,
String authRole,
Map<String, Object> providerConfig
SMAuthProviderCustomConfiguration providerConfig
) throws DBException {
SMAuthProvider<?> smAuthProviderInstance = authProvider.getInstance();
@@ -20,6 +20,7 @@ fragment AuthProviderInfo on AuthProviderInfo {
# features
#}
federated
configurable
configurations {
...AuthProviderConfigurationInfo
@@ -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<IAuthOptions, null> = 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<IAuthOptions, null> = observer(function
authenticationService.configureAuthProvider?.();
}
function renderForm(provider: AuthProvider | null) {
function renderForm(provider: AuthProvider | null, configuration: AuthProviderConfiguration | null) {
if (!provider) {
return <TextPlaceholder>{translate('authentication_select_provider')}</TextPlaceholder>;
}
@@ -146,7 +146,9 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
);
}
return <AuthProviderForm provider={provider} credentials={state.credentials} authenticate={dialogData.authenticating} />;
return (
<AuthProviderForm provider={provider} configuration={configuration} credentials={state.credentials} authenticate={dialogData.authenticating} />
);
}
return styled(useStyles(BASE_TAB_STYLES, styles, UNDERLINE_TAB_STYLES, UNDERLINE_TAB_BIG_STYLES))(
@@ -161,27 +163,50 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
<CommonDialogBody noBodyPadding>
{showTabs && (
<TabList aria-label="Auth providers">
{dialogData.providers.map(provider => (
<Tab
key={provider.id}
tabId={provider.id}
title={provider.description || provider.label}
disabled={dialogData.authenticating}
onClick={() => {
state.setActiveProvider(provider);
}}
>
<TabTitle>{provider.label}</TabTitle>
</Tab>
))}
{dialogData.configurations.length > 0 && (
{dialogData.providers
.map(provider => {
if (provider.configurable) {
return provider.configurations?.map(configuration => {
const tabId = getAuthProviderTabId(provider, configuration);
return (
<Tab
key={tabId}
tabId={tabId}
title={configuration.displayName}
disabled={dialogData.authenticating}
onClick={() => {
state.setActiveProvider(provider, configuration);
}}
>
<TabTitle>{configuration.displayName}</TabTitle>
</Tab>
);
});
}
return (
<Tab
key={provider.id}
tabId={provider.id}
title={provider.description || provider.label}
disabled={dialogData.authenticating}
onClick={() => {
state.setActiveProvider(provider, null);
}}
>
<TabTitle>{provider.label}</TabTitle>
</Tab>
);
})
.flat()}
{dialogData.federatedProviders.length > 0 && (
<Tab
key={FEDERATED_AUTH}
tabId={FEDERATED_AUTH}
title={translate('authentication_auth_federated')}
disabled={dialogData.authenticating}
onClick={() => {
state.setActiveProvider(null);
state.setActiveProvider(null, null);
state.setTabId(FEDERATED_AUTH);
}}
>
<TabTitle>{translate('authentication_auth_federated')}</TabTitle>
@@ -193,13 +218,13 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
<ConfigurationsList
activeProvider={state.activeProvider}
activeConfiguration={state.activeConfiguration}
providers={dialogData.configurations}
providers={dialogData.federatedProviders}
authTask={dialogData.authTask}
login={login}
onClose={rejectDialog}
/>
) : (
<SubmittingForm onSubmit={() => login(linkUser)}>{renderForm(state.activeProvider)}</SubmittingForm>
<SubmittingForm onSubmit={() => login(linkUser)}>{renderForm(state.activeProvider, state.activeConfiguration)}</SubmittingForm>
)}
</CommonDialogBody>
{!federate && (
@@ -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;
}
@@ -27,7 +27,7 @@ interface IData {
configure: boolean;
adminPageActive: boolean;
providers: AuthProvider[];
configurations: AuthProvider[];
federatedProviders: AuthProvider[];
login: (linkUser: boolean, provider?: AuthProvider, configuration?: AuthProviderConfiguration) => Promise<void>;
loginFederated: (provider: AuthProvider, configuration: AuthProviderConfiguration, onClose?: () => void) => Promise<void>;
@@ -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;
}
@@ -31,6 +31,7 @@ export function matchType(type?: string) {
case 'int':
case 'double':
case 'long':
case 'Integer':
return 'number';
default:
return 'text';