Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -21748,8 +21748,6 @@
"xpack.fleet.multiTextInput.deleteRowButton": "Löschen Sie \"{fieldLabel}\" Eingang {index}",
"xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "{type} enthält ungültige Zeichen",
"xpack.fleet.namespaceValidation.lowercaseErrorMessage": "{type} muss kleingeschrieben werden",
"xpack.fleet.namespaceValidation.notAllowedPrefixError": "Namespace sollte mit {allowedNamespacePrefixes}beginnen",
"xpack.fleet.namespaceValidation.notAllowedPrefixesError": "Der Namespace sollte mit einem dieser Präfixe beginnen: {allowedNamespacePrefixes}",
"xpack.fleet.namespaceValidation.requiredErrorMessage": "{type} ist erforderlich",
"xpack.fleet.namespaceValidation.tooLongErrorMessage": "{type} darf nicht mehr als 100 Byte umfassen",
"xpack.fleet.newEnrollmentKey.cancelButtonLabel": "Abbrechen",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21695,8 +21695,6 @@
"xpack.fleet.multiTextInput.deleteRowButton": "Supprimer l'entrée \"{fieldLabel}\" {index}",
"xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "{type} contient des caractères non valides",
"xpack.fleet.namespaceValidation.lowercaseErrorMessage": "{type} doit être en minuscules",
"xpack.fleet.namespaceValidation.notAllowedPrefixError": "Un espace de nom doit commencer par {allowedNamespacePrefixes}",
"xpack.fleet.namespaceValidation.notAllowedPrefixesError": "Un espace de nom doit commencer par l'un des préfixes suivants : {allowedNamespacePrefixes}",
"xpack.fleet.namespaceValidation.requiredErrorMessage": "{type} est requis",
"xpack.fleet.namespaceValidation.tooLongErrorMessage": "{type} ne peut pas dépasser 100 octets",
"xpack.fleet.newEnrollmentKey.cancelButtonLabel": "Annuler",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21818,8 +21818,6 @@
"xpack.fleet.multiTextInput.deleteRowButton": "\"{fieldLabel}\"入力{index}を削除",
"xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "{type}に無効な文字が含まれています",
"xpack.fleet.namespaceValidation.lowercaseErrorMessage": "{type}は小文字で指定する必要があります",
"xpack.fleet.namespaceValidation.notAllowedPrefixError": "名前空間の先頭は{allowedNamespacePrefixes}にしてください",
"xpack.fleet.namespaceValidation.notAllowedPrefixesError": "名前空間の先頭は{allowedNamespacePrefixes}のプレフィックスのいずれかにしてください",
"xpack.fleet.namespaceValidation.requiredErrorMessage": "{type}が必要です",
"xpack.fleet.namespaceValidation.tooLongErrorMessage": "{type}は100バイト以下でなければなりません",
"xpack.fleet.newEnrollmentKey.cancelButtonLabel": "キャンセル",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21821,8 +21821,6 @@
"xpack.fleet.multiTextInput.deleteRowButton": "删除“{fieldLabel}”输入 {index}",
"xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "{type} 包含无效字符",
"xpack.fleet.namespaceValidation.lowercaseErrorMessage": "{type} 必须为小写",
"xpack.fleet.namespaceValidation.notAllowedPrefixError": "命名空间应以 {allowedNamespacePrefixes} 开头",
"xpack.fleet.namespaceValidation.notAllowedPrefixesError": "命名空间应以这些前缀之一开头:{allowedNamespacePrefixes}",
"xpack.fleet.namespaceValidation.requiredErrorMessage": "{type} 必填",
"xpack.fleet.namespaceValidation.tooLongErrorMessage": "{type} 不能超过 100 个字节",
"xpack.fleet.newEnrollmentKey.cancelButtonLabel": "取消",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ export * from './cloud_connectors';

export { validateSslCertPath } from './ssl_validators';

export { isNamespaceAllowedByPrefixes } from './namespace_prefixes';

export type { YamlModule } from './yaml_utils';
export { createYamlKeysSorter, toYaml } from './yaml_utils';
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@ describe('Fleet - isValidNamespace', () => {
).valid
).toBe(false);
expect(isValidNamespace('default', false, ['test']).valid).toBe(false);
expect(isValidNamespace('default', false, ['prod', 'qa']).valid).toBe(false);
});

it('accepts a namespace matching any of multiple allowed prefixes', () => {
expect(isValidNamespace('prod', false, ['prod', 'qa']).valid).toBe(true);
expect(isValidNamespace('qa', false, ['prod', 'qa']).valid).toBe(true);
expect(isValidNamespace('prodenv', false, ['prod', 'qa']).valid).toBe(true);
expect(isValidNamespace('qaenv', false, ['prod', 'qa']).valid).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,25 @@ export function isValidNamespace(
return { valid, error };
}

for (const prefix of allowedNamespacePrefixes || []) {
if (!namespace.trim().startsWith(prefix)) {
return allowedNamespacePrefixes?.length === 1
? {
valid: false,
error: i18n.translate('xpack.fleet.namespaceValidation.notAllowedPrefixError', {
defaultMessage: 'Namespace should start with {allowedNamespacePrefixes}',
values: {
allowedNamespacePrefixes: allowedNamespacePrefixes?.[0],
},
}),
}
: {
valid: false,
error: i18n.translate('xpack.fleet.namespaceValidation.notAllowedPrefixesError', {
defaultMessage:
'Namespace should start with one of these prefixes {allowedNamespacePrefixes}',
values: {
allowedNamespacePrefixes: allowedNamespacePrefixes?.join(', ') ?? '',
},
}),
};
if (allowedNamespacePrefixes && allowedNamespacePrefixes.length > 0) {
const matchesAnyPrefix = allowedNamespacePrefixes.some((prefix) =>
namespace.trim().startsWith(prefix)
);
if (!matchesAnyPrefix) {
return {
valid: false,
error: i18n.translate('xpack.fleet.namespaceValidation.notAllowedPrefixError', {
defaultMessage:
'Namespace should start with {count, plural, one {{allowedNamespacePrefixes}} other {one of these prefixes: {allowedNamespacePrefixes}}}',
values: {
count: allowedNamespacePrefixes.length,
allowedNamespacePrefixes:
allowedNamespacePrefixes.length === 1
? allowedNamespacePrefixes[0]
: allowedNamespacePrefixes.join(', '),
},
}),
};
}
}
return { valid: true };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { isNamespaceAllowedByPrefixes } from './namespace_prefixes';

describe('isNamespaceAllowedByPrefixes', () => {
it('returns true when prefixes are null (no restriction)', () => {
expect(isNamespaceAllowedByPrefixes('production', null)).toBe(true);
expect(isNamespaceAllowedByPrefixes('', null)).toBe(true);
});

it('returns true when namespace starts with one of the allowed prefixes', () => {
expect(isNamespaceAllowedByPrefixes('production_west', ['production', 'staging'])).toBe(true);
expect(isNamespaceAllowedByPrefixes('staging', ['production', 'staging'])).toBe(true);
});

it('returns false when namespace does not match any allowed prefix', () => {
expect(isNamespaceAllowedByPrefixes('dev', ['production', 'staging'])).toBe(false);
});

it('returns false when prefix list is empty', () => {
// Empty array means restriction is in effect but nothing is allowed.
expect(isNamespaceAllowedByPrefixes('anything', [])).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

/**
* Returns true if the namespace is permitted by the given prefix list.
* `null` means no restriction (anything is permitted).
*/
export function isNamespaceAllowedByPrefixes(
namespace: string,
prefixes: string[] | null
): boolean {
if (prefixes === null) {
return true;
}
return prefixes.some((prefix) => namespace.startsWith(prefix));
}
Loading
Loading