Skip to content

feat(sap-features-hub): update password rules for sap and sap hana #17764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feat/sap-features-hub
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"system_title": "Précisez les détails de votre futur système SAP",
"system_subtitle": "Saisissez les détails de votre futur système SAP correspondant à vos besoins.",
"system_sid_validation_message": "Un SID doit comporter 3 caractères alphanumériques en majuscule et commence par une lettre.",
"system_password_validation_message": "Le mot de passe doit contenir au moins 8 caractères, incluant une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial parmi : !\"@ $%&/()=?'*+~#-.,;:{[]}\\<>│_",
"system_password_sap_validation_message": "Le mot de passe doit contenir entre 10 et 30 caractères, incluant une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial excepté \\ (antislash), ' (simple guillemet), \" (double guillemet), ` (backtick), $ (dollar), et ne doit pas comporter de caractères spéciaux successifs",
"system_password_sap_hana_validation_message": "Le mot de passe doit contenir entre 10 et 30 caractères, incluant une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial parmi _ (tiret du bas), # (dièse), @ (arobase), $ (dollar), ! (point d'exclamation), ne doit pas démarrer par un chiffre ou un tiret du bas (_), ni comporter de caractères spéciaux successifs",
"system_password_sap_master_helper": "Sera utilisé pour tous les utilisateurs SAP demandés par l'installeur (DDIC, sapadm, etc.)",
"system_cta": "Saisissez l'emplacement des sources SAP",
"source_summary": "Sources d'installation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
import { useInstallationFormContext } from '@/context/InstallationForm.context';
import { TextField } from '@/components/Form/TextField.component';
import { getSystemFormData } from '@/utils/formStepData';
import { isValidInput, isValidSapPassword } from '@/utils/formValidation';
import { isValidInput } from '@/utils/formValidation';
import {
isValidSapPassword,
isValidSapHanaPassword,
} from '@/utils/passwordValidation';
import FormLayout from '@/components/Form/FormLayout.component';
import { FORM_LABELS } from '@/constants/form.constants';
import { TRACKING } from '@/tracking.constants';
Expand Down Expand Up @@ -39,7 +43,6 @@ export default function InstallationStepSystemInformation() {
);

const sidRule = t('system_sid_validation_message');
const pwdRule = t('system_password_validation_message');

return (
<FormLayout
Expand Down Expand Up @@ -71,23 +74,32 @@ export default function InstallationStepSystemInformation() {
/>
))}
<OdsText preset="heading-3">{t('passwords')}</OdsText>
<OdsText className="italic">{pwdRule}</OdsText>
{SYSTEM_PASSWORD_INPUTS.map(({ name, helperKey, ...inputProps }) => (
<TextField
key={name}
type="password"
name={name}
onOdsChange={(e) => {
const isValid = isValidSapPassword(e.detail.value as string);
setValues((val) => ({ ...val, [name]: e.detail.value }));
setErrors((err) => ({ ...err, [name]: isValid ? '' : pwdRule }));
}}
value={values[name]}
error={values[name] && errors[name]}
helperText={t(helperKey)}
{...inputProps}
/>
))}
{SYSTEM_PASSWORD_INPUTS.map(
({ name, helperKey, passwordType, ...inputProps }) => (
<TextField
key={name}
type="password"
name={name}
onOdsChange={(e) => {
const isValid =
passwordType === 'sapHana'
? isValidSapHanaPassword(e.detail.value as string)
: isValidSapPassword(e.detail.value as string);
const rule =
passwordType === 'sapHana'
? t('system_password_sap_hana_validation_message')
: t('system_password_sap_validation_message');

setValues((val) => ({ ...val, [name]: e.detail.value }));
setErrors((err) => ({ ...err, [name]: isValid ? '' : rule }));
}}
value={values[name]}
error={values[name] && errors[name]}
helperText={t(helperKey)}
{...inputProps}
/>
),
)}
</FormLayout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { SystemForm, TextInputData } from '@/types/form.type';
import { FORM_LABELS } from '@/constants/form.constants';
import { LABELS } from '@/utils/label.constants';

type SapHanaPasswordInputs = Array<
TextInputData<keyof SystemForm> & {
passwordType: 'sap' | 'sapHana';
}
>;

// A SAP SID consists of three uppercase alphanumeric characters and starts with a letter.
const SAP_SID_PATTERN = /^[A-Z][A-Z0-9]{2}$/.source;

Expand All @@ -18,26 +24,30 @@ export const SYSTEM_TEXT_INPUTS: TextInputData<keyof SystemForm>[] = [
},
] as const;

export const SYSTEM_PASSWORD_INPUTS: TextInputData<keyof SystemForm>[] = [
export const SYSTEM_PASSWORD_INPUTS: SapHanaPasswordInputs = [
{
name: 'masterSapPassword',
label: FORM_LABELS.masterSapPassword,
helperKey: 'system_password_sap_master_helper',
validator: { isRequired: true },
passwordType: 'sap',
},
{
name: 'masterSapHanaPassword',
label: FORM_LABELS.masterSapHanaPassword,
validator: { isRequired: true },
passwordType: 'sapHana',
},
{
name: 'sidadmPassword',
label: FORM_LABELS.sidadmPassword,
validator: { isRequired: true },
passwordType: 'sap',
},
{
name: 'systemPassword',
label: FORM_LABELS.systemPassword,
validator: { isRequired: true },
passwordType: 'sapHana',
},
] as const;
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { describe, expect, test } from 'vitest';
import { OdsInputChangeEvent } from '@ovhcloud/ods-components';
import {
isValidDomain,
isValidInput,
isValidSapPassword,
isValidUrl,
} from './formValidation';
import { isValidDomain, isValidInput, isValidUrl } from './formValidation';

describe('isValid test suite', () => {
it('should return true if input is valid', () => {
Expand Down Expand Up @@ -58,21 +53,3 @@ describe('isValidDomain test suite', () => {
expect(isValidDomain(input)).toBe(expected);
});
});

describe('isValidSapPassword test suite', () => {
test.each([
['Password1!', true],
["Pa1!@$%&/()=? '*+~#-.,;:{[]}<>_|", true],
['Short1!', false],
['NOLOWER1!', false],
['noupper1!', false],
['Nonumber!', false],
['Nospecial1', false],
['Invalid1char€', false],
])(
'should evaluate validity of SAP password %s as: %s',
(input, expected) => {
expect(isValidSapPassword(input)).toBe(expected);
},
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,3 @@ export const isValidDomain = (value: string) => {
value,
);
};

export const isValidSapPassword = (password: string): boolean => {
if (password?.length < 8) return false;
if (!/[a-z]/.test(password)) return false;
if (!/[A-Z]/.test(password)) return false;
if (!/\d/.test(password)) return false;
if (!/[!" @$%&/()=?'*+~#\-.,;:{[\]}<>_|]/.test(password)) return false;
if (!/^[A-Za-z\d!" @$%&/()=?'*+~#\-.,;:{[\]}<>_|]*$/.test(password)) {
return false;
}
return true;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, test, expect } from 'vitest';
import {
isValidSapPassword,
isValidSapHanaPassword,
} from './passwordValidation';

describe('common sapPassword & sapHanaPassword requirements', () => {
test.each([
['Short123!', false],
['WITHOUTLOWERCASE1!', false],
['withoutuppercase1!', false],
['withoutnumber!', false],
['With2ConsecutiveSpecialCharacters!?', false],
])('should be invalid for both SAP password types: %s', (input) => {
expect(isValidSapPassword(input)).toBe(false);
expect(isValidSapHanaPassword(input)).toBe(false);
});
});

describe('isValidSapPassword test suite', () => {
test.each([
['Password1WithoutSpecialCharacter', false],
['Password1\\', false],
["Password1'", false],
['Password1"', false],
['Password1`', false],
['Password1$', false],

['Password1!', true],
['1PasswordStartingWithDigit_', true],
['_1PasswordStartingWithUnderscore', true],
])(
'should evaluate validity of SAP password %s as: %s',
(input, expected) => {
expect(isValidSapPassword(input)).toBe(expected);
},
);
});

describe('isValidSapHanaPassword test suite', () => {
test.each([
['1PasswordStartingWithDigit_', false],
['_1PasswordStartingWithUnderscore', false],
['Password1WithoutRequiredSpecialChar,?;.:/=+(){}[]&"\'`§°-€£%', false],

['Password1!', true],
['Password1_', true],
['Password1#', true],
['Password1@', true],
['Password1$', true],
])(
'should evaluate validity of SAP_HANA password %s as: %s',
(input, expected) => {
expect(isValidSapHanaPassword(input)).toBe(expected);
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
type Rule = (password: string) => boolean;

export const sapPasswordRules = {
hasMinLength: (pwd: string) => pwd?.length >= 10,
hasLowerCase: (pwd: string) => /[a-z]/.test(pwd),
hasUpperCase: (pwd: string) => /[A-Z]/.test(pwd),
hasDigit: (pwd: string) => /\d/.test(pwd),
hasSpecialChar: (pwd: string) => /[^A-Za-z0-9]/.test(pwd),
doesNotStartWithDigitOrUnderscore: (pwd: string) => !/^[0-9_]/.test(pwd),
doesNotHaveConsecutiveSpecialChars: (pwd: string) =>
!/[^A-Za-z0-9]{2,}/.test(pwd),
doesNotHaveSapForbiddenChar: (pwd: string) => !/[\\'"`$]/.test(pwd),
hasRequiredSapHanaSpecialChar: (pwd: string) => /[_#@$!]/.test(pwd),
okok: 'true',
};

const baseRules: Rule[] = [
sapPasswordRules.hasMinLength,
sapPasswordRules.hasLowerCase,
sapPasswordRules.hasUpperCase,
sapPasswordRules.hasDigit,
sapPasswordRules.doesNotHaveConsecutiveSpecialChars,
];
const sapRules: Rule[] = [
...baseRules,
sapPasswordRules.hasSpecialChar,
sapPasswordRules.doesNotHaveSapForbiddenChar,
];
const sapHanaRules: Rule[] = [
...baseRules,
sapPasswordRules.hasRequiredSapHanaSpecialChar,
sapPasswordRules.doesNotStartWithDigitOrUnderscore,
];

const validatePassword = (password: string, rules: Rule[]): boolean =>
rules.every((rule) => rule(password));

export const isValidSapPassword = (password: string): boolean =>
validatePassword(password, sapRules);

export const isValidSapHanaPassword = (password: string): boolean =>
validatePassword(password, sapHanaRules);
Loading