Skip to content

Commit

Permalink
feat(cli): Use additional permissions with config-sync command (#2822)
Browse files Browse the repository at this point in the history
* feat(config): include additional permissions when using config-sync

* refactor(cli): validate additional oathScope

* refactor(cli): implement getPermissions function, update tests

* refactor(cli): move getPermission function to transformer.ts

* refactor(cli): add validation for additional permission name

* refactor(cli): show duplicated permission name in error thrown
  • Loading branch information
Rhotimee authored and emmenko committed Dec 9, 2022
1 parent fb9e6a9 commit 94511f8
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .changeset/dirty-queens-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@commercetools-frontend/application-config': patch
'@commercetools-frontend/application-shell': patch
---

Include additional permissions when creating and updating custom application
1 change: 1 addition & 0 deletions packages/application-config/src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,5 @@ function entryPointUriPathToPermissionKeys<PermissionGroupName extends string>(
export {
entryPointUriPathToResourceAccesses,
entryPointUriPathToPermissionKeys,
formatEntryPointUriPathToResourceAccessKey,
};
71 changes: 55 additions & 16 deletions packages/application-config/src/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { JSONSchemaForCustomApplicationConfigurationFiles } from './schema';
import type { CustomApplicationData } from './types';
import { entryPointUriPathToResourceAccesses } from './formatters';
import { validateEntryPointUriPath, validateSubmenuLinks } from './validations';
import {
entryPointUriPathToResourceAccesses,
formatEntryPointUriPathToResourceAccessKey,
} from './formatters';
import {
validateEntryPointUriPath,
validateSubmenuLinks,
validateAdditionalOAuthScopes,
} from './validations';

// The `uriPath` of each submenu link is supposed to be defined relative
// to the `entryPointUriPath`. Computing the full path is done internally to keep
Expand All @@ -18,32 +25,64 @@ const computeUriPath = (uriPath: string, entryPointUriPath: string) => {
return `${entryPointUriPath}/${uriPath}`;
};

const getPermissions = (
appConfig: JSONSchemaForCustomApplicationConfigurationFiles
) => {
const additionalResourceAccessKeyToOauthScopeMap = (
appConfig.additionalOAuthScopes || []
).reduce((previousOauthScope, { name, view, manage }) => {
const formattedResourceKey =
formatEntryPointUriPathToResourceAccessKey(name);
return {
...previousOauthScope,
[`view${formattedResourceKey}`]: view,
[`manage${formattedResourceKey}`]: manage,
};
}, {} as Record<string, string[]>);

const additionalPermissionNames =
appConfig.additionalOAuthScopes?.map(({ name }) => name) || [];

const permissionKeys = entryPointUriPathToResourceAccesses(
appConfig.entryPointUriPath,
additionalPermissionNames
) as Record<string, string>;

const additionalPermissions = Object.keys(
additionalResourceAccessKeyToOauthScopeMap
).map((additionalResourceAccessKey) => ({
name: permissionKeys[additionalResourceAccessKey],
oAuthScopes:
additionalResourceAccessKeyToOauthScopeMap[additionalResourceAccessKey],
}));

return [
{
name: permissionKeys.view,
oAuthScopes: appConfig.oAuthScopes.view,
},
{
name: permissionKeys.manage,
oAuthScopes: appConfig.oAuthScopes.manage,
},
...additionalPermissions,
];
};

function transformCustomApplicationConfigToData(
appConfig: JSONSchemaForCustomApplicationConfigurationFiles
): CustomApplicationData {
validateEntryPointUriPath(appConfig);
validateSubmenuLinks(appConfig);

const permissionKeys = entryPointUriPathToResourceAccesses(
appConfig.entryPointUriPath
);
validateAdditionalOAuthScopes(appConfig);

return {
id: appConfig.env.production.applicationId,
name: appConfig.name,
description: appConfig.description,
entryPointUriPath: appConfig.entryPointUriPath,
url: appConfig.env.production.url,
permissions: [
{
name: permissionKeys.view,
oAuthScopes: appConfig.oAuthScopes.view,
},
{
name: permissionKeys.manage,
oAuthScopes: appConfig.oAuthScopes.manage,
},
],
permissions: getPermissions(appConfig),
icon: appConfig.icon,
mainMenuLink: appConfig.mainMenuLink,
submenuLinks: appConfig.submenuLinks.map((submenuLink) => ({
Expand Down
19 changes: 19 additions & 0 deletions packages/application-config/src/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,22 @@ export const validateSubmenuLinks = (
uriPathSet.add(uriPath);
});
};

export const validateAdditionalOAuthScopes = (
config: JSONSchemaForCustomApplicationConfigurationFiles
) => {
const additionalPermissionNames = new Set();
config.additionalOAuthScopes?.forEach(({ name }) => {
if (additionalPermissionNames.has(name)) {
throw new Error(
`Duplicate additional permission "${name}". Every additional permission must have a unique name`
);
}
if (!name.match(ENTRY_POINT_URI_PATH_REGEX)) {
throw new Error(
`Additional permission name "${name}" is invalid. The value may be between 2 and 64 characters and only contain alphanumeric lowercase characters, non-consecutive underscores and hyphens. Leading and trailing underscore and hyphens are also not allowed`
);
}
additionalPermissionNames.add(name);
});
};
12 changes: 12 additions & 0 deletions packages/application-config/test/fixtures/config-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@
"view": ["view_products"],
"manage": []
},
"additionalOAuthScopes": [
{
"name": "movies",
"view": ["view_products"],
"manage": []
},
{
"name": "merch",
"view": ["view_channels"],
"manage": ["manage_channels"]
}
],
"headers": {
"csp": {
"script-src": ["https://track.avengers.app"],
Expand Down
39 changes: 39 additions & 0 deletions packages/application-config/test/json-schema.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
validateConfig,
validateEntryPointUriPath,
validateSubmenuLinks,
validateAdditionalOAuthScopes,
} from '../src/validations';
import fixtureConfigSimple from './fixtures/config-simple.json';
import fixtureConfigFull from './fixtures/config-full.json';
Expand Down Expand Up @@ -252,4 +253,42 @@ describe('invalid configurations', () => {
`"Duplicate URI path. Every submenu link must have a unique URI path value"`
);
});
it('should validate that additionalOauthScope name is unique', () => {
expect(() =>
validateAdditionalOAuthScopes({
...fixtureConfigSimple,
additionalOAuthScopes: [
{
name: 'movies',
view: ['view_products'],
manage: [],
},

{
name: 'movies',
view: ['view_channels'],
manage: [],
},
],
})
).toThrowErrorMatchingInlineSnapshot(
`"Duplicate additional permission \\"movies\\". Every additional permission must have a unique name"`
);
});
it('should validate the additional permission names matches the entry point regex', () => {
expect(() =>
validateAdditionalOAuthScopes({
...fixtureConfigSimple,
additionalOAuthScopes: [
{
name: '-movies',
view: ['view_products'],
manage: [],
},
],
})
).toThrowErrorMatchingInlineSnapshot(
`"Additional permission name \\"-movies\\" is invalid. The value may be between 2 and 64 characters and only contain alphanumeric lowercase characters, non-consecutive underscores and hyphens. Leading and trailing underscore and hyphens are also not allowed"`
);
});
});
110 changes: 78 additions & 32 deletions packages/application-config/test/process-config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,14 +305,12 @@ describe('processing a full config', () => {
entryPointUriPath: 'avengers',
url: 'https://avengers.app',
permissions: [
{
name: 'viewAvengers',
oAuthScopes: ['view_products'],
},
{
name: 'manageAvengers',
oAuthScopes: [],
},
{ name: 'viewAvengers', oAuthScopes: ['view_products'] },
{ name: 'manageAvengers', oAuthScopes: [] },
{ name: 'viewAvengersMovies', oAuthScopes: ['view_products'] },
{ name: 'manageAvengersMovies', oAuthScopes: [] },
{ name: 'viewAvengersMerch', oAuthScopes: ['view_channels'] },
{ name: 'manageAvengersMerch', oAuthScopes: ['manage_channels'] },
],
icon: '<svg><path fill="#000000" /></svg>',
mainMenuLink: {
Expand Down Expand Up @@ -350,6 +348,17 @@ describe('processing a full config', () => {
oAuthScopes: {
view: ['view_products'],
},
additionalOAuthScopes: [
{
name: 'movies',
view: ['view_products'],
},
{
name: 'merch',
view: ['view_channels'],
manage: ['manage_channels'],
},
],
},
},
},
Expand Down Expand Up @@ -389,14 +398,12 @@ describe('processing a full config', () => {
entryPointUriPath: 'avengers',
url: 'https://avengers.app',
permissions: [
{
name: 'viewAvengers',
oAuthScopes: ['view_products'],
},
{
name: 'manageAvengers',
oAuthScopes: [],
},
{ name: 'viewAvengers', oAuthScopes: ['view_products'] },
{ name: 'manageAvengers', oAuthScopes: [] },
{ name: 'viewAvengersMovies', oAuthScopes: ['view_products'] },
{ name: 'manageAvengersMovies', oAuthScopes: [] },
{ name: 'viewAvengersMerch', oAuthScopes: ['view_channels'] },
{ name: 'manageAvengersMerch', oAuthScopes: ['manage_channels'] },
],
icon: '<svg><path fill="#000000" /></svg>',
mainMenuLink: {
Expand Down Expand Up @@ -462,14 +469,12 @@ describe('processing a full config', () => {
entryPointUriPath: 'avengers',
url: 'https://avengers.app',
permissions: [
{
name: 'viewAvengers',
oAuthScopes: ['view_products'],
},
{
name: 'manageAvengers',
oAuthScopes: [],
},
{ name: 'viewAvengers', oAuthScopes: ['view_products'] },
{ name: 'manageAvengers', oAuthScopes: [] },
{ name: 'viewAvengersMovies', oAuthScopes: ['view_products'] },
{ name: 'manageAvengersMovies', oAuthScopes: [] },
{ name: 'viewAvengersMerch', oAuthScopes: ['view_channels'] },
{ name: 'manageAvengersMerch', oAuthScopes: ['manage_channels'] },
],
icon: '<svg><path fill="#000000" /></svg>',
mainMenuLink: {
Expand Down Expand Up @@ -507,6 +512,17 @@ describe('processing a full config', () => {
oAuthScopes: {
view: ['view_products'],
},
additionalOAuthScopes: [
{
name: 'movies',
view: ['view_products'],
},
{
name: 'merch',
view: ['view_channels'],
manage: ['manage_channels'],
},
],
},
},
},
Expand Down Expand Up @@ -548,14 +564,12 @@ describe('processing a full config', () => {
entryPointUriPath: 'avengers',
url: 'https://avengers.app',
permissions: [
{
name: 'viewAvengers',
oAuthScopes: ['view_products'],
},
{
name: 'manageAvengers',
oAuthScopes: [],
},
{ name: 'viewAvengers', oAuthScopes: ['view_products'] },
{ name: 'manageAvengers', oAuthScopes: [] },
{ name: 'viewAvengersMovies', oAuthScopes: ['view_products'] },
{ name: 'manageAvengersMovies', oAuthScopes: [] },
{ name: 'viewAvengersMerch', oAuthScopes: ['view_channels'] },
{ name: 'manageAvengersMerch', oAuthScopes: ['manage_channels'] },
],
icon: '<svg><path fill="#000000" /></svg>',
mainMenuLink: {
Expand Down Expand Up @@ -1111,6 +1125,22 @@ describe('processing a config with OIDC', () => {
name: 'manageAvengers',
oAuthScopes: ['manage_orders'],
},
{
name: 'viewAvengersMovies',
oAuthScopes: ['view_products'],
},
{
name: 'manageAvengersMovies',
oAuthScopes: [],
},
{
name: 'viewAvengersMerch',
oAuthScopes: [],
},
{
name: 'manageAvengersMerch',
oAuthScopes: ['manage_channels'],
},
],
icon: '<svg><path fill="#000000" /></svg>',
mainMenuLink: {
Expand Down Expand Up @@ -1197,6 +1227,22 @@ describe('processing a config with OIDC', () => {
name: 'manageAvengers',
oAuthScopes: ['manage_orders'],
},
{
name: 'viewAvengersMovies',
oAuthScopes: ['view_products'],
},
{
name: 'manageAvengersMovies',
oAuthScopes: [],
},
{
name: 'viewAvengersMerch',
oAuthScopes: [],
},
{
name: 'manageAvengersMerch',
oAuthScopes: ['manage_channels'],
},
],
icon: '<svg><path fill="#000000" /></svg>',
mainMenuLink: {
Expand Down
Loading

0 comments on commit 94511f8

Please sign in to comment.