From eace7bd05d2e8bf55f511d289b7cc3a8c36ae52b Mon Sep 17 00:00:00 2001 From: Ainur Date: Mon, 15 Jan 2024 14:22:27 +0100 Subject: [PATCH 01/10] CB-4491 password policy config api --- .../DefaultConfiguration/cloudbeaver.conf | 6 +++ .../SQLiteConfiguration/cloudbeaver.conf | 6 +++ .../schema/service.core.graphqls | 8 ++++ .../model/WebPasswordPolicyConfiguration.java | 46 +++++++++++++++++++ .../io/cloudbeaver/model/WebServerConfig.java | 5 ++ .../io/cloudbeaver/server/CBApplication.java | 15 +++++- .../io/cloudbeaver/server/CBConstants.java | 1 + 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf index f65b0eee16..76058283fc 100644 --- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf +++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf @@ -36,6 +36,12 @@ maxConnections: 100, validationQuery: "SELECT 1" } + }, + passwordPolicy: { + minLength: 8, + requiresUpperCase: true, + minDigits: 1, + minSpecialCharacters: 0 } }, diff --git a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf index 67427c38c1..4ad5b43bf5 100644 --- a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf +++ b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf @@ -27,6 +27,12 @@ maxConnections: 100, validationQuery: "SELECT 1" } + }, + passwordPolicy: { + minLength: 8, + requiresUpperCase: true, + minDigits: 1, + minSpecialCharacters: 0 } }, diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 0e998720b4..7a5dbbf372 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -99,6 +99,13 @@ type WebServiceConfig { bundleVersion: String! } +type PasswordPolicyConfig @since(version: "23.3.3") { + minLength: Int! + minDigits: Int! + minSpecialCharacters: Int! + requiresUpperCase: Boolean! +} + type ProductInfo { id: ID! version: String! @@ -152,6 +159,7 @@ type ServerConfig { defaultNavigatorSettings: NavigatorSettings! disabledDrivers: [ID!]! resourceQuotas: Object! + passwordPolicyConfiguration: PasswordPolicyConfig! @since(version: "23.3.3") } type SessionInfo { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java new file mode 100644 index 0000000000..475c17ceee --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java @@ -0,0 +1,46 @@ +/* + * 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.model; + +import org.jkiss.dbeaver.model.meta.Property; + +public class WebPasswordPolicyConfiguration { + private int minLength; + private int minDigits; + private int minSpecialCharacters; + private boolean requiresUpperCase; + + @Property + public int getMinLength() { + return minLength; + } + + @Property + public int getMinDigits() { + return minDigits; + } + + @Property + public int getMinSpecialCharacters() { + return minSpecialCharacters; + } + + @Property + public boolean isRequiresUpperCase() { + return requiresUpperCase; + } +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index bc2eda8600..94099c686e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -217,4 +217,9 @@ public String getDefaultAuthRole() { public String getDefaultUserTeam() { return application.getAppConfiguration().getDefaultUserTeam(); } + + @Property + public WebPasswordPolicyConfiguration getPasswordPolicyConfiguration() { + return application.getPasswordPolicyConfiguration(); + } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index b38bf4deb6..e4eefbe960 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -22,6 +22,7 @@ import io.cloudbeaver.WebServiceUtils; import io.cloudbeaver.auth.CBAuthConstants; import io.cloudbeaver.auth.NoAuthCredentialsProvider; +import io.cloudbeaver.model.WebPasswordPolicyConfiguration; import io.cloudbeaver.model.app.BaseWebApplication; import io.cloudbeaver.model.app.WebAuthApplication; import io.cloudbeaver.model.app.WebAuthConfiguration; @@ -127,6 +128,7 @@ public static CBApplication getInstance() { protected final Map databaseConfiguration = new HashMap<>(); protected final SMControllerConfiguration securityManagerConfiguration = new SMControllerConfiguration(); private final CBAppConfig appConfiguration = new CBAppConfig(); + private final WebPasswordPolicyConfiguration passwordPolicyConfig = new WebPasswordPolicyConfiguration(); private Map externalProperties = new LinkedHashMap<>(); private Map originalConfigurationProperties = new LinkedHashMap<>(); @@ -231,6 +233,10 @@ public Map getProductConfiguration() { return productConfiguration; } + public WebPasswordPolicyConfiguration getPasswordPolicyConfiguration() { + return passwordPolicyConfig; + } + public SMAdminController getSecurityController() { return securityController; } @@ -599,6 +605,11 @@ protected void parseConfiguration(Map configProps) throws DBExce Map appConfig = JSONUtils.getObject(configProps, "app"); validateConfiguration(appConfig); gson.fromJson(gson.toJsonTree(appConfig), CBAppConfig.class); + // Password policy config + gson.fromJson( + gson.toJsonTree(JSONUtils.getObject(serverConfig, CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION)), + WebPasswordPolicyConfiguration.class + ); databaseConfiguration.putAll(JSONUtils.getObject(serverConfig, CBConstants.PARAM_DB_CONFIGURATION)); @@ -772,11 +783,13 @@ protected GsonBuilder getGsonBuilder() { InstanceCreator appConfigCreator = type -> appConfiguration; InstanceCreator navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings(); InstanceCreator smConfigCreator = type -> securityManagerConfiguration; + InstanceCreator webPasswordPolicyConfigCreator = type -> passwordPolicyConfig; return new GsonBuilder() .setLenient() .registerTypeAdapter(CBAppConfig.class, appConfigCreator) .registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator) - .registerTypeAdapter(SMControllerConfiguration.class, smConfigCreator); + .registerTypeAdapter(SMControllerConfiguration.class, smConfigCreator) + .registerTypeAdapter(WebPasswordPolicyConfiguration.class, webPasswordPolicyConfigCreator); } protected void readAdditionalConfiguration(Map rootConfig) throws DBException { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java index 9eb64d24c0..7e35aba999 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java @@ -50,6 +50,7 @@ public class CBConstants { public static final String PARAM_DEVEL_MODE = "develMode"; public static final String PARAM_SECURITY_MANAGER = "enableSecurityManager"; public static final String PARAM_SM_CONFIGURATION = "sm"; + public static final String PARAM_PASSWORD_POLICY_CONFIGURATION = "passwordPolicy"; public static final int DEFAULT_SERVER_PORT = 8080; //public static final String DEFAULT_SERVER_NAME = "CloudBeaver Web Server"; From d18304044cba7c6e84f463aac2504d1c5058161b Mon Sep 17 00:00:00 2001 From: Ainur Date: Mon, 15 Jan 2024 17:12:10 +0100 Subject: [PATCH 02/10] CB-4491 move policy config to sm --- .../DefaultConfiguration/cloudbeaver.conf | 10 ++++- .../SQLiteConfiguration/cloudbeaver.conf | 16 +++++--- .../schema/service.core.graphqls | 2 +- .../io/cloudbeaver/model/WebServerConfig.java | 5 ++- .../io/cloudbeaver/server/CBApplication.java | 39 +++++++++++++++---- .../PasswordPolicyConfiguration.java} | 20 ++++++---- .../security/SMControllerConfiguration.java | 5 +++ 7 files changed, 72 insertions(+), 25 deletions(-) rename server/bundles/{io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java => io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java} (59%) diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf index 76058283fc..c3e1bfc4f8 100644 --- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf +++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf @@ -23,7 +23,13 @@ enableBruteForceProtection: "${CLOUDBEAVER_BRUTE_FORCE_PROTECTION_ENABLED:true}", maxFailedLogin: "${CLOUDBEAVER_MAX_FAILED_LOGINS:10}", minimumLoginTimeout: "${CLOUDBEAVER_MINIMUM_LOGIN_TIMEOUT:1}", - blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}" + blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}", + passwordPolicy: { + minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}", + requiresUpperLowerCase: "${CLOUDBEAVER_POLICY_UPPER_LOWER_CASE_REQUIRED:true}", + minDigits: "${CLOUDBEAVER_POLICY_MIN_DIGITS:1}", + minSpecialCharacters: "${CLOUDBEAVER_POLICY_MIN_SPECIAL_CHARACTERS:0}" + } }, database: { @@ -39,7 +45,7 @@ }, passwordPolicy: { minLength: 8, - requiresUpperCase: true, + requiresUpperLowerCase: true, minDigits: 1, minSpecialCharacters: 0 } diff --git a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf index 4ad5b43bf5..040df1167f 100644 --- a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf +++ b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf @@ -28,11 +28,17 @@ validationQuery: "SELECT 1" } }, - passwordPolicy: { - minLength: 8, - requiresUpperCase: true, - minDigits: 1, - minSpecialCharacters: 0 + sm: { + enableBruteForceProtection: "${CLOUDBEAVER_BRUTE_FORCE_PROTECTION_ENABLED:true}", + maxFailedLogin: "${CLOUDBEAVER_MAX_FAILED_LOGINS:10}", + minimumLoginTimeout: "${CLOUDBEAVER_MINIMUM_LOGIN_TIMEOUT:1}", + blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}", + passwordPolicy: { + minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}", + requiresUpperLowerCase: "${CLOUDBEAVER_POLICY_UPPER_LOWER_CASE_REQUIRED:true}", + minDigits: "${CLOUDBEAVER_POLICY_MIN_DIGITS:1}", + minSpecialCharacters: "${CLOUDBEAVER_POLICY_MIN_SPECIAL_CHARACTERS:0}" + } } }, diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 7a5dbbf372..63f85d79d4 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -103,7 +103,7 @@ type PasswordPolicyConfig @since(version: "23.3.3") { minLength: Int! minDigits: Int! minSpecialCharacters: Int! - requiresUpperCase: Boolean! + requiresUpperLowerCase: Boolean! } type ProductInfo { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index 94099c686e..ea429a9529 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -20,6 +20,7 @@ import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; +import io.cloudbeaver.service.security.PasswordPolicyConfiguration; import org.jkiss.dbeaver.model.meta.Property; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.registry.language.PlatformLanguageDescriptor; @@ -219,7 +220,7 @@ public String getDefaultUserTeam() { } @Property - public WebPasswordPolicyConfiguration getPasswordPolicyConfiguration() { - return application.getPasswordPolicyConfiguration(); + public PasswordPolicyConfiguration getPasswordPolicyConfiguration() { + return application.getSecurityManagerConfiguration().getPasswordPolicyConfiguration(); } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index e4eefbe960..c6c7af08af 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -22,7 +22,7 @@ import io.cloudbeaver.WebServiceUtils; import io.cloudbeaver.auth.CBAuthConstants; import io.cloudbeaver.auth.NoAuthCredentialsProvider; -import io.cloudbeaver.model.WebPasswordPolicyConfiguration; +import io.cloudbeaver.service.security.PasswordPolicyConfiguration; import io.cloudbeaver.model.app.BaseWebApplication; import io.cloudbeaver.model.app.WebAuthApplication; import io.cloudbeaver.model.app.WebAuthConfiguration; @@ -128,7 +128,6 @@ public static CBApplication getInstance() { protected final Map databaseConfiguration = new HashMap<>(); protected final SMControllerConfiguration securityManagerConfiguration = new SMControllerConfiguration(); private final CBAppConfig appConfiguration = new CBAppConfig(); - private final WebPasswordPolicyConfiguration passwordPolicyConfig = new WebPasswordPolicyConfiguration(); private Map externalProperties = new LinkedHashMap<>(); private Map originalConfigurationProperties = new LinkedHashMap<>(); @@ -233,8 +232,8 @@ public Map getProductConfiguration() { return productConfiguration; } - public WebPasswordPolicyConfiguration getPasswordPolicyConfiguration() { - return passwordPolicyConfig; + public SMControllerConfiguration getSecurityManagerConfiguration() { + return securityManagerConfiguration; } public SMAdminController getSecurityController() { @@ -608,7 +607,7 @@ protected void parseConfiguration(Map configProps) throws DBExce // Password policy config gson.fromJson( gson.toJsonTree(JSONUtils.getObject(serverConfig, CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION)), - WebPasswordPolicyConfiguration.class + PasswordPolicyConfiguration.class ); databaseConfiguration.putAll(JSONUtils.getObject(serverConfig, CBConstants.PARAM_DB_CONFIGURATION)); @@ -783,13 +782,14 @@ protected GsonBuilder getGsonBuilder() { InstanceCreator appConfigCreator = type -> appConfiguration; InstanceCreator navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings(); InstanceCreator smConfigCreator = type -> securityManagerConfiguration; - InstanceCreator webPasswordPolicyConfigCreator = type -> passwordPolicyConfig; + InstanceCreator smPasswordPoliceConfigCreator = + type -> securityManagerConfiguration.getPasswordPolicyConfiguration(); return new GsonBuilder() .setLenient() .registerTypeAdapter(CBAppConfig.class, appConfigCreator) .registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator) .registerTypeAdapter(SMControllerConfiguration.class, smConfigCreator) - .registerTypeAdapter(WebPasswordPolicyConfiguration.class, webPasswordPolicyConfigCreator); + .registerTypeAdapter(PasswordPolicyConfiguration.class, smPasswordPoliceConfigCreator); } protected void readAdditionalConfiguration(Map rootConfig) throws DBException { @@ -1055,6 +1055,7 @@ protected Map collectConfigurationProperties( } serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfigProperties); } + savePasswordPolicyConfig(originServerConfig, serverConfigProperties); } { var appConfigProperties = new LinkedHashMap(); @@ -1164,6 +1165,30 @@ protected Map collectConfigurationProperties( return rootConfig; } + private void savePasswordPolicyConfig(Map originServerConfig, LinkedHashMap serverConfigProperties) { + // save password policy configuration + var passwordPolicyProperties = new LinkedHashMap(); + + var oldRuntimePasswordPolicyConfig = JSONUtils.getObject( + JSONUtils.getObject(originServerConfig, CBConstants.PARAM_SM_CONFIGURATION), + CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION + ); + Gson gson = getGson(); + Map passwordPolicyConfig = gson.fromJson( + gson.toJsonTree(securityManagerConfiguration.getPasswordPolicyConfiguration()), + JSONUtils.MAP_TYPE_TOKEN + ); + if (!CommonUtils.isEmpty(passwordPolicyConfig) && !isDistributed()) { + for (Map.Entry mp : passwordPolicyConfig.entrySet()) { + copyConfigValue(oldRuntimePasswordPolicyConfig, passwordPolicyProperties, mp.getKey(), mp.getValue()); + } + serverConfigProperties.put( + CBConstants.PARAM_SM_CONFIGURATION, + Map.of(CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION, passwordPolicyProperties) + ); + } + } + //////////////////////////////////////////////////////////////////////// // License management diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java similarity index 59% rename from server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java rename to server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java index 475c17ceee..baaa4843a7 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebPasswordPolicyConfiguration.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java @@ -14,15 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.cloudbeaver.model; +package io.cloudbeaver.service.security; import org.jkiss.dbeaver.model.meta.Property; -public class WebPasswordPolicyConfiguration { - private int minLength; - private int minDigits; - private int minSpecialCharacters; - private boolean requiresUpperCase; +public class PasswordPolicyConfiguration { + private static final int DEFAULT_MIN_LENGTH = 8; + private static final int DEFAULT_MIN_DIGITS = 1; + private static final int DEFAULT_MIN_SPECIAL_CHARACTERS = 0; + private static final boolean DEFAULT_REQUIRES_UPPER_LOWER_CASE = true; + private int minLength = DEFAULT_MIN_LENGTH; + private int minDigits = DEFAULT_MIN_DIGITS; + private int minSpecialCharacters = DEFAULT_MIN_SPECIAL_CHARACTERS; + private boolean requiresUpperLowerCase = DEFAULT_REQUIRES_UPPER_LOWER_CASE; @Property public int getMinLength() { @@ -40,7 +44,7 @@ public int getMinSpecialCharacters() { } @Property - public boolean isRequiresUpperCase() { - return requiresUpperCase; + public boolean isRequiresUpperLowerCase() { + return requiresUpperLowerCase; } } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/SMControllerConfiguration.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/SMControllerConfiguration.java index 26fb64585e..70a704faf1 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/SMControllerConfiguration.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/SMControllerConfiguration.java @@ -36,6 +36,7 @@ public class SMControllerConfiguration { private int maxFailedLogin = DEFAULT_MAX_FAILED_LOGIN; private int minimumLoginTimeout = DEFAULT_MINIMUM_LOGIN_TIMEOUT; private int blockLoginPeriod = DEFAULT_BLOCK_LOGIN_PERIOD; + private final PasswordPolicyConfiguration passwordPolicy = new PasswordPolicyConfiguration(); public int getAccessTokenTtl() { return accessTokenTtl; @@ -92,4 +93,8 @@ public void setMinimumLoginTimeout(int minimumTimeout) { public void setBlockLoginPeriod(int blockPeriod) { this.blockLoginPeriod = blockPeriod; } + + public PasswordPolicyConfiguration getPasswordPolicyConfiguration() { + return passwordPolicy; + } } From 08aaf8dc35ee65a2ad6cec03235abc6e9c20b07a Mon Sep 17 00:00:00 2001 From: Ainur Date: Tue, 16 Jan 2024 11:36:52 +0100 Subject: [PATCH 03/10] CB-4491 cb config fix --- .../sample-databases/DefaultConfiguration/cloudbeaver.conf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf index c3e1bfc4f8..ba6f13cbec 100644 --- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf +++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf @@ -42,12 +42,6 @@ maxConnections: 100, validationQuery: "SELECT 1" } - }, - passwordPolicy: { - minLength: 8, - requiresUpperLowerCase: true, - minDigits: 1, - minSpecialCharacters: 0 } }, From 49dc68d825f3997851b8c81efcb9ac6a3ec9c0f4 Mon Sep 17 00:00:00 2001 From: naumov Date: Tue, 16 Jan 2024 17:15:39 +0100 Subject: [PATCH 04/10] CB-4264 add password policy --- .../src/PasswordPolicyService.ts | 73 +++++++++++++++++++ .../packages/core-authentication/src/index.ts | 2 + .../core-authentication/src/locales/en.ts | 5 ++ .../core-authentication/src/locales/it.ts | 5 ++ .../core-authentication/src/locales/ru.ts | 5 ++ .../core-authentication/src/locales/zh.ts | 5 ++ .../core-authentication/src/manifest.ts | 2 + .../src/usePasswordPolicy.ts | 22 ++++++ .../core-root/src/ServerConfigResource.ts | 4 + .../data/defaultServerConfig.ts | 6 ++ .../src/queries/session/serverConfig.gql | 6 ++ .../ServerConfigurationService.ts | 42 +++++++---- .../plugin-administration/src/locales/en.ts | 2 + .../plugin-administration/src/locales/it.ts | 2 + .../plugin-administration/src/locales/ru.ts | 2 + .../plugin-administration/src/locales/zh.ts | 2 + ...verConfigurationAuthenticationBootstrap.ts | 10 ++- .../UserForm/Info/UserFormInfoCredentials.tsx | 4 +- .../ChangePassword/useChangePassword.ts | 12 ++- 19 files changed, 193 insertions(+), 18 deletions(-) create mode 100644 webapp/packages/core-authentication/src/PasswordPolicyService.ts create mode 100644 webapp/packages/core-authentication/src/usePasswordPolicy.ts diff --git a/webapp/packages/core-authentication/src/PasswordPolicyService.ts b/webapp/packages/core-authentication/src/PasswordPolicyService.ts new file mode 100644 index 0000000000..03392799f0 --- /dev/null +++ b/webapp/packages/core-authentication/src/PasswordPolicyService.ts @@ -0,0 +1,73 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { computed, makeObservable } from 'mobx'; + +import { injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; +import type { PasswordPolicyConfig } from '@cloudbeaver/core-sdk'; + +const DEFAULT_PASSWORD_POLICY: PasswordPolicyConfig = { + minLength: 8, + requiresUpperLowerCase: false, + minDigits: 0, + minSpecialCharacters: 0, +}; + +type ValidationResult = { isValid: true; errorMessage: null } | { isValid: false; errorMessage: string }; + +@injectable() +export class PasswordPolicyService { + get config(): PasswordPolicyConfig { + return { + minLength: this.serverConfigResource.data?.passwordPolicyConfiguration?.minLength || DEFAULT_PASSWORD_POLICY.minLength, + requiresUpperLowerCase: + this.serverConfigResource.data?.passwordPolicyConfiguration?.requiresUpperLowerCase || DEFAULT_PASSWORD_POLICY.requiresUpperLowerCase, + minDigits: this.serverConfigResource.data?.passwordPolicyConfiguration?.minDigits || DEFAULT_PASSWORD_POLICY.minDigits, + minSpecialCharacters: + this.serverConfigResource.data?.passwordPolicyConfiguration?.minSpecialCharacters || DEFAULT_PASSWORD_POLICY.minSpecialCharacters, + }; + } + + constructor(private readonly serverConfigResource: ServerConfigResource, private readonly localizationService: LocalizationService) { + makeObservable(this, { + config: computed, + }); + } + + validatePassword(password: string): ValidationResult { + if (password.length < this.config.minLength) { + return { + isValid: false, + errorMessage: this.localizationService.translate('core_authentication_password_policy_min_length', undefined, { min: this.config.minLength }), + }; + } + + if (this.config.requiresUpperLowerCase && !(/[a-z]/.test(password) && /[A-Z]/.test(password))) { + return { isValid: false, errorMessage: this.localizationService.translate('core_authentication_password_policy_upper_lower_case') }; + } + + if ((password.match(/\d/g) || []).length < this.config.minDigits) { + return { + isValid: false, + errorMessage: this.localizationService.translate('core_authentication_password_policy_min_digits', undefined, { min: this.config.minDigits }), + }; + } + + if ((password.match(/[!@#$%^&*(),.?":{}|<>]/g) || []).length < this.config.minSpecialCharacters) { + return { + isValid: false, + errorMessage: this.localizationService.translate('core_authentication_password_policy_min_special_characters', undefined, { + min: this.config.minSpecialCharacters, + }), + }; + } + + return { isValid: true, errorMessage: null }; + } +} diff --git a/webapp/packages/core-authentication/src/index.ts b/webapp/packages/core-authentication/src/index.ts index b2b7c7af17..a18575bacc 100644 --- a/webapp/packages/core-authentication/src/index.ts +++ b/webapp/packages/core-authentication/src/index.ts @@ -20,3 +20,5 @@ export * from './UsersResource'; export * from './TeamMetaParametersResource'; export * from './EAdminPermission'; export * from './AUTH_SETTINGS_GROUP'; +export * from './PasswordPolicyService'; +export * from './usePasswordPolicy'; diff --git a/webapp/packages/core-authentication/src/locales/en.ts b/webapp/packages/core-authentication/src/locales/en.ts index fd933c546c..4554669df2 100644 --- a/webapp/packages/core-authentication/src/locales/en.ts +++ b/webapp/packages/core-authentication/src/locales/en.ts @@ -2,4 +2,9 @@ export default [ ['settings_authentication', 'Authentication'], ['settings_authentication_disable_anonymous_access_name', 'Disable anonymous access'], ['settings_authentication_disable_anonymous_access_description', 'Disable anonymous access function'], + + ['core_authentication_password_policy_min_length', 'Password must be at least {arg:min} characters long'], + ['core_authentication_password_policy_upper_lower_case', 'Password must contain both upper and lower case letters'], + ['core_authentication_password_policy_min_digits', 'Password must contain at least {arg:min} digits'], + ['core_authentication_password_policy_min_special_characters', 'Password must contain at least {arg:min} special characters'], ]; diff --git a/webapp/packages/core-authentication/src/locales/it.ts b/webapp/packages/core-authentication/src/locales/it.ts index fd933c546c..4554669df2 100644 --- a/webapp/packages/core-authentication/src/locales/it.ts +++ b/webapp/packages/core-authentication/src/locales/it.ts @@ -2,4 +2,9 @@ export default [ ['settings_authentication', 'Authentication'], ['settings_authentication_disable_anonymous_access_name', 'Disable anonymous access'], ['settings_authentication_disable_anonymous_access_description', 'Disable anonymous access function'], + + ['core_authentication_password_policy_min_length', 'Password must be at least {arg:min} characters long'], + ['core_authentication_password_policy_upper_lower_case', 'Password must contain both upper and lower case letters'], + ['core_authentication_password_policy_min_digits', 'Password must contain at least {arg:min} digits'], + ['core_authentication_password_policy_min_special_characters', 'Password must contain at least {arg:min} special characters'], ]; diff --git a/webapp/packages/core-authentication/src/locales/ru.ts b/webapp/packages/core-authentication/src/locales/ru.ts index 7edfa853fa..51fffc3dbd 100644 --- a/webapp/packages/core-authentication/src/locales/ru.ts +++ b/webapp/packages/core-authentication/src/locales/ru.ts @@ -2,4 +2,9 @@ export default [ ['settings_authentication', 'Аутентификация'], ['settings_authentication_disable_anonymous_access_name', 'Отключить анонимный доступ'], ['settings_authentication_disable_anonymous_access_description', 'Отключить функцию анонимного доступа'], + + ['core_authentication_password_policy_min_length', 'Пароль должен быть не менее {arg:min} символов'], + ['core_authentication_password_policy_upper_lower_case', 'Пароль должен содержать как заглавные, так и строчные буквы'], + ['core_authentication_password_policy_min_digits', 'Пароль должен содержать не менее {arg:min} цифр'], + ['core_authentication_password_policy_min_special_characters', 'Пароль должен содержать не менее {arg:min} специальных символов'], ]; diff --git a/webapp/packages/core-authentication/src/locales/zh.ts b/webapp/packages/core-authentication/src/locales/zh.ts index fd933c546c..4554669df2 100644 --- a/webapp/packages/core-authentication/src/locales/zh.ts +++ b/webapp/packages/core-authentication/src/locales/zh.ts @@ -2,4 +2,9 @@ export default [ ['settings_authentication', 'Authentication'], ['settings_authentication_disable_anonymous_access_name', 'Disable anonymous access'], ['settings_authentication_disable_anonymous_access_description', 'Disable anonymous access function'], + + ['core_authentication_password_policy_min_length', 'Password must be at least {arg:min} characters long'], + ['core_authentication_password_policy_upper_lower_case', 'Password must contain both upper and lower case letters'], + ['core_authentication_password_policy_min_digits', 'Password must contain at least {arg:min} digits'], + ['core_authentication_password_policy_min_special_characters', 'Password must contain at least {arg:min} special characters'], ]; diff --git a/webapp/packages/core-authentication/src/manifest.ts b/webapp/packages/core-authentication/src/manifest.ts index b6ae2c3fc1..d61ab7a50e 100644 --- a/webapp/packages/core-authentication/src/manifest.ts +++ b/webapp/packages/core-authentication/src/manifest.ts @@ -16,6 +16,7 @@ import { AuthProvidersResource } from './AuthProvidersResource'; import { AuthRolesResource } from './AuthRolesResource'; import { AuthSettingsService } from './AuthSettingsService'; import { LocaleService } from './LocaleService'; +import { PasswordPolicyService } from './PasswordPolicyService'; import { TeamMetaParametersResource } from './TeamMetaParametersResource'; import { TeamsManagerService } from './TeamsManagerService'; import { TeamsResource } from './TeamsResource'; @@ -47,6 +48,7 @@ export const coreAuthenticationManifest: PluginManifest = { UserConfigurationBootstrap, AuthRolesResource, TeamMetaParametersResource, + PasswordPolicyService, LocaleService, ], }; diff --git a/webapp/packages/core-authentication/src/usePasswordPolicy.ts b/webapp/packages/core-authentication/src/usePasswordPolicy.ts new file mode 100644 index 0000000000..fed5237589 --- /dev/null +++ b/webapp/packages/core-authentication/src/usePasswordPolicy.ts @@ -0,0 +1,22 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { useCustomInputValidation } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; + +import { PasswordPolicyService } from './PasswordPolicyService'; + +export function usePasswordPolicy() { + const passwordPolicyService = useService(PasswordPolicyService); + + const ref = useCustomInputValidation(value => { + const validation = passwordPolicyService.validatePassword(value); + return validation.isValid ? null : validation.errorMessage; + }); + + return ref; +} diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 168c131608..b3be79531e 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -149,6 +149,10 @@ export class ServerConfigResource extends CachedDataResource) = releaseTime: 'July 11, 2022', licenseInfo: '', }, + passwordPolicyConfiguration: { + minLength: 8, + minDigits: 0, + minSpecialCharacters: 0, + requiresUpperLowerCase: false, + }, }, }); diff --git a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql index da4ec7912d..a2d84cfb4c 100644 --- a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql +++ b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql @@ -52,5 +52,11 @@ query serverConfig { releaseTime licenseInfo } + passwordPolicyConfiguration { + minLength + minDigits + minSpecialCharacters + requiresUpperLowerCase + } } } diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 345f7ddfe4..090ab61d5f 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -153,7 +153,7 @@ export class ServerConfigurationService { const validation = contexts.getContext(serverConfigValidationContext); - return validation.getState(); + return validation.valid; } private readonly loadServerConfig: IExecutorHandler = async (data, contexts) => { @@ -200,7 +200,7 @@ export class ServerConfigurationService { private readonly save: IExecutorHandler = async (data, contexts) => { const validation = contexts.getContext(serverConfigValidationContext); - if (!validation.getState()) { + if (!validation.valid) { return; } @@ -221,8 +221,18 @@ export class ServerConfigurationService { private readonly ensureValidation: IExecutorHandler = (data, contexts) => { const validation = contexts.getContext(serverConfigValidationContext); - if (!validation.getState()) { + if (!validation.valid) { ExecutorInterrupter.interrupt(contexts); + + if (validation.messages.length > 0) { + this.notificationService.notify( + { + title: 'administration_configuration_wizard_step_validation_message', + message: validation.messages.join('\n'), + }, + validation.valid ? ENotificationType.Info : ENotificationType.Error, + ); + } this.done = false; } else { this.done = true; @@ -295,21 +305,27 @@ export class ServerConfigurationService { } export interface IValidationStatusContext { - getState: () => boolean; + valid: boolean; + messages: string[]; invalidate: () => void; + info: (message: string) => void; + error: (message: string) => void; } export function serverConfigValidationContext(): IValidationStatusContext { - let state = true; - - const invalidate = () => { - state = false; - }; - const getState = () => state; - return { - getState, - invalidate, + valid: true, + messages: [], + invalidate() { + this.valid = false; + }, + info(message: string) { + this.messages.push(message); + }, + error(message: string) { + this.messages.push(message); + this.valid = false; + }, }; } diff --git a/webapp/packages/plugin-administration/src/locales/en.ts b/webapp/packages/plugin-administration/src/locales/en.ts index 10887d7624..b2ec88b42a 100644 --- a/webapp/packages/plugin-administration/src/locales/en.ts +++ b/webapp/packages/plugin-administration/src/locales/en.ts @@ -65,6 +65,8 @@ export default [ ['administration_configuration_wizard_configuration_navigator_show_system_objects', 'System Objects'], ['administration_configuration_wizard_configuration_navigator_show_utility_objects', 'Utility Objects'], + ['administration_configuration_wizard_step_validation_message', 'Failed to proceed to the next step'], + ['administration_configuration_wizard_finish', 'Confirmation'], ['administration_configuration_wizard_finish_step_description', 'Confirmation'], ['administration_configuration_wizard_finish_title', 'That is almost it.'], diff --git a/webapp/packages/plugin-administration/src/locales/it.ts b/webapp/packages/plugin-administration/src/locales/it.ts index 2fcc1d55e8..1f70630398 100644 --- a/webapp/packages/plugin-administration/src/locales/it.ts +++ b/webapp/packages/plugin-administration/src/locales/it.ts @@ -70,6 +70,8 @@ export default [ ['administration_configuration_wizard_configuration_navigator_show_system_objects', 'Oggetti di Sistema'], ['administration_configuration_wizard_configuration_navigator_show_utility_objects', 'Oggetti di UtilitàUtility Objects'], + ['administration_configuration_wizard_step_validation_message', 'Failed to proceed to the next step'], + ['administration_configuration_wizard_finish', 'Conferma'], ['administration_configuration_wizard_finish_step_description', 'Conferma'], ['administration_configuration_wizard_finish_title', 'Ci siamo quasi.'], diff --git a/webapp/packages/plugin-administration/src/locales/ru.ts b/webapp/packages/plugin-administration/src/locales/ru.ts index aaed836b51..1a506514bd 100644 --- a/webapp/packages/plugin-administration/src/locales/ru.ts +++ b/webapp/packages/plugin-administration/src/locales/ru.ts @@ -26,6 +26,8 @@ export default [ 'Все новые подключения, созданные пользователем, будут иметь только базовую информацию в дереве навигации', ], + ['administration_configuration_wizard_step_validation_message', 'Не удалось перейти к следующему шагу'], + ['administration_configuration_wizard_configuration_security', 'Безопасность'], ['administration_configuration_wizard_configuration_security_admin_credentials', 'Позволить сохранять приватные данные'], ['administration_configuration_wizard_configuration_security_public_credentials', 'Позволить сохранять приватные данные для пользователей'], diff --git a/webapp/packages/plugin-administration/src/locales/zh.ts b/webapp/packages/plugin-administration/src/locales/zh.ts index 1dc16e6cb9..a815b51f49 100644 --- a/webapp/packages/plugin-administration/src/locales/zh.ts +++ b/webapp/packages/plugin-administration/src/locales/zh.ts @@ -52,6 +52,8 @@ export default [ ['administration_configuration_wizard_configuration_navigator_show_system_objects', '系统对象'], ['administration_configuration_wizard_configuration_navigator_show_utility_objects', '实用程序对象'], + ['administration_configuration_wizard_step_validation_message', 'Failed to proceed to the next step'], + ['administration_configuration_wizard_finish', '确认'], ['administration_configuration_wizard_finish_step_description', '确认'], ['administration_configuration_wizard_finish_title', '差不多就这样。'], diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts index 293767a2a2..7df18decf2 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ import { AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource } from '@cloudbeaver/core-authentication'; +import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, PasswordPolicyService } from '@cloudbeaver/core-authentication'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor'; @@ -27,6 +27,7 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { private readonly authProvidersResource: AuthProvidersResource, private readonly serverConfigResource: ServerConfigResource, private readonly notificationService: NotificationService, + private readonly passwordPolicyService: PasswordPolicyService, ) { super(); } @@ -81,7 +82,12 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { const validation = contexts.getContext(serverConfigValidationContext); if (!data.state.serverConfig.adminName || data.state.serverConfig.adminName.length < 6 || !data.state.serverConfig.adminPassword) { - validation.invalidate(); + return validation.invalidate(); + } + + const passwordValidation = this.passwordPolicyService.validatePassword(data.state.serverConfig.adminPassword); + if (!passwordValidation.isValid) { + validation.error(passwordValidation.errorMessage); } }; } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx index aade7c157c..23698b5958 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, isLocalUser, UsersResource } from '@cloudbeaver/core-authentication'; +import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, isLocalUser, usePasswordPolicy, UsersResource } from '@cloudbeaver/core-authentication'; import { Container, GroupTitle, InputField, useCustomInputValidation, useResource, useTranslate } from '@cloudbeaver/core-blocks'; import { FormMode } from '@cloudbeaver/core-ui'; import { isValuesEqual } from '@cloudbeaver/core-utils'; @@ -33,6 +33,7 @@ export const UserFormInfoCredentials = observer(function UserFormInfoCred { active: tabSelected && editing }, ); const authProvidersResource = useResource(UserFormInfoCredentials, AuthProvidersResource, null); + const passwordPolicyRef = usePasswordPolicy(); let local = authProvidersResource.resource.isEnabled(AUTH_PROVIDER_LOCAL_ID); @@ -56,6 +57,7 @@ export const UserFormInfoCredentials = observer(function UserFormInfoCred {local && ( <> ({ @@ -42,6 +43,13 @@ export function useChangePassword(): IState { return this.config.password.length > 0 && this.config.oldPassword.length > 0 && this.config.repeatedPassword.length > 0; }, async changePassword() { + const validation = this.passwordPolicyService.validatePassword(this.config.password); + + if (!validation.isValid) { + this.notificationService.logError({ title: validation.errorMessage }); + return; + } + if (this.config.password !== this.config.repeatedPassword) { this.notificationService.logError({ title: 'plugin_user_profile_authentication_change_password_passwords_not_match' }); return; @@ -71,6 +79,6 @@ export function useChangePassword(): IState { changePassword: action.bound, resetConfig: action, }, - { usersResource, notificationService }, + { usersResource, notificationService, passwordPolicyService }, ); } From 504b9892f34068ed1a94190de2739c00d3b67205 Mon Sep 17 00:00:00 2001 From: Ainur Date: Tue, 16 Jan 2024 17:21:55 +0100 Subject: [PATCH 05/10] CB-4491 deserialization fix --- .../src/io/cloudbeaver/server/CBApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index cf81026252..1d5bf6df54 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -597,7 +597,7 @@ protected void parseConfiguration(Map configProps) throws DBExce enableSecurityManager); //SM config gson.fromJson( - gson.toJsonTree(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)), + gson.toJson(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)), SMControllerConfiguration.class ); // App config From 452aafc0428264b6e466b92fc0ef5c3bc6d960f9 Mon Sep 17 00:00:00 2001 From: Ainur Date: Tue, 16 Jan 2024 17:23:38 +0100 Subject: [PATCH 06/10] CB-4491 deserialization fix --- .../src/io/cloudbeaver/server/CBApplication.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index 1d5bf6df54..b931441a47 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -604,11 +604,6 @@ protected void parseConfiguration(Map configProps) throws DBExce Map appConfig = JSONUtils.getObject(configProps, "app"); validateConfiguration(appConfig); gson.fromJson(gson.toJsonTree(appConfig), CBAppConfig.class); - // Password policy config - gson.fromJson( - gson.toJsonTree(JSONUtils.getObject(serverConfig, CBConstants.PARAM_PASSWORD_POLICY_CONFIGURATION)), - PasswordPolicyConfiguration.class - ); databaseConfiguration.putAll(JSONUtils.getObject(serverConfig, CBConstants.PARAM_DB_CONFIGURATION)); From 8f809cdcc7b759c5c832ec02682e0daae909b01b Mon Sep 17 00:00:00 2001 From: naumov Date: Wed, 17 Jan 2024 12:10:27 +0100 Subject: [PATCH 07/10] CB-4264 add title --- .../Authentication/ChangePassword/useChangePassword.ts | 5 ++++- webapp/packages/plugin-user-profile/src/locales/en.ts | 1 + webapp/packages/plugin-user-profile/src/locales/it.ts | 1 + webapp/packages/plugin-user-profile/src/locales/ru.ts | 1 + webapp/packages/plugin-user-profile/src/locales/zh.ts | 1 + 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts index 21ae90369a..1486d6935a 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts @@ -46,7 +46,10 @@ export function useChangePassword(): IState { const validation = this.passwordPolicyService.validatePassword(this.config.password); if (!validation.isValid) { - this.notificationService.logError({ title: validation.errorMessage }); + this.notificationService.logError({ + title: 'plugin_user_profile_authentication_change_password_password_validation_error', + message: validation.errorMessage, + }); return; } diff --git a/webapp/packages/plugin-user-profile/src/locales/en.ts b/webapp/packages/plugin-user-profile/src/locales/en.ts index 0317590459..c0a5a26775 100644 --- a/webapp/packages/plugin-user-profile/src/locales/en.ts +++ b/webapp/packages/plugin-user-profile/src/locales/en.ts @@ -15,4 +15,5 @@ export default [ ['plugin_user_profile_authentication_change_password_success', 'Password was changed successfully'], ['plugin_user_profile_authentication_change_password_submit_label', 'Change'], ['plugin_user_profile_authentication_change_password_passwords_not_match', "Passwords don't match"], + ['plugin_user_profile_authentication_change_password_password_validation_error', 'Password validation failed'], ]; diff --git a/webapp/packages/plugin-user-profile/src/locales/it.ts b/webapp/packages/plugin-user-profile/src/locales/it.ts index b6e2d18a81..2c796e7aeb 100644 --- a/webapp/packages/plugin-user-profile/src/locales/it.ts +++ b/webapp/packages/plugin-user-profile/src/locales/it.ts @@ -15,4 +15,5 @@ export default [ ['plugin_user_profile_authentication_change_password_success', 'Password modificata con successo'], ['plugin_user_profile_authentication_change_password_submit_label', 'Modifica'], ['plugin_user_profile_authentication_change_password_passwords_not_match', 'Le Passwords non coincidono'], + ['plugin_user_profile_authentication_change_password_password_validation_error', 'Password validation failed'], ]; diff --git a/webapp/packages/plugin-user-profile/src/locales/ru.ts b/webapp/packages/plugin-user-profile/src/locales/ru.ts index e6c3f0dce4..ac5bc52bc8 100644 --- a/webapp/packages/plugin-user-profile/src/locales/ru.ts +++ b/webapp/packages/plugin-user-profile/src/locales/ru.ts @@ -15,4 +15,5 @@ export default [ ['plugin_user_profile_authentication_change_password_success', 'Пароль был успешно изменен'], ['plugin_user_profile_authentication_change_password_submit_label', 'Сменить'], ['plugin_user_profile_authentication_change_password_passwords_not_match', 'Пароли не совпадают'], + ['plugin_user_profile_authentication_change_password_password_validation_error', 'Валидация пароля не удалась'], ]; diff --git a/webapp/packages/plugin-user-profile/src/locales/zh.ts b/webapp/packages/plugin-user-profile/src/locales/zh.ts index e29fb5c32d..f1abcfe504 100644 --- a/webapp/packages/plugin-user-profile/src/locales/zh.ts +++ b/webapp/packages/plugin-user-profile/src/locales/zh.ts @@ -15,4 +15,5 @@ export default [ ['plugin_user_profile_authentication_change_password_success', '密码更改成功'], ['plugin_user_profile_authentication_change_password_submit_label', '更改'], ['plugin_user_profile_authentication_change_password_passwords_not_match', '密码不匹配'], + ['plugin_user_profile_authentication_change_password_password_validation_error', 'Password validation failed'], ]; From 58ad8a6593dd97b9129e931f1e576acd84b44347 Mon Sep 17 00:00:00 2001 From: Ainur Date: Wed, 17 Jan 2024 16:53:18 +0100 Subject: [PATCH 08/10] CB-4264 rename params --- .../DefaultConfiguration/cloudbeaver.conf | 6 +++--- .../SQLiteConfiguration/cloudbeaver.conf | 6 +++--- .../schema/service.core.graphqls | 6 +++--- .../security/PasswordPolicyConfiguration.java | 18 +++++++++--------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf index ba6f13cbec..4102f85217 100644 --- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf +++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf @@ -26,9 +26,9 @@ blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}", passwordPolicy: { minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}", - requiresUpperLowerCase: "${CLOUDBEAVER_POLICY_UPPER_LOWER_CASE_REQUIRED:true}", - minDigits: "${CLOUDBEAVER_POLICY_MIN_DIGITS:1}", - minSpecialCharacters: "${CLOUDBEAVER_POLICY_MIN_SPECIAL_CHARACTERS:0}" + requireMixedCase: "${CLOUDBEAVER_POLICY_REQUIRE_MIXED_CASE:true}", + minNumberCount: "${CLOUDBEAVER_POLICY_MIN_NUMBER_COUNT:1}", + minSymbolCount: "${CLOUDBEAVER_POLICY_MIN_SYMBOL_COUNT:0}" } }, diff --git a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf index 040df1167f..aceb52c885 100644 --- a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf +++ b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf @@ -35,9 +35,9 @@ blockLoginPeriod: "${CLOUDBEAVER_BLOCK_PERIOD:300}", passwordPolicy: { minLength: "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}", - requiresUpperLowerCase: "${CLOUDBEAVER_POLICY_UPPER_LOWER_CASE_REQUIRED:true}", - minDigits: "${CLOUDBEAVER_POLICY_MIN_DIGITS:1}", - minSpecialCharacters: "${CLOUDBEAVER_POLICY_MIN_SPECIAL_CHARACTERS:0}" + requireMixedCase: "${CLOUDBEAVER_POLICY_REQUIRE_MIXED_CASE:true}", + minNumberCount: "${CLOUDBEAVER_POLICY_MIN_NUMBER_COUNT:1}", + minSymbolCount: "${CLOUDBEAVER_POLICY_MIN_SYMBOL_COUNT:0}" } } diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 63f85d79d4..0b2124d5e9 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -101,9 +101,9 @@ type WebServiceConfig { type PasswordPolicyConfig @since(version: "23.3.3") { minLength: Int! - minDigits: Int! - minSpecialCharacters: Int! - requiresUpperLowerCase: Boolean! + minNumberCount: Int! + minSymbolCount: Int! + requireMixedCase: Boolean! } type ProductInfo { diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java index baaa4843a7..28076d7174 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/PasswordPolicyConfiguration.java @@ -24,9 +24,9 @@ public class PasswordPolicyConfiguration { private static final int DEFAULT_MIN_SPECIAL_CHARACTERS = 0; private static final boolean DEFAULT_REQUIRES_UPPER_LOWER_CASE = true; private int minLength = DEFAULT_MIN_LENGTH; - private int minDigits = DEFAULT_MIN_DIGITS; - private int minSpecialCharacters = DEFAULT_MIN_SPECIAL_CHARACTERS; - private boolean requiresUpperLowerCase = DEFAULT_REQUIRES_UPPER_LOWER_CASE; + private int minNumberCount = DEFAULT_MIN_DIGITS; + private int minSymbolCount = DEFAULT_MIN_SPECIAL_CHARACTERS; + private boolean requireMixedCase = DEFAULT_REQUIRES_UPPER_LOWER_CASE; @Property public int getMinLength() { @@ -34,17 +34,17 @@ public int getMinLength() { } @Property - public int getMinDigits() { - return minDigits; + public int getMinNumberCount() { + return minNumberCount; } @Property - public int getMinSpecialCharacters() { - return minSpecialCharacters; + public int getMinSymbolCount() { + return minSymbolCount; } @Property - public boolean isRequiresUpperLowerCase() { - return requiresUpperLowerCase; + public boolean isRequireMixedCase() { + return requireMixedCase; } } From f36dcf3ee4ecf723a0d12467e3062444b292675a Mon Sep 17 00:00:00 2001 From: naumov Date: Wed, 17 Jan 2024 17:28:59 +0100 Subject: [PATCH 09/10] CB-4264 fix naming --- .../src/PasswordPolicyService.ts | 30 ++++++++++--------- .../data/defaultServerConfig.ts | 6 ++-- .../src/queries/session/serverConfig.gql | 6 ++-- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/webapp/packages/core-authentication/src/PasswordPolicyService.ts b/webapp/packages/core-authentication/src/PasswordPolicyService.ts index 03392799f0..dd81b87e96 100644 --- a/webapp/packages/core-authentication/src/PasswordPolicyService.ts +++ b/webapp/packages/core-authentication/src/PasswordPolicyService.ts @@ -14,9 +14,9 @@ import type { PasswordPolicyConfig } from '@cloudbeaver/core-sdk'; const DEFAULT_PASSWORD_POLICY: PasswordPolicyConfig = { minLength: 8, - requiresUpperLowerCase: false, - minDigits: 0, - minSpecialCharacters: 0, + minNumberCount: 0, + minSymbolCount: 0, + requireMixedCase: false, }; type ValidationResult = { isValid: true; errorMessage: null } | { isValid: false; errorMessage: string }; @@ -26,11 +26,9 @@ export class PasswordPolicyService { get config(): PasswordPolicyConfig { return { minLength: this.serverConfigResource.data?.passwordPolicyConfiguration?.minLength || DEFAULT_PASSWORD_POLICY.minLength, - requiresUpperLowerCase: - this.serverConfigResource.data?.passwordPolicyConfiguration?.requiresUpperLowerCase || DEFAULT_PASSWORD_POLICY.requiresUpperLowerCase, - minDigits: this.serverConfigResource.data?.passwordPolicyConfiguration?.minDigits || DEFAULT_PASSWORD_POLICY.minDigits, - minSpecialCharacters: - this.serverConfigResource.data?.passwordPolicyConfiguration?.minSpecialCharacters || DEFAULT_PASSWORD_POLICY.minSpecialCharacters, + minNumberCount: this.serverConfigResource.data?.passwordPolicyConfiguration?.minNumberCount || DEFAULT_PASSWORD_POLICY.minNumberCount, + minSymbolCount: this.serverConfigResource.data?.passwordPolicyConfiguration?.minSymbolCount || DEFAULT_PASSWORD_POLICY.minSymbolCount, + requireMixedCase: this.serverConfigResource.data?.passwordPolicyConfiguration?.requireMixedCase || DEFAULT_PASSWORD_POLICY.requireMixedCase, }; } @@ -41,29 +39,33 @@ export class PasswordPolicyService { } validatePassword(password: string): ValidationResult { - if (password.length < this.config.minLength) { + const trimmedPassword = password.trim(); + + if (trimmedPassword.length < this.config.minLength) { return { isValid: false, errorMessage: this.localizationService.translate('core_authentication_password_policy_min_length', undefined, { min: this.config.minLength }), }; } - if (this.config.requiresUpperLowerCase && !(/[a-z]/.test(password) && /[A-Z]/.test(password))) { + if (this.config.requireMixedCase && !(/[a-z]/.test(trimmedPassword) && /[A-Z]/.test(trimmedPassword))) { return { isValid: false, errorMessage: this.localizationService.translate('core_authentication_password_policy_upper_lower_case') }; } - if ((password.match(/\d/g) || []).length < this.config.minDigits) { + if ((trimmedPassword.match(/\d/g) || []).length < this.config.minNumberCount) { return { isValid: false, - errorMessage: this.localizationService.translate('core_authentication_password_policy_min_digits', undefined, { min: this.config.minDigits }), + errorMessage: this.localizationService.translate('core_authentication_password_policy_min_digits', undefined, { + min: this.config.minNumberCount, + }), }; } - if ((password.match(/[!@#$%^&*(),.?":{}|<>]/g) || []).length < this.config.minSpecialCharacters) { + if ((trimmedPassword.match(/[!@#$%^&*(),.?":{}|<>]/g) || []).length < this.config.minSymbolCount) { return { isValid: false, errorMessage: this.localizationService.translate('core_authentication_password_policy_min_special_characters', undefined, { - min: this.config.minSpecialCharacters, + min: this.config.minSymbolCount, }), }; } diff --git a/webapp/packages/core-root/src/__custom_mocks__/data/defaultServerConfig.ts b/webapp/packages/core-root/src/__custom_mocks__/data/defaultServerConfig.ts index b0b2839e0a..7ec67c0596 100644 --- a/webapp/packages/core-root/src/__custom_mocks__/data/defaultServerConfig.ts +++ b/webapp/packages/core-root/src/__custom_mocks__/data/defaultServerConfig.ts @@ -121,9 +121,9 @@ export const defaultServerConfig: (productConfiguration?: Record) = }, passwordPolicyConfiguration: { minLength: 8, - minDigits: 0, - minSpecialCharacters: 0, - requiresUpperLowerCase: false, + minNumberCount: 0, + minSymbolCount: 0, + requireMixedCase: false, }, }, }); diff --git a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql index a2d84cfb4c..b88cfc9032 100644 --- a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql +++ b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql @@ -54,9 +54,9 @@ query serverConfig { } passwordPolicyConfiguration { minLength - minDigits - minSpecialCharacters - requiresUpperLowerCase + minNumberCount + minSymbolCount + requireMixedCase } } } From 04637bacbbea9f73afcd16808d9708cad1cd50b7 Mon Sep 17 00:00:00 2001 From: naumov Date: Wed, 17 Jan 2024 18:10:55 +0100 Subject: [PATCH 10/10] CB-4264 accept any language --- .../packages/core-authentication/src/PasswordPolicyService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/core-authentication/src/PasswordPolicyService.ts b/webapp/packages/core-authentication/src/PasswordPolicyService.ts index dd81b87e96..4c19107734 100644 --- a/webapp/packages/core-authentication/src/PasswordPolicyService.ts +++ b/webapp/packages/core-authentication/src/PasswordPolicyService.ts @@ -48,7 +48,7 @@ export class PasswordPolicyService { }; } - if (this.config.requireMixedCase && !(/[a-z]/.test(trimmedPassword) && /[A-Z]/.test(trimmedPassword))) { + if (this.config.requireMixedCase && !(/\p{Ll}/u.test(trimmedPassword) && /\p{Lu}/u.test(trimmedPassword))) { return { isValid: false, errorMessage: this.localizationService.translate('core_authentication_password_policy_upper_lower_case') }; }