Skip to content

Commit

Permalink
feat(config): add support for restrictionKeys (#2602)
Browse files Browse the repository at this point in the history
## Proposed change
As a developer I want to be able to flag a configuration property as
sensitive.
It was decided to have an array of restriction, named `restrictionKeys`
for each property, to support potential future use case with different
level of restriction based on the role of the user configuring the
property value.
This PR includes:
- the update of the metadata schema
- the extraction of `restrictionKeys` from the jsdoc tags
`@o3rRestrictionKeys`
- a linter to limit the possible keys used with `@o3rRestrictionKeys`

## Related issues

<!--
Please make sure to follow the [contribution
guidelines](https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md)
-->

*- No issue associated -*

<!-- * 🐛 Fix #issue -->
<!-- * 🐛 Fix resolves #issue -->
<!-- * 🚀 Feature #issue -->
<!-- * 🚀 Feature resolves #issue -->
<!-- * :octocat: Pull Request #issue -->
  • Loading branch information
matthieu-crouzet authored Dec 20, 2024
2 parents 88b7d37 + b0bc18b commit c8834dc
Show file tree
Hide file tree
Showing 16 changed files with 428 additions and 9 deletions.
6 changes: 6 additions & 0 deletions apps/showcase/eslint.local.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export default [
}
}
}
],
'@o3r/o3r-restriction-key-tags': [
'error',
{
supportedKeys: ['api owners']
}
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ConfigurationPresConfig extends Configuration {
* @o3rWidgetParam allDestinationsDifferent true
* @o3rWidgetParam atLeastOneDestinationAvailable true
* @o3rWidgetParam destinationPattern "[A-Z][a-zA-Z-' ]+"
* @o3rRestrictionKey "api owners"
*/
destinations: DestinationConfiguration[];
/**
Expand Down
5 changes: 3 additions & 2 deletions docs/ab-testing/AB_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ export class ExperimentFactsService extends FactsService<ExperimentFacts> {
}
}
```
**Warning** Your service must be imported only once in the application. A good way to do it is to provide it in root as
a singleton.
> [!WARNING]
> Your service must be imported only once in the application.
> A good way to do it is to provide it in root as a singleton.
#### The A/B Testing service
Now, you need to link ``ExperimentFactsService`` and the ``AbTestBridge`` to update the ``experiments`` fact that reflects the
Expand Down
65 changes: 65 additions & 0 deletions docs/linter/eslint-plugin/rules/o3r-restriction-key-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# @o3r/o3r-restriction-key-tags

Ensures that the tags `@o3rRestrictionKey` are used with correct values.

> [!WARNING]
> This rule must be configured to be used.
## How to use

```json
{
"@o3r/o3r-restriction-key-tags": [
"error",
{
"supportedInterfaceNames": ["NestedConfiguration", "Configuration", "CustomConfigurationInterface"],
"supportedKeys": ["restriction", "restriction_with_underscores", "restriction with spaces"]
}
]
}
```

## Valid code example

```typescript
export interface ConfigExample extends Configuration {
/**
* @o3rRestrictionKey restriction_with_underscores
*/
prop1: string;
/**
* @o3rRestrictionKey restriction
* @o3rRestrictionKey "restriction with spaces"
*/
prop2: string;
}
```

## Invalid code example

```typescript
export interface ConfigExample extends Configuration {
/**
* @o3rRestrictionKey restriction with spaces
*/
prop: string;
}
```

```typescript
export interface ConfigExample extends Configuration {
/**
* @o3rRestrictionKey unknownRestriction
*/
prop: string | number;
}
```

```typescript
export interface NotAConfigInterface {
/**
* @o3rRestrictionKey restriction
*/
prop: string;
}
```
6 changes: 3 additions & 3 deletions docs/linter/eslint-plugin/rules/o3r-widget-tags.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# @o3r/o3r-widget-tags

Ensures that @o3rWidget and @o3rWidgetParam are used with correct value.
Ensures that the tags @o3rWidget and @o3rWidgetParam are used with correct values.


**Warning** This rule must be configured to be used.
> [!WARNING]
> This rule must be configured to be used.
## How to use

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export class ComponentConfigExtractor {
const res: ConfigProperty = {
description: configDocInfo?.description || '',
category: configDocInfo?.category,
restrictionKeys: configDocInfo?.restrictionKeys,
label: configDocInfo?.label || name.replace(/([A-Z])/g, ' $1'),
name,
type: 'unknown',
Expand Down
2 changes: 2 additions & 0 deletions packages/@o3r/components/src/core/component.output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ export interface ConfigProperty {
widget?: ConfigPropertyWidget;
/** If true, the CMS user must specify a value for the property */
required?: boolean;
/** Restriction keys */
restrictionKeys?: string[];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@
"items": {
"type": "string"
}
},
"restrictionKeys": {
"type": "array",
"description": "Restriction keys to customize the user permissions",
"items": {
"type": "string"
}
}
},
"allOf": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = {
'jsdoc/check-tag-names': [
'warn',
{
definedTags: ['note', 'title', 'o3rCategory', 'o3rCategories', 'o3rWidget', 'o3rWidgetParam', 'o3rRequired']
definedTags: ['note', 'title', 'o3rRestrictionKey', 'o3rCategory', 'o3rCategories', 'o3rWidget', 'o3rWidgetParam', 'o3rRequired']
}
],
'jsdoc/check-types': 'warn',
Expand Down
2 changes: 1 addition & 1 deletion packages/@o3r/eslint-config/src/rules/typescript/jsdoc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const config = [
'jsdoc/check-tag-names': [
'error',
{
definedTags: ['note', 'title', 'o3rCategory', 'o3rCategories', 'o3rWidget', 'o3rWidgetParam', 'o3rRequired']
definedTags: ['note', 'title', 'o3rRestrictionKey', 'o3rCategory', 'o3rCategories', 'o3rWidget', 'o3rWidgetParam', 'o3rRequired']
}
],
'jsdoc/no-defaults': 'off',
Expand Down
4 changes: 3 additions & 1 deletion packages/@o3r/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import matchingConfigurationName from './rules/typescript/matching-configuration
import noFolderImportForModule from './rules/typescript/no-folder-import-for-module/no-folder-import-for-module';
import noMultipleTypeConfigurationProperty from './rules/typescript/no-multiple-type-configuration-property/no-multiple-type-configuration-property';
import o3rCategoriesTags from './rules/typescript/o3r-categories-tags/o3r-categories-tags';
import o3rRestrictionKeyTags from './rules/typescript/o3r-restriction-key-tags/o3r-restriction-key-tags';
import o3rWidgetTags from './rules/typescript/o3r-widget-tags/o3r-widget-tags';
import yarnrcPackageExtensionHarmonize from './rules/yaml/yarnrc-package-extensions-harmonize/yarnrc-package-extensions-harmonize';

Expand All @@ -18,7 +19,8 @@ module.exports = {
'matching-configuration-name': matchingConfigurationName,
'yarnrc-package-extensions-harmonize': yarnrcPackageExtensionHarmonize,
'no-multiple-type-configuration-property': noMultipleTypeConfigurationProperty,
'o3r-categories-tags': o3rCategoriesTags
'o3r-categories-tags': o3rCategoriesTags,
'o3r-restriction-key-tags': o3rRestrictionKeyTags
},
configs: {
'@o3r/no-folder-import-for-module': 'error',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import o3rRestrictionKeyRule, {
O3rRestrictionKeyTagsRuleOption,
} from './o3r-restriction-key-tags';
const {
RuleTester
} = require('@typescript-eslint/rule-tester');

const ruleTester = new RuleTester();

const code = `
export interface Config extends Configuration {
/**
* @o3rRestrictionKey valid
* @o3rRestrictionKey valid_with_underscore
* @o3rRestrictionKey 'valid with space (single quote)'
* @o3rRestrictionKey "valid with space (double quote)"
* @o3rRestrictionKey '"valid" with double quote inside'
* @o3rRestrictionKey "'valid' with single quote inside"
* @o3rRestrictionKey valid_with_number_1
*/
prop: string;
}
`;

const supportedKeys = [
'valid',
'valid_with_underscore',
'valid with space',
'valid with space (single quote)',
'valid with space (double quote)',
'"valid" with double quote inside',
"'valid' with single quote inside",
'valid_with_number_1'
];

const options = [{ supportedKeys }] as const satisfies Readonly<[O3rRestrictionKeyTagsRuleOption]>;

const unknownKeys = [
`unknown_restriction`,
`"invalid quote'`,
`'another invalid quote"`,
`'unknown with single quote'`,
`"unknown with double quote"`
];

const getCodeFor = (key: string) => `
export interface Config extends Configuration {
/**
* @o3rRestrictionKey ${key}
*/
prop: string;
}
`;

const getSuggestionFor = (actualKey: string) => supportedKeys.map((supportedKey) => ({
messageId: 'suggestUseSupportedKey',
data: {
actualKey,
supportedKey
},
output: getCodeFor(/[^\w]/.test(supportedKey) ? `"${supportedKey}"` : supportedKey)
}));

ruleTester.run('o3r-restriction-key-tags', o3rRestrictionKeyRule, {
valid: [
{
code,
options
},
{
code: `
export interface Config extends Configuration {
/**
* Property without restriction
*/
prop: string;
}`,
options
}
],
invalid: [
{
code: getCodeFor('"at least one key provided"'),
options: [{}],
errors: [
{ messageId: 'noRestrictionKeyProvided' }
]
},
{
code: code.replace(' extends Configuration', ''),
options,
errors: [
{
messageId: 'notInConfigurationInterface'
}
]
},
{
code: getCodeFor('valid with space'),
options,
output: getCodeFor(`"valid with space"`),
errors: [
{
messageId: 'notWrapWithQuotes',
data: {
actualKey: 'valid with space'
},
suggestions: [{
messageId: 'suggestWrapWithQuotes',
data: {
actualKey: 'valid with space'
},
output: getCodeFor(`"valid with space"`)
}]
}
]
},
...unknownKeys.map((key) => ({
code: getCodeFor(key),
options,
errors: [{
messageId: 'notSupportedKey',
data: {
actualKey: key,
supportedKeys: supportedKeys.join(', ')
},
suggestions: getSuggestionFor(key)
}]
}))
]
});
Loading

0 comments on commit c8834dc

Please sign in to comment.