Compare commits

...
17 changed files with 161 additions and 182 deletions
@@ -276,13 +276,21 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
}
@Override
@Deprecated
public String[] getEnabledAuthProviders() {
if (enabledAuthProviders == null) {
// No config - enable all providers (+backward compatibility)
return WebAuthProviderRegistry.getInstance().getAuthProviders()
.stream().map(WebAuthProviderDescriptor::getId).toArray(String[]::new);
}
return enabledAuthProviders;
//all enabled
return WebAuthProviderRegistry.getInstance().getAuthProviders()
.stream()
.map(WebAuthProviderDescriptor::getId)
.toArray(String[]::new);
// if (enabledAuthProviders == null) {
// // No config - enable all providers (+backward compatibility)
// return WebAuthProviderRegistry.getInstance().getAuthProviders()
// .stream()
// .map(WebAuthProviderDescriptor::getId)
// .toArray(String[]::new);
// }
// return enabledAuthProviders;
}
public void setEnabledAuthProviders(String[] enabledAuthProviders) {
@@ -19,7 +19,6 @@ package io.cloudbeaver.registry;
import io.cloudbeaver.auth.CBAuthConstants;
import io.cloudbeaver.auth.SMAuthProviderFederated;
import io.cloudbeaver.auth.SMSignOutLinkProvider;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
@@ -69,6 +68,10 @@ public class WebAuthProviderConfiguration {
return config.isDisabled();
}
public boolean isDefault() {
return config.isDefault();
}
public String getIconURL() {
return config.getIconURL();
}
@@ -29,6 +29,7 @@ import org.jkiss.dbeaver.model.impl.AbstractDescriptor;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.security.SMAuthCredentialsProfile;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.model.security.SMAuthProviderDescriptor;
import org.jkiss.dbeaver.model.security.SMSubjectType;
import org.jkiss.utils.ArrayUtils;
@@ -61,6 +62,8 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
private final String[] requiredFeatures;
private final boolean isRequired;
private String[] types;
@Nullable
private final SMAuthProviderCustomConfiguration defaultConfiguration;
public WebAuthProviderDescriptor(IConfigurationElement cfg) {
super(cfg);
@@ -98,6 +101,26 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
String typesAttr = cfg.getAttribute(WebRegistryConstant.ATTR_CATEGORIES);
this.types = CommonUtils.isEmpty(typesAttr) ? new String[0] : typesAttr.split(",");
IConfigurationElement[] defaultConfigurations = cfg.getChildren(WebRegistryConstant.TAG_DEFAULT_CONFIGURATION);
if (defaultConfigurations != null && defaultConfigurations.length > 0) {
if (defaultConfigurations.length > 1) {
throw new IllegalStateException("Multiple default configuration elements defined for auth provider " + getId());
}
IConfigurationElement defaultConfiguration = defaultConfigurations[0];
this.defaultConfiguration = SMAuthProviderCustomConfiguration.builder()
.id(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_ID))
.provider(getId())
.displayName(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_LABEL))
.disabled(CommonUtils.toBoolean(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_DISABLED)))
.iconURL(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_ICON))
.description(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_DESCRIPTION))
.parameters(new HashMap<>())
.isDefault(CommonUtils.toBoolean(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_IS_DEFAULT)))
.build();
} else {
this.defaultConfiguration = null;
}
}
@NotNull
@@ -239,4 +262,9 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
return false;
}
}
@Nullable
public SMAuthProviderCustomConfiguration getDefaultConfiguration() {
return defaultConfiguration;
}
}
@@ -21,11 +21,13 @@ import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
import org.jkiss.dbeaver.registry.RegistryConstants;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.util.*;
import java.util.stream.Collectors;
public class WebAuthProviderRegistry {
@@ -120,4 +122,11 @@ public class WebAuthProviderRegistry {
return authProviders.get(id);
}
public Set<SMAuthProviderCustomConfiguration> getMandatoryConfigurations() {
return authProviders.values()
.stream()
.map(WebAuthProviderDescriptor::getDefaultConfiguration)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
}
@@ -32,8 +32,11 @@ public class WebRegistryConstant {
public static final String ATTR_REQUIRED_FEATURES = "requiredFeatures";
public static final String ATTR_CATEGORIES = "categories";
public static final String ATTR_SERVICE_PROVIDER = "serviceProvider";
public static final String ATTR_DISABLED = "disabled";
public static final String ATTR_IS_DEFAULT = "isDefault";
public static final String TAG_CONFIGURATION = "configuration";
public static final String TAG_CREDENTIALS = "credentials";
public static final String TAG_META_PARAMETERS = "metaParameters";
public static final String TAG_DEFAULT_CONFIGURATION = "defaultConfiguration";
}
@@ -24,6 +24,7 @@ import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.config.PasswordPolicyConfiguration;
import io.cloudbeaver.model.config.SMControllerConfiguration;
import io.cloudbeaver.registry.WebAuthProviderRegistry;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
@@ -223,6 +224,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
appConfiguration.getAuthCustomConfigurations().stream()
)
.collect(Collectors.toCollection(LinkedHashSet::new));
Set<SMAuthProviderCustomConfiguration> mandatoryConfigurations = WebAuthProviderRegistry.getInstance().getMandatoryConfigurations();
mergedAuthProviders.addAll(mandatoryConfigurations);
appConfiguration.setAuthProvidersConfigurations(mergedAuthProviders);
}
@@ -134,6 +134,7 @@ public class CBStaticServlet extends DefaultServlet {
}
CBAppConfig appConfig = application.getAppConfiguration();
String[] authProviders = appConfig.getEnabledAuthProviders();
//fixme there is no single auth provider
if (authProviders.length == 1) {
String authProviderId = authProviders[0];
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(authProviderId);
@@ -83,6 +83,7 @@ type AdminAuthProviderConfiguration {
id: ID!
displayName: String!
disabled: Boolean!
isDefault: Boolean!
iconURL: String
description: String
@@ -31,6 +31,7 @@ type AuthProviderConfiguration {
displayName: String!
disabled: Boolean!
authRoleProvided: Boolean
isDefault: Boolean
iconURL: String
description: String
@@ -14,6 +14,12 @@
<property id="password" label="User password" type="string" description="User password" encryption="hash" admin="true" user="true" required="true"/>
</propertyGroup>
</credentials>
<defaultConfiguration id="local"
label="local"
disabled="false"
iconURL=""
description="configuration of local provider"
isDefault="true"/>
</authProvider>
<authProvider id="reverseProxy" label="Reverse proxy"
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -18,7 +18,12 @@ import {
ResourceKeyUtils,
} from '@cloudbeaver/core-resource';
import { EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root';
import { type AdminAuthProviderConfiguration, type GetAuthProviderConfigurationsQueryVariables, GraphQLService } from '@cloudbeaver/core-sdk';
import {
type AdminAuthProviderConfiguration,
type GetAuthProviderConfigurationsQueryVariables,
GraphQLService,
type SaveAuthProviderConfigurationQueryVariables,
} from '@cloudbeaver/core-sdk';
import type { AuthProviderConfiguration } from './AuthProvidersResource.js';
@@ -39,7 +44,7 @@ export class AuthConfigurationsResource extends CachedMapResource<string, AuthCo
permissionsResource.require(this, EAdminPermission.admin).outdateResource(this);
}
async saveConfiguration(config: AuthConfiguration): Promise<AuthConfiguration> {
async saveConfiguration(config: SaveAuthProviderConfigurationQueryVariables): Promise<AuthConfiguration> {
await this.performUpdate(config.id, [], async () => {
const response = await this.graphQLService.sdk.saveAuthProviderConfiguration(config);
@@ -1,17 +1,5 @@
query getAuthProviderConfigurations($providerId: ID) {
configurations: listAuthProviderConfigurations(providerId: $providerId) {
providerId
id
displayName
disabled
iconURL
description
parameters
signInLink
signOutLink
redirectLink
metadataLink
acsLink
entityIdLink
...AdminAuthProviderConfigurationInfo
}
}
@@ -16,18 +16,6 @@ query saveAuthProviderConfiguration(
description: $description
parameters: $parameters
) {
providerId
id
displayName
disabled
iconURL
description
parameters
signInLink
signOutLink
redirectLink
metadataLink
acsLink
entityIdLink
...AdminAuthProviderConfigurationInfo
}
}
@@ -0,0 +1,16 @@
fragment AdminAuthProviderConfigurationInfo on AdminAuthProviderConfiguration {
providerId
id
displayName
disabled
isDefault
iconURL
description
parameters
signInLink
signOutLink
redirectLink
metadataLink
acsLink
entityIdLink
}
@@ -0,0 +1,63 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, AuthSettingsService } from '@cloudbeaver/core-authentication';
import { FormContext, type PlaceholderComponent, Switch, useExecutor, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration';
export const AnonymousAccess: PlaceholderComponent<IConfigurationPlaceholderProps> = observer(function AuthenticationProviders({
state: { serverConfig },
}) {
const providers = useResource(AuthenticationProviders, AuthProvidersResource, CachedMapAllKey);
const translate = useTranslate();
const formContext = useContext(FormContext);
const authSettingsService = useService(AuthSettingsService);
if (formContext === null) {
throw new Error('Form state should be provided');
}
const localProvider = providers.resource.get(AUTH_PROVIDER_LOCAL_ID);
const authenticationDisabled = serverConfig.enabledAuthProviders?.length === 0;
const isAnonymousAccessDisabled = authSettingsService.disableAnonymousAccess;
useExecutor({
executor: formContext.onChange,
handlers: [
function switchControls() {
if (serverConfig.enabledAuthProviders?.length === 0) {
if (localProvider && !isAnonymousAccessDisabled) {
serverConfig.anonymousAccessEnabled = true;
}
}
},
],
});
if (!localProvider || isAnonymousAccessDisabled) {
return null;
}
return (
<Switch
name="anonymousAccessEnabled"
state={serverConfig}
description={translate('administration_configuration_wizard_configuration_anonymous_access_description')}
mod={['primary']}
disabled={authenticationDisabled}
small
autoHide
>
{translate('administration_configuration_wizard_configuration_anonymous_access')}
</Switch>
);
});
@@ -1,142 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import React, { useContext } from 'react';
import {
AUTH_PROVIDER_LOCAL_ID,
AuthProviderService,
AuthProvidersResource,
AuthSettingsService,
sortProvider,
} from '@cloudbeaver/core-authentication';
import { FormContext, Group, GroupTitle, type PlaceholderComponent, Switch, useExecutor, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { isDefined } from '@dbeaver/js-helpers';
import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration';
import { ServerConfigurationAdminForm } from './ServerConfigurationAdminForm.js';
export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceholderProps> = observer(function AuthenticationProviders({
state: { serverConfig },
configurationWizard,
}) {
const authProviderService = useService(AuthProviderService);
const providers = useResource(AuthenticationProviders, AuthProvidersResource, CachedMapAllKey);
const translate = useTranslate();
const formContext = useContext(FormContext);
const authSettingsService = useService(AuthSettingsService);
if (formContext === null) {
throw new Error('Form state should be provided');
}
const localProvider = providers.resource.get(AUTH_PROVIDER_LOCAL_ID);
const providerList = providers.data
.filter(isDefined)
.filter(provider => {
if (provider.private) {
return false;
}
if (configurationWizard) {
const disabledByFeature = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
if (provider.configurable || disabledByFeature || provider.authHidden) {
return false;
}
}
return true;
})
.sort(sortProvider);
const externalAuthentication = providerList.length === 0;
const authenticationDisabled = serverConfig.enabledAuthProviders?.length === 0;
const isAnonymousAccessDisabled = authSettingsService.disableAnonymousAccess;
useExecutor({
executor: formContext.onChange,
handlers: [
function switchControls() {
if (serverConfig.enabledAuthProviders?.length === 0) {
if (localProvider && !isAnonymousAccessDisabled) {
serverConfig.anonymousAccessEnabled = true;
}
}
if (serverConfig.enabledAuthProviders?.length) {
serverConfig.enabledAuthProviders = serverConfig.enabledAuthProviders.filter(providerId => {
const provider = providers.resource.get(providerId)!;
return !provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
});
}
},
],
});
if (externalAuthentication) {
return null;
}
return (
<React.Fragment>
<Group key="authentication" form gap>
<GroupTitle>{translate('administration_configuration_wizard_configuration_authentication_group')}</GroupTitle>
{localProvider && !isAnonymousAccessDisabled ? (
<Switch
name="anonymousAccessEnabled"
state={serverConfig}
description={translate('administration_configuration_wizard_configuration_anonymous_access_description')}
mod={['primary']}
disabled={authenticationDisabled}
small
autoHide
>
{translate('administration_configuration_wizard_configuration_anonymous_access')}
</Switch>
) : null}
{providerList.map(provider => {
const links = authProviderService.getServiceDescriptionLinks(provider);
const disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
const tooltip = disabled ? `Following services need to be enabled: "${provider.requiredFeatures.join(', ')}"` : '';
return (
<Switch
key={provider.id}
id={`authProvider_${provider.id}`}
title={tooltip}
value={provider.id}
name="enabledAuthProviders"
state={serverConfig}
description={
<>
{provider.description}
{links.map(link => {
const Description = link.description();
return <Description key={link.id} configurationWizard={configurationWizard} />;
})}
</>
}
mod={['primary']}
disabled={disabled}
small
autoHide
>
{provider.label}
</Switch>
);
})}
</Group>
{configurationWizard && localProvider && <ServerConfigurationAdminForm serverConfig={serverConfig} />}
</React.Fragment>
);
});
@@ -11,9 +11,7 @@ import { ServerConfigurationAdministrationNavService, ServerConfigurationService
import { AuthenticationService } from '@cloudbeaver/plugin-authentication';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
const AuthenticationProviders = importLazyComponent(() =>
import('./Administration/ServerConfiguration/AuthenticationProviders.js').then(m => m.AuthenticationProviders),
);
const AnonymousAccess = importLazyComponent(() => import('./Administration/ServerConfiguration/AnonymousAccess.js').then(m => m.AnonymousAccess));
@injectable(() => [ServerConfigurationService, ServerConfigurationAdministrationNavService, AuthenticationService])
export class PluginBootstrap extends Bootstrap {
@@ -26,7 +24,7 @@ export class PluginBootstrap extends Bootstrap {
}
override register(): void {
this.serverConfigurationService.configurationContainer.add(AuthenticationProviders, 0);
this.serverConfigurationService.securitySettingsContainer.add(AnonymousAccess, 0);
this.authenticationService.setConfigureAuthProvider(() => this.serverConfigurationAdministrationNavService.navToSettings());
}
}