* refactor: 💡 initial refactor for utils to share components * refactor: 💡 actions logic * Add missing copyright headers * refactor: 💡 simplify actions getter logic * refactor: 💡 convert output fields logic into a separate getter * test: 💍 fix unit test * fix: 🐛 bug with unknown fields lint error * refactor: 💡 pr feedback - simplify template check * refactor: 💡 remove unnecessary unit test file
280 lines
7.6 KiB
JavaScript
280 lines
7.6 KiB
JavaScript
/**
|
|
* Copyright IBM Corp. 2021, 2026
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
export const GRANT_FIELDS = ['ids', 'type', 'actions', 'output_fields'];
|
|
export const TEMPLATE_RESOURCE_TYPES = {
|
|
'{{.User.Id}}': 'user',
|
|
'{{.Account.Id}}': 'account',
|
|
'{{user.id}}': 'user',
|
|
'{{account.id}}': 'account',
|
|
};
|
|
|
|
export const normalizeGrantsSchema = (grantsSchema) => {
|
|
const resourceTypes = grantsSchema?.resource_types ?? [];
|
|
|
|
const resourcesByType = resourceTypes.reduce((resources, resourceType) => {
|
|
const collectionActions = resourceType.collection_actions ?? [];
|
|
const idActions = resourceType.id_actions ?? [];
|
|
const idPrefixes = resourceType.id_prefixes ?? [];
|
|
|
|
resources[resourceType.type] = {
|
|
actions: [...collectionActions, ...idActions],
|
|
collectionActions,
|
|
idActions,
|
|
idPrefixes,
|
|
parentType: resourceType.parent_type,
|
|
outputFields: resourceType.output_fields ?? [],
|
|
};
|
|
|
|
return resources;
|
|
}, {});
|
|
|
|
const validResourceTypes = resourceTypes.map((resource) => resource.type);
|
|
validResourceTypes.push('*');
|
|
|
|
const allActions = new Set();
|
|
Object.values(resourcesByType).forEach((resource) => {
|
|
resource.actions.forEach((action) => allActions.add(action));
|
|
});
|
|
const validActions = Array.from(allActions);
|
|
|
|
const resourceTypesByIdPrefix = resourceTypes.reduce((map, resource) => {
|
|
resource.id_prefixes?.forEach((prefix) => {
|
|
map[prefix] = resource.type;
|
|
});
|
|
return map;
|
|
}, {});
|
|
|
|
let parentTypes = new Set(
|
|
resourceTypes.filter((r) => r.parent_type).map((r) => r.parent_type),
|
|
);
|
|
parentTypes = Array.from(parentTypes);
|
|
|
|
const topLevelTypes = resourceTypes
|
|
.filter((r) => {
|
|
const hasCreateAction = r.collection_actions.includes('create');
|
|
const hasListAction = r.collection_actions.includes('list');
|
|
return !r.parent_type && (hasCreateAction || hasListAction);
|
|
})
|
|
.map((r) => r.type);
|
|
|
|
const childResourceTypesByParentType = resourceTypes.reduce(
|
|
(childrenByParentType, resourceType) => {
|
|
const parentType = resourceType.parent_type;
|
|
|
|
if (!parentType) {
|
|
return childrenByParentType;
|
|
}
|
|
|
|
childrenByParentType[parentType] = [
|
|
...(childrenByParentType[parentType] ?? []),
|
|
resourceType.type,
|
|
];
|
|
|
|
return childrenByParentType;
|
|
},
|
|
{},
|
|
);
|
|
|
|
return {
|
|
resourcesByType,
|
|
resourceTypesByIdPrefix,
|
|
childResourceTypesByParentType,
|
|
parentTypes,
|
|
topLevelTypes,
|
|
validResourceTypes,
|
|
validActions,
|
|
};
|
|
};
|
|
|
|
export const parseGrantLine = (lineText = '', lineStart = 0) => {
|
|
const pairs = lineText.split(';').filter((p) => p.trim());
|
|
|
|
const fields = [];
|
|
const fieldMap = {};
|
|
const fieldPositions = {};
|
|
const fieldKeyCount = {};
|
|
|
|
let pairOffset = 0;
|
|
|
|
for (const pair of pairs) {
|
|
const pairInLine = lineText.indexOf(pair, pairOffset);
|
|
const [key, ...valueParts] = pair.split('=');
|
|
const value = valueParts.join('=');
|
|
|
|
const trimmedKey = key.trim();
|
|
const keyStartInPair = pair.indexOf(trimmedKey);
|
|
const valueStartInPair = pair.indexOf('=') + 1;
|
|
|
|
const pos = {
|
|
keyStart: lineStart + pairInLine + keyStartInPair,
|
|
valueStart: lineStart + pairInLine + valueStartInPair,
|
|
valueEnd: lineStart + pairInLine + pair.length,
|
|
};
|
|
|
|
const hasValue = valueParts.length > 0;
|
|
|
|
fields.push({
|
|
key: trimmedKey,
|
|
value: hasValue ? value.trim() : undefined,
|
|
pos,
|
|
});
|
|
fieldKeyCount[trimmedKey] = (fieldKeyCount[trimmedKey] ?? 0) + 1;
|
|
|
|
if (hasValue && trimmedKey) {
|
|
fieldPositions[trimmedKey] = pos;
|
|
const trimmedValue = value.trim();
|
|
if (trimmedValue) {
|
|
fieldMap[trimmedKey] = trimmedValue;
|
|
}
|
|
}
|
|
|
|
pairOffset = pairInLine + pair.length + 1;
|
|
}
|
|
|
|
return { fields, fieldMap, fieldPositions, fieldKeyCount };
|
|
};
|
|
|
|
const getChildResourceValues = (schema, compatibleType, getValues) => {
|
|
if (
|
|
!compatibleType ||
|
|
!schema.childResourceTypesByParentType[compatibleType]?.length
|
|
) {
|
|
return [];
|
|
}
|
|
|
|
const childTypes =
|
|
schema.childResourceTypesByParentType[compatibleType] ?? [];
|
|
|
|
return [
|
|
...new Set(
|
|
childTypes.flatMap((type) => getValues(schema.resourcesByType[type])),
|
|
),
|
|
];
|
|
};
|
|
|
|
const getChildResourceActions = (schema, compatibleType) =>
|
|
getChildResourceValues(
|
|
schema,
|
|
compatibleType,
|
|
(resource) => resource?.actions ?? [],
|
|
);
|
|
|
|
const getChildResourceOutputFields = (schema, compatibleType) =>
|
|
getChildResourceValues(
|
|
schema,
|
|
compatibleType,
|
|
(resource) => resource?.outputFields ?? [],
|
|
);
|
|
|
|
const withWildCard = (values = []) => ['*', ...new Set(values)];
|
|
|
|
export const getValidActions = (
|
|
schema,
|
|
{ typeValue, idsValue, idsWildcard, idsKnownType },
|
|
) => {
|
|
const typeWildcard = typeValue === '*';
|
|
const hasExplicitType = Boolean(typeValue) && !typeWildcard;
|
|
|
|
if (idsValue) {
|
|
// No type, ids resolve to a known type → that type's id actions.
|
|
if (!typeValue && idsKnownType) {
|
|
return {
|
|
actionsType: 'id',
|
|
actions: withWildCard(
|
|
schema.resourcesByType[idsKnownType]?.idActions ?? [],
|
|
),
|
|
};
|
|
}
|
|
|
|
// Wildcard type, known id type → child resource actions.
|
|
if (typeWildcard && idsKnownType) {
|
|
return {
|
|
actionsType: 'child',
|
|
actions: withWildCard(getChildResourceActions(schema, idsKnownType)),
|
|
};
|
|
}
|
|
|
|
// Explicit type → validate parent relationship then return type actions.
|
|
if (hasExplicitType) {
|
|
if (idsKnownType) {
|
|
const selectedResource = schema.resourcesByType[typeValue];
|
|
if (selectedResource?.parentType !== idsKnownType) {
|
|
return { actionsType: 'invalid', actions: [] };
|
|
}
|
|
}
|
|
return {
|
|
actionsType: 'type',
|
|
actions: withWildCard(schema.resourcesByType[typeValue]?.actions ?? []),
|
|
};
|
|
}
|
|
|
|
// Wildcard ids with no explicit type → all actions.
|
|
if (idsWildcard && !hasExplicitType) {
|
|
return { actionsType: 'all', actions: withWildCard(schema.validActions) };
|
|
}
|
|
} else {
|
|
// No ids and no type → all actions.
|
|
if (!typeValue) {
|
|
return { actionsType: 'all', actions: withWildCard(schema.validActions) };
|
|
}
|
|
|
|
// No ids, explicit top-level type → collection actions.
|
|
if (hasExplicitType && schema.topLevelTypes.includes(typeValue)) {
|
|
return {
|
|
actionsType: 'collection',
|
|
actions: schema.resourcesByType[typeValue]?.collectionActions ?? [],
|
|
};
|
|
}
|
|
}
|
|
|
|
return { actionsType: 'invalid', actions: [] };
|
|
};
|
|
|
|
export const getValidOutputFields = (schema, { typeValue, idsKnownType }) => {
|
|
const typeWildcard = typeValue === '*';
|
|
const hasExplicitType = Boolean(typeValue) && !typeWildcard;
|
|
|
|
if (hasExplicitType) {
|
|
const resourceOutputFields =
|
|
schema.resourcesByType[typeValue]?.outputFields ?? [];
|
|
return {
|
|
outputFieldsType: 'type',
|
|
outputFields: resourceOutputFields.length
|
|
? withWildCard(resourceOutputFields)
|
|
: ['*'],
|
|
resolvedType: typeValue,
|
|
};
|
|
}
|
|
|
|
if (typeWildcard && idsKnownType) {
|
|
const childOutputFields = getChildResourceOutputFields(
|
|
schema,
|
|
idsKnownType,
|
|
);
|
|
return {
|
|
outputFieldsType: 'child',
|
|
outputFields: childOutputFields.length
|
|
? withWildCard(childOutputFields)
|
|
: ['*'],
|
|
resolvedType: null,
|
|
};
|
|
}
|
|
|
|
if (!typeValue && idsKnownType) {
|
|
const resourceOutputFields =
|
|
schema.resourcesByType[idsKnownType]?.outputFields ?? [];
|
|
return {
|
|
outputFieldsType: 'type',
|
|
outputFields: resourceOutputFields.length
|
|
? withWildCard(resourceOutputFields)
|
|
: ['*'],
|
|
resolvedType: idsKnownType,
|
|
};
|
|
}
|
|
|
|
return { outputFieldsType: 'all', outputFields: ['*'], resolvedType: null };
|
|
};
|