Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/CB-4264-password-policy' into CB…
Browse files Browse the repository at this point in the history
…-4264-password-policy
  • Loading branch information
yagudin10 committed Jan 16, 2024
2 parents 504b989 + 49dc68d commit 486d7b6
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 18 deletions.
73 changes: 73 additions & 0 deletions webapp/packages/core-authentication/src/PasswordPolicyService.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
}
2 changes: 2 additions & 0 deletions webapp/packages/core-authentication/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ export * from './UsersResource';
export * from './TeamMetaParametersResource';
export * from './EAdminPermission';
export * from './AUTH_SETTINGS_GROUP';
export * from './PasswordPolicyService';
export * from './usePasswordPolicy';
5 changes: 5 additions & 0 deletions webapp/packages/core-authentication/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
];
5 changes: 5 additions & 0 deletions webapp/packages/core-authentication/src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
];
5 changes: 5 additions & 0 deletions webapp/packages/core-authentication/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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} специальных символов'],
];
5 changes: 5 additions & 0 deletions webapp/packages/core-authentication/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
];
2 changes: 2 additions & 0 deletions webapp/packages/core-authentication/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -47,6 +48,7 @@ export const coreAuthenticationManifest: PluginManifest = {
UserConfigurationBootstrap,
AuthRolesResource,
TeamMetaParametersResource,
PasswordPolicyService,
LocaleService,
],
};
22 changes: 22 additions & 0 deletions webapp/packages/core-authentication/src/usePasswordPolicy.ts
Original file line number Diff line number Diff line change
@@ -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<string>(value => {
const validation = passwordPolicyService.validatePassword(value);
return validation.isValid ? null : validation.errorMessage;
});

return ref;
}
4 changes: 4 additions & 0 deletions webapp/packages/core-root/src/ServerConfigResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
return this.data?.resourceQuotas ?? {};
}

get passwordPolicy() {
return this.data?.passwordPolicyConfiguration ?? {};
}

get resourceManagerEnabled() {
return this.update.resourceManagerEnabled ?? this.data?.resourceManagerEnabled ?? false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,11 @@ export const defaultServerConfig: (productConfiguration?: Record<string, any>) =
releaseTime: 'July 11, 2022',
licenseInfo: '',
},
passwordPolicyConfiguration: {
minLength: 8,
minDigits: 0,
minSpecialCharacters: 0,
requiresUpperLowerCase: false,
},
},
});
6 changes: 6 additions & 0 deletions webapp/packages/core-sdk/src/queries/session/serverConfig.gql
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,11 @@ query serverConfig {
releaseTime
licenseInfo
}
passwordPolicyConfiguration {
minLength
minDigits
minSpecialCharacters
requiresUpperLowerCase
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class ServerConfigurationService {

const validation = contexts.getContext(serverConfigValidationContext);

return validation.getState();
return validation.valid;
}

private readonly loadServerConfig: IExecutorHandler<ILoadConfigData> = async (data, contexts) => {
Expand Down Expand Up @@ -200,7 +200,7 @@ export class ServerConfigurationService {
private readonly save: IExecutorHandler<IServerConfigSaveData> = async (data, contexts) => {
const validation = contexts.getContext(serverConfigValidationContext);

if (!validation.getState()) {
if (!validation.valid) {
return;
}

Expand All @@ -221,8 +221,18 @@ export class ServerConfigurationService {
private readonly ensureValidation: IExecutorHandler<IServerConfigSaveData> = (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;
Expand Down Expand Up @@ -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;
},
};
}

Expand Down
2 changes: 2 additions & 0 deletions webapp/packages/plugin-administration/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'],
Expand Down
2 changes: 2 additions & 0 deletions webapp/packages/plugin-administration/src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'],
Expand Down
2 changes: 2 additions & 0 deletions webapp/packages/plugin-administration/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', 'Позволить сохранять приватные данные для пользователей'],
Expand Down
2 changes: 2 additions & 0 deletions webapp/packages/plugin-administration/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', '差不多就这样。'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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();
}
Expand Down Expand Up @@ -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);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -33,6 +33,7 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
{ active: tabSelected && editing },
);
const authProvidersResource = useResource(UserFormInfoCredentials, AuthProvidersResource, null);
const passwordPolicyRef = usePasswordPolicy();

let local = authProvidersResource.resource.isEnabled(AUTH_PROVIDER_LOCAL_ID);

Expand All @@ -56,6 +57,7 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
{local && (
<>
<InputField
ref={passwordPolicyRef}
type="password"
name="password"
state={tabState.state}
Expand Down
Loading

0 comments on commit 486d7b6

Please sign in to comment.