isc-transforms-mcp 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "sailpoint.isc.transforms.conditional.schema.json",
|
|
4
4
|
"title": "SailPoint ISC Transform Schema - conditional",
|
|
5
|
-
"description": "Strict schema
|
|
5
|
+
"description": "Strict schema for the SailPoint ISC Conditional transform. Evaluates a 'ValueA eq ValueB' expression and returns positiveCondition (true) or negativeCondition (false). Only 'eq' is supported — other operators throw IllegalArgumentException at runtime. Comparisons are case-sensitive. Operands cannot be null at runtime.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
@@ -12,31 +12,22 @@
|
|
|
12
12
|
],
|
|
13
13
|
"properties": {
|
|
14
14
|
"type": {
|
|
15
|
-
"const": "conditional"
|
|
15
|
+
"const": "conditional",
|
|
16
|
+
"description": "Transform operation type. Must be exactly 'conditional'."
|
|
16
17
|
},
|
|
17
18
|
"name": {
|
|
18
19
|
"type": "string",
|
|
19
|
-
"minLength": 1
|
|
20
|
+
"minLength": 1,
|
|
21
|
+
"description": "Display name for this transform, shown in UI dropdowns and identity profile mappings."
|
|
20
22
|
},
|
|
21
23
|
"requiresPeriodicRefresh": {
|
|
22
24
|
"type": "boolean",
|
|
23
25
|
"default": false,
|
|
24
|
-
"description": "
|
|
26
|
+
"description": "If true, re-evaluates this transform during the nightly identity refresh cycle. Default is false."
|
|
25
27
|
},
|
|
26
28
|
"attributes": {
|
|
27
29
|
"type": "object",
|
|
28
|
-
"description": "Conditional logic configuration,
|
|
29
|
-
"additionalProperties": {
|
|
30
|
-
"description": "Dynamic variables referenced via $variableName. Can be string literals or nested transforms.",
|
|
31
|
-
"anyOf": [
|
|
32
|
-
{
|
|
33
|
-
"type": "string"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"$ref": "#/$defs/NestedTransform"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
},
|
|
30
|
+
"description": "Conditional logic configuration. Required: expression, positiveCondition, negativeCondition. Optional: dynamic variable keys (any additional key) whose values are static strings or nested transforms, referenced via $variableName in the expression and conditions.",
|
|
40
31
|
"required": [
|
|
41
32
|
"expression",
|
|
42
33
|
"positiveCondition",
|
|
@@ -46,26 +37,36 @@
|
|
|
46
37
|
"expression": {
|
|
47
38
|
"type": "string",
|
|
48
39
|
"minLength": 1,
|
|
49
|
-
"
|
|
50
|
-
"
|
|
40
|
+
"pattern": "^.+\\s+eq\\s+.+$",
|
|
41
|
+
"description": "Equality expression of the form '<ValueA> eq <ValueB>'. RULES: (1) Only 'eq' operator is supported — !=, ==, >, <, ne, gt, lt etc. throw IllegalArgumentException. (2) Comparisons are case-sensitive: 'Engineering' != 'engineering'. (3) Operands cannot be null at runtime. (4) Variables are referenced with $variableName syntax and must be declared as keys in attributes."
|
|
51
42
|
},
|
|
52
43
|
"positiveCondition": {
|
|
53
44
|
"type": "string",
|
|
54
|
-
"
|
|
55
|
-
"description": "Output when expression evaluates to true. May reference a dynamic variable via $var."
|
|
45
|
+
"description": "Value returned when expression evaluates to true. Can be a static string (e.g., 'Active') or a $variableName reference to a declared variable in attributes. Can be empty string to return a blank value."
|
|
56
46
|
},
|
|
57
47
|
"negativeCondition": {
|
|
58
48
|
"type": "string",
|
|
59
|
-
"
|
|
60
|
-
"description": "Output when expression evaluates to false. May reference a dynamic variable via $var."
|
|
49
|
+
"description": "Value returned when expression evaluates to false. Can be a static string (e.g., 'Inactive') or a $variableName reference to a declared variable in attributes. Can be empty string to return a blank value."
|
|
61
50
|
}
|
|
51
|
+
},
|
|
52
|
+
"additionalProperties": {
|
|
53
|
+
"description": "Dynamic variable declaration. Key becomes the variable name, referenced as $keyName in expression, positiveCondition, or negativeCondition. Value must be a static string or a nested transform object.",
|
|
54
|
+
"anyOf": [
|
|
55
|
+
{
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Static string value for this variable."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"$ref": "#/$defs/NestedTransform"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
},
|
|
65
66
|
"$defs": {
|
|
66
67
|
"NestedTransform": {
|
|
67
68
|
"type": "object",
|
|
68
|
-
"description": "Nested transform object used as a dynamic variable.
|
|
69
|
+
"description": "Nested transform object used as a dynamic variable. The 'name' field is optional in nested transforms per SailPoint docs examples.",
|
|
69
70
|
"additionalProperties": false,
|
|
70
71
|
"required": [
|
|
71
72
|
"type",
|
|
@@ -73,18 +74,22 @@
|
|
|
73
74
|
],
|
|
74
75
|
"properties": {
|
|
75
76
|
"id": {
|
|
76
|
-
"type": "string"
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "Optional transform ID when referencing an existing saved transform."
|
|
77
79
|
},
|
|
78
80
|
"name": {
|
|
79
81
|
"type": "string",
|
|
80
|
-
"minLength": 1
|
|
82
|
+
"minLength": 1,
|
|
83
|
+
"description": "Optional display name for the nested transform."
|
|
81
84
|
},
|
|
82
85
|
"type": {
|
|
83
86
|
"type": "string",
|
|
84
|
-
"minLength": 1
|
|
87
|
+
"minLength": 1,
|
|
88
|
+
"description": "The transform operation type (e.g., 'accountAttribute', 'static', 'dateFormat')."
|
|
85
89
|
},
|
|
86
90
|
"requiresPeriodicRefresh": {
|
|
87
|
-
"type": "boolean"
|
|
91
|
+
"type": "boolean",
|
|
92
|
+
"description": "Whether this nested transform re-evaluates during nightly refresh."
|
|
88
93
|
},
|
|
89
94
|
"attributes": {
|
|
90
95
|
"type": "object",
|
|
@@ -97,7 +102,7 @@
|
|
|
97
102
|
"examples": [
|
|
98
103
|
{
|
|
99
104
|
"type": "conditional",
|
|
100
|
-
"name": "
|
|
105
|
+
"name": "Department Science Check",
|
|
101
106
|
"attributes": {
|
|
102
107
|
"expression": "$department eq Science",
|
|
103
108
|
"positiveCondition": "true",
|
|
@@ -113,7 +118,7 @@
|
|
|
113
118
|
},
|
|
114
119
|
{
|
|
115
120
|
"type": "conditional",
|
|
116
|
-
"name": "
|
|
121
|
+
"name": "Assign Building by Department",
|
|
117
122
|
"attributes": {
|
|
118
123
|
"expression": "$department eq Science",
|
|
119
124
|
"positiveCondition": "$scienceBuilding",
|
|
@@ -138,6 +143,25 @@
|
|
|
138
143
|
}
|
|
139
144
|
}
|
|
140
145
|
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"type": "conditional",
|
|
149
|
+
"name": "Active Employee Flag",
|
|
150
|
+
"requiresPeriodicRefresh": true,
|
|
151
|
+
"attributes": {
|
|
152
|
+
"expression": "$workerStatus eq active",
|
|
153
|
+
"positiveCondition": "true",
|
|
154
|
+
"negativeCondition": "false",
|
|
155
|
+
"workerStatus": {
|
|
156
|
+
"type": "accountAttribute",
|
|
157
|
+
"attributes": {
|
|
158
|
+
"applicationName": "corp-hr-system",
|
|
159
|
+
"attributeName": "WORKER_STATUS__c",
|
|
160
|
+
"accountSortAttribute": "created",
|
|
161
|
+
"accountSortDescending": true
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
141
165
|
}
|
|
142
166
|
]
|
|
143
167
|
}
|
package/dist/transforms/lint.js
CHANGED
|
@@ -263,28 +263,88 @@ function lintAccountAttribute(attrs) {
|
|
|
263
263
|
// ---------------------------------------------------------------------------
|
|
264
264
|
function lintConditional(attrs) {
|
|
265
265
|
const msgs = [];
|
|
266
|
+
const RESERVED = new Set(["expression", "positiveCondition", "negativeCondition"]);
|
|
267
|
+
// Helper: extract all $varName references from a string
|
|
268
|
+
const extractVars = (s) => (s.match(/\$([A-Za-z_][A-Za-z0-9_]*)/g) ?? []).map((v) => v.slice(1));
|
|
269
|
+
// All non-reserved keys in attributes are declared dynamic variables
|
|
270
|
+
const declaredVars = new Set(Object.keys(attrs ?? {}).filter((k) => !RESERVED.has(k)));
|
|
271
|
+
// --- 1. expression: required, non-empty ---
|
|
266
272
|
const exprRaw = attrs?.expression;
|
|
267
273
|
const expr = String(exprRaw ?? "").trim();
|
|
268
274
|
if (!expr) {
|
|
269
275
|
push(msgs, "error", "Missing required attribute: expression.", "attributes.expression");
|
|
270
276
|
return msgs;
|
|
271
277
|
}
|
|
278
|
+
// --- 2. Forbidden operators (using any of these throws IllegalArgumentException at runtime) ---
|
|
272
279
|
const forbidden = /(!=|==|>=|<=|>|<|\bne\b|\bgt\b|\blt\b|\bge\b|\ble\b)/i;
|
|
273
280
|
if (forbidden.test(expr)) {
|
|
274
|
-
push(msgs, "error", `Unsupported operator in expression
|
|
281
|
+
push(msgs, "error", `Unsupported operator in expression '${expr}'. ` +
|
|
282
|
+
"Only 'eq' is supported — using !=, ==, >, <, ne, gt, lt, ge, le throws IllegalArgumentException at runtime.", "attributes.expression");
|
|
275
283
|
}
|
|
276
|
-
|
|
277
|
-
|
|
284
|
+
// --- 3. Must contain exactly one 'eq' ---
|
|
285
|
+
const eqMatches = expr.match(/\beq\b/gi) ?? [];
|
|
286
|
+
if (eqMatches.length === 0) {
|
|
287
|
+
push(msgs, "error", `Conditional expression must use the 'eq' comparator: '<ValueA> eq <ValueB>'. Got: '${expr}'.`, "attributes.expression");
|
|
278
288
|
}
|
|
289
|
+
else if (eqMatches.length > 1) {
|
|
290
|
+
push(msgs, "error", `Expression must contain exactly one 'eq'. Found ${eqMatches.length} occurrences in: '${expr}'. ` +
|
|
291
|
+
"Nest multiple conditions using separate conditional transforms if needed.", "attributes.expression");
|
|
292
|
+
}
|
|
293
|
+
// --- 4. Both sides of 'eq' must be non-empty ---
|
|
279
294
|
const parts = expr.split(/\beq\b/i);
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
const valueA = parts[0]?.trim() ?? "";
|
|
296
|
+
const valueB = parts[1]?.trim() ?? "";
|
|
297
|
+
if (parts.length !== 2 || valueA.length === 0 || valueB.length === 0) {
|
|
298
|
+
push(msgs, "error", `Expression must follow '<ValueA> eq <ValueB>' with non-empty values on both sides. Got: '${expr}'.`, "attributes.expression");
|
|
299
|
+
}
|
|
300
|
+
// --- 5. Case-sensitivity info for literal operands ---
|
|
301
|
+
if (valueA.length > 0 && valueB.length > 0) {
|
|
302
|
+
const aIsVar = valueA.startsWith("$");
|
|
303
|
+
const bIsVar = valueB.startsWith("$");
|
|
304
|
+
if (!aIsVar || !bIsVar) {
|
|
305
|
+
push(msgs, "info", "Conditional comparisons are case-sensitive. " +
|
|
306
|
+
`'${!aIsVar ? valueA : valueB}' must match the source value exactly — ` +
|
|
307
|
+
"'Engineering' and 'engineering' are treated as different values.", "attributes.expression");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// --- 6. Cross-check $variable references in expression ---
|
|
311
|
+
if (valueA.length > 0 && valueB.length > 0) {
|
|
312
|
+
for (const varName of extractVars(expr)) {
|
|
313
|
+
if (!declaredVars.has(varName)) {
|
|
314
|
+
push(msgs, "error", `Expression references '$${varName}' but no matching variable key '${varName}' is declared in attributes. ` +
|
|
315
|
+
`Add a '${varName}' key to attributes as a static string or nested transform.`, "attributes.expression");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
282
318
|
}
|
|
283
|
-
|
|
284
|
-
|
|
319
|
+
// --- 7. positiveCondition: type check + $var cross-check ---
|
|
320
|
+
const posRaw = attrs?.positiveCondition;
|
|
321
|
+
if (posRaw !== undefined) {
|
|
322
|
+
if (typeof posRaw !== "string") {
|
|
323
|
+
push(msgs, "error", "positiveCondition must be a string — either a static value or a $variableName reference.", "attributes.positiveCondition");
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
for (const varName of extractVars(posRaw)) {
|
|
327
|
+
if (!declaredVars.has(varName)) {
|
|
328
|
+
push(msgs, "error", `positiveCondition references '$${varName}' but no matching variable key '${varName}' is declared in attributes. ` +
|
|
329
|
+
`Add a '${varName}' key to attributes as a static string or nested transform.`, "attributes.positiveCondition");
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
285
333
|
}
|
|
286
|
-
|
|
287
|
-
|
|
334
|
+
// --- 8. negativeCondition: type check + $var cross-check ---
|
|
335
|
+
const negRaw = attrs?.negativeCondition;
|
|
336
|
+
if (negRaw !== undefined) {
|
|
337
|
+
if (typeof negRaw !== "string") {
|
|
338
|
+
push(msgs, "error", "negativeCondition must be a string — either a static value or a $variableName reference.", "attributes.negativeCondition");
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
for (const varName of extractVars(negRaw)) {
|
|
342
|
+
if (!declaredVars.has(varName)) {
|
|
343
|
+
push(msgs, "error", `negativeCondition references '$${varName}' but no matching variable key '${varName}' is declared in attributes. ` +
|
|
344
|
+
`Add a '${varName}' key to attributes as a static string or nested transform.`, "attributes.negativeCondition");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
288
348
|
}
|
|
289
349
|
return msgs;
|
|
290
350
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "isc-transforms-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for SailPoint Identity Security Cloud (ISC) Transform authoring — scaffold, strict lint, catalog, and safe upsert to live tenants.",
|
|
6
6
|
"author": {
|