@servicenow/sdk-build-plugins 2.0.1
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.
- package/dist/AttachmentPlugin.d.ts +253 -0
- package/dist/AttachmentPlugin.js +216 -0
- package/dist/AttachmentPlugin.js.map +1 -0
- package/dist/BusinessRulePlugin.d.ts +56 -0
- package/dist/BusinessRulePlugin.js +171 -0
- package/dist/BusinessRulePlugin.js.map +1 -0
- package/dist/CrossScopePrivilegePlugin.d.ts +22 -0
- package/dist/CrossScopePrivilegePlugin.js +42 -0
- package/dist/CrossScopePrivilegePlugin.js.map +1 -0
- package/dist/DefaultPlugin.d.ts +71 -0
- package/dist/DefaultPlugin.js +238 -0
- package/dist/DefaultPlugin.js.map +1 -0
- package/dist/IdPlugin.d.ts +17 -0
- package/dist/IdPlugin.js +45 -0
- package/dist/IdPlugin.js.map +1 -0
- package/dist/ListPlugin.d.ts +91 -0
- package/dist/ListPlugin.js +398 -0
- package/dist/ListPlugin.js.map +1 -0
- package/dist/PropertyPlugin.d.ts +122 -0
- package/dist/PropertyPlugin.js +165 -0
- package/dist/PropertyPlugin.js.map +1 -0
- package/dist/ScriptTemplatePlugin.d.ts +31 -0
- package/dist/ScriptTemplatePlugin.js +208 -0
- package/dist/ScriptTemplatePlugin.js.map +1 -0
- package/dist/UserPreferencePlugin.d.ts +16 -0
- package/dist/UserPreferencePlugin.js +30 -0
- package/dist/UserPreferencePlugin.js.map +1 -0
- package/dist/aclAndRole/AclPlugin.d.ts +117 -0
- package/dist/aclAndRole/AclPlugin.js +285 -0
- package/dist/aclAndRole/AclPlugin.js.map +1 -0
- package/dist/aclAndRole/RolePlugin.d.ts +58 -0
- package/dist/aclAndRole/RolePlugin.js +152 -0
- package/dist/aclAndRole/RolePlugin.js.map +1 -0
- package/dist/aclAndRole/Util.d.ts +3 -0
- package/dist/aclAndRole/Util.js +106 -0
- package/dist/aclAndRole/Util.js.map +1 -0
- package/dist/app/ApplicationMenuPlugin.d.ts +32 -0
- package/dist/app/ApplicationMenuPlugin.js +106 -0
- package/dist/app/ApplicationMenuPlugin.js.map +1 -0
- package/dist/atf/ATFComposer.d.ts +492 -0
- package/dist/atf/ATFComposer.js +2717 -0
- package/dist/atf/ATFComposer.js.map +1 -0
- package/dist/atf/TestPlugin.d.ts +31 -0
- package/dist/atf/TestPlugin.js +95 -0
- package/dist/atf/TestPlugin.js.map +1 -0
- package/dist/atf/index.d.ts +1 -0
- package/dist/atf/index.js +9 -0
- package/dist/atf/index.js.map +1 -0
- package/dist/db/ColumnPlugins.d.ts +278 -0
- package/dist/db/ColumnPlugins.js +112 -0
- package/dist/db/ColumnPlugins.js.map +1 -0
- package/dist/db/RecordPlugin.d.ts +208 -0
- package/dist/db/RecordPlugin.js +287 -0
- package/dist/db/RecordPlugin.js.map +1 -0
- package/dist/db/TablePlugin.d.ts +742 -0
- package/dist/db/TablePlugin.js +1249 -0
- package/dist/db/TablePlugin.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +27 -0
- package/dist/db/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/scriptedRESTAPI/RESTDeserializationUtils.d.ts +12 -0
- package/dist/scriptedRESTAPI/RESTDeserializationUtils.js +371 -0
- package/dist/scriptedRESTAPI/RESTDeserializationUtils.js.map +1 -0
- package/dist/scriptedRESTAPI/RESTSerializationUtils.d.ts +15 -0
- package/dist/scriptedRESTAPI/RESTSerializationUtils.js +177 -0
- package/dist/scriptedRESTAPI/RESTSerializationUtils.js.map +1 -0
- package/dist/scriptedRESTAPI/RestApiPlugin.d.ts +144 -0
- package/dist/scriptedRESTAPI/RestApiPlugin.js +318 -0
- package/dist/scriptedRESTAPI/RestApiPlugin.js.map +1 -0
- package/dist/scriptedRESTAPI/RestSchemaUtils.d.ts +190 -0
- package/dist/scriptedRESTAPI/RestSchemaUtils.js +53 -0
- package/dist/scriptedRESTAPI/RestSchemaUtils.js.map +1 -0
- package/dist/scriptedRESTAPI/RestUtils.d.ts +75 -0
- package/dist/scriptedRESTAPI/RestUtils.js +469 -0
- package/dist/scriptedRESTAPI/RestUtils.js.map +1 -0
- package/dist/scripts/ClientScriptPlugin.d.ts +43 -0
- package/dist/scripts/ClientScriptPlugin.js +190 -0
- package/dist/scripts/ClientScriptPlugin.js.map +1 -0
- package/dist/scripts/scriptUtils.d.ts +15 -0
- package/dist/scripts/scriptUtils.js +83 -0
- package/dist/scripts/scriptUtils.js.map +1 -0
- package/dist/uxf/ExperiencePlugin.d.ts +22 -0
- package/dist/uxf/ExperiencePlugin.js +55 -0
- package/dist/uxf/ExperiencePlugin.js.map +1 -0
- package/dist/uxf/RoutesPlugin.d.ts +22 -0
- package/dist/uxf/RoutesPlugin.js +176 -0
- package/dist/uxf/RoutesPlugin.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/cleanUxValue.d.ts +4 -0
- package/dist/uxf/UxfFormulaParser/cleanUxValue.js +65 -0
- package/dist/uxf/UxfFormulaParser/cleanUxValue.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/api.d.ts +189 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/api.js +158 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/api.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.d.ts +13 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +604 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.d.ts +12 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.d.ts +31 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +64 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/index.d.ts +3 -0
- package/dist/uxf/UxfFormulaParser/index.js +11 -0
- package/dist/uxf/UxfFormulaParser/index.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/parser.d.ts +8 -0
- package/dist/uxf/UxfFormulaParser/parser.js +87 -0
- package/dist/uxf/UxfFormulaParser/parser.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.d.ts +8 -0
- package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js +17 -0
- package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js.map +1 -0
- package/dist/uxf/constants.d.ts +2 -0
- package/dist/uxf/constants.js +8 -0
- package/dist/uxf/constants.js.map +1 -0
- package/dist/uxf/index.d.ts +2 -0
- package/dist/uxf/index.js +11 -0
- package/dist/uxf/index.js.map +1 -0
- package/dist/uxf/tectonicIdGenerator.d.ts +12 -0
- package/dist/uxf/tectonicIdGenerator.js +102 -0
- package/dist/uxf/tectonicIdGenerator.js.map +1 -0
- package/license +9 -0
- package/package.json +42 -0
- package/src/AttachmentPlugin.ts +262 -0
- package/src/BusinessRulePlugin.ts +251 -0
- package/src/CrossScopePrivilegePlugin.ts +54 -0
- package/src/DefaultPlugin.ts +272 -0
- package/src/IdPlugin.ts +47 -0
- package/src/ListPlugin.ts +497 -0
- package/src/PropertyPlugin.ts +218 -0
- package/src/ScriptTemplatePlugin.ts +223 -0
- package/src/UserPreferencePlugin.ts +36 -0
- package/src/aclAndRole/AclPlugin.ts +410 -0
- package/src/aclAndRole/RolePlugin.ts +225 -0
- package/src/aclAndRole/Util.ts +104 -0
- package/src/app/ApplicationMenuPlugin.ts +158 -0
- package/src/atf/ATFComposer.ts +3356 -0
- package/src/atf/TestPlugin.ts +119 -0
- package/src/atf/index.ts +1 -0
- package/src/db/ColumnPlugins.ts +117 -0
- package/src/db/RecordPlugin.ts +391 -0
- package/src/db/TablePlugin.ts +1581 -0
- package/src/db/index.ts +3 -0
- package/src/index.ts +16 -0
- package/src/scriptedRESTAPI/RESTDeserializationUtils.ts +410 -0
- package/src/scriptedRESTAPI/RESTSerializationUtils.ts +227 -0
- package/src/scriptedRESTAPI/RestApiPlugin.ts +438 -0
- package/src/scriptedRESTAPI/RestSchemaUtils.ts +72 -0
- package/src/scriptedRESTAPI/RestUtils.ts +507 -0
- package/src/scripts/ClientScriptPlugin.ts +251 -0
- package/src/scripts/scriptUtils.ts +81 -0
- package/src/uxf/ExperiencePlugin.ts +64 -0
- package/src/uxf/RoutesPlugin.ts +215 -0
- package/src/uxf/UxfFormulaParser/cleanUxValue.ts +73 -0
- package/src/uxf/UxfFormulaParser/grammerParser/api.js +166 -0
- package/src/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +606 -0
- package/src/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
- package/src/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +65 -0
- package/src/uxf/UxfFormulaParser/index.ts +4 -0
- package/src/uxf/UxfFormulaParser/parser.ts +64 -0
- package/src/uxf/UxfFormulaParser/utils/getErrorMsg.ts +13 -0
- package/src/uxf/constants.ts +4 -0
- package/src/uxf/index.ts +2 -0
- package/src/uxf/tectonicIdGenerator.ts +81 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Acl,
|
|
3
|
+
TableAcl,
|
|
4
|
+
NamedAcl,
|
|
5
|
+
AclAttributes,
|
|
6
|
+
AclBase,
|
|
7
|
+
AclOperations,
|
|
8
|
+
AclTypes,
|
|
9
|
+
} from '@servicenow/sdk-core/runtime/app'
|
|
10
|
+
import {
|
|
11
|
+
Context,
|
|
12
|
+
Plugin,
|
|
13
|
+
extractCallExpression,
|
|
14
|
+
generateCallExpressionExportForDocument,
|
|
15
|
+
getOrCreateEntitySourceFile,
|
|
16
|
+
getSysUpdateName,
|
|
17
|
+
linkDocument,
|
|
18
|
+
removeNode,
|
|
19
|
+
transformFunctionArguments,
|
|
20
|
+
FluentDiagnostic,
|
|
21
|
+
findObjectPropertyValue,
|
|
22
|
+
EntityData,
|
|
23
|
+
ObjectData,
|
|
24
|
+
} from '@servicenow/sdk-build-core'
|
|
25
|
+
import { addObjectToArrayById, removeObjectFromArrayById } from './Util'
|
|
26
|
+
import { Record as DBRecord, TableName } from '@servicenow/sdk-core/runtime/db'
|
|
27
|
+
import { scriptInfo, ScriptInfo, buildScriptImport, except } from '../scripts/scriptUtils'
|
|
28
|
+
import * as ts from 'ts-morph'
|
|
29
|
+
import { AclNamedTypes } from '@servicenow/sdk-core/runtime/app'
|
|
30
|
+
import { processScript } from '../ScriptTemplatePlugin'
|
|
31
|
+
import { Diagnostic } from '@servicenow/sdk-project'
|
|
32
|
+
|
|
33
|
+
type AclDoc = {
|
|
34
|
+
table: string
|
|
35
|
+
data: {
|
|
36
|
+
sys_id: string
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type AclRole = {
|
|
41
|
+
table: string
|
|
42
|
+
data: {
|
|
43
|
+
sys_security_acl: string
|
|
44
|
+
sys_user_role: string
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type AclData = AclBase & {
|
|
49
|
+
name: string
|
|
50
|
+
type: keyof typeof AclTypes
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ExecuteOnlyTypes = new Set([
|
|
54
|
+
'client_callable_flow_object',
|
|
55
|
+
'client_callable_script_include',
|
|
56
|
+
'graphql',
|
|
57
|
+
'processor',
|
|
58
|
+
'rest_endpoint',
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
function aclAsRecord<T extends TableName>(config: Acl<T>) {
|
|
62
|
+
const tableConfig = config as TableAcl<T>
|
|
63
|
+
const namedConfig = config as NamedAcl
|
|
64
|
+
|
|
65
|
+
const field = String(tableConfig.field || '')
|
|
66
|
+
const name = namedConfig.name || (field ? `${tableConfig.table}.${field}` : tableConfig.table)
|
|
67
|
+
if (!name) {
|
|
68
|
+
throw new Error(`ACL of type ${config.type} and $id '${config.$id}' has no name or associated table`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (config.operation === 'execute' && !ExecuteOnlyTypes.has(config.type)) {
|
|
72
|
+
throw new Error(`ACL ${name} cannot use operation 'execute', because it is of type '${config.type}'`)
|
|
73
|
+
}
|
|
74
|
+
if (config.script && config.type === 'graphql') {
|
|
75
|
+
throw new Error(`ACL ${name} does not support scripts, because it is of type ${config.type}`)
|
|
76
|
+
}
|
|
77
|
+
if (tableConfig.applies_to && tableConfig.type !== 'record') {
|
|
78
|
+
throw new Error(`ACL ${name} cannot set applies_to unless its type is 'record'`)
|
|
79
|
+
}
|
|
80
|
+
if (tableConfig.operation === 'add_to_list' && (config.script || config.condition)) {
|
|
81
|
+
throw new Error(`ACL ${name} cannot have a script or condition due to its 'add_to_list' operation`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return DBRecord({
|
|
85
|
+
table: 'sys_security_acl',
|
|
86
|
+
$id: config.$id,
|
|
87
|
+
data: {
|
|
88
|
+
name,
|
|
89
|
+
active: config.active ?? true,
|
|
90
|
+
type: AclTypes[config.type ?? 'record'],
|
|
91
|
+
operation: AclOperations[config.operation as keyof typeof AclOperations] || config.operation || '',
|
|
92
|
+
decision_type: config.decision_type ?? 'allow',
|
|
93
|
+
description: config.description || '',
|
|
94
|
+
local_or_existing: config.local_or_existing || 'Local',
|
|
95
|
+
admin_overrides: config.admin_overrides ?? true,
|
|
96
|
+
advanced: !!config.script,
|
|
97
|
+
condition: config.condition ?? '',
|
|
98
|
+
applies_to: tableConfig.applies_to ?? '',
|
|
99
|
+
script: config.script as any,
|
|
100
|
+
security_attribute:
|
|
101
|
+
(config.security_attribute && AclAttributes[config.security_attribute as keyof typeof AclAttributes]) ||
|
|
102
|
+
config.security_attribute ||
|
|
103
|
+
'',
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default Plugin({
|
|
109
|
+
name: 'Acl',
|
|
110
|
+
ownedTables: {
|
|
111
|
+
sys_security_acl: { diagnosticLevel: Diagnostic.Level.Warn },
|
|
112
|
+
},
|
|
113
|
+
extractors: {
|
|
114
|
+
entity: {
|
|
115
|
+
CallExpression(node, context) {
|
|
116
|
+
const result = extractCallExpression(Acl, 'record', node, context, (acl) =>
|
|
117
|
+
context.registerExplicitId('sys_security_acl', acl.$id)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if (!result.handled || !(0 in result.data)) {
|
|
121
|
+
return result
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const acl = result.data[0]
|
|
125
|
+
const scriptVal = getScript(acl, context)
|
|
126
|
+
const aclRecord = aclAsRecord(acl.getValue())
|
|
127
|
+
|
|
128
|
+
const m2mRoleToAcl =
|
|
129
|
+
acl
|
|
130
|
+
.getProperty('roles')
|
|
131
|
+
?.getValue()
|
|
132
|
+
?.map((role) => {
|
|
133
|
+
const roleId = typeof role === 'string' ? role : role.$id
|
|
134
|
+
|
|
135
|
+
const aclId = context.keys.registerExplicitId('sys_security_acl', aclRecord.$id)
|
|
136
|
+
const roleSysId =
|
|
137
|
+
typeof role === 'string' ? role : context.registerExplicitId('sys_user_role', role.$id)
|
|
138
|
+
const guid = context.keys.registerCompositeId('sys_security_acl_role', {
|
|
139
|
+
sys_security_acl: aclRecord.$id,
|
|
140
|
+
sys_user_role: roleId,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return new EntityData(
|
|
144
|
+
'record',
|
|
145
|
+
guid,
|
|
146
|
+
ObjectData.fromObjectValue(
|
|
147
|
+
DBRecord({
|
|
148
|
+
table: 'sys_security_acl_role',
|
|
149
|
+
$id: guid,
|
|
150
|
+
data: {
|
|
151
|
+
sys_security_acl: aclId,
|
|
152
|
+
sys_user_role: roleSysId,
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
node
|
|
156
|
+
),
|
|
157
|
+
node
|
|
158
|
+
)
|
|
159
|
+
}) || []
|
|
160
|
+
|
|
161
|
+
const diagnostics = performDiagnosticChecksOnSecurityAttribute(
|
|
162
|
+
node,
|
|
163
|
+
acl.getValue(),
|
|
164
|
+
m2mRoleToAcl,
|
|
165
|
+
context.mode
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const aclWithScript = { ...aclRecord, data: { ...aclRecord.data, script: scriptVal || '' } }
|
|
169
|
+
const aclEntityData = new EntityData(
|
|
170
|
+
'record',
|
|
171
|
+
acl.getGuid(),
|
|
172
|
+
ObjectData.fromObjectValue(aclWithScript, node),
|
|
173
|
+
node
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
handled: true,
|
|
178
|
+
diagnostics: [...result.diagnostics, ...diagnostics],
|
|
179
|
+
data: [aclEntityData, ...m2mRoleToAcl],
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
raw: {
|
|
184
|
+
FunctionDeclaration(node, context) {
|
|
185
|
+
const info = scriptInfo(node, context, Acl.name)
|
|
186
|
+
if (!info) {
|
|
187
|
+
return { handled: false }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { handled: true, diagnostics: [], data: [info] }
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
FunctionExpression(node, context) {
|
|
194
|
+
const info = scriptInfo(node, context, Acl.name)
|
|
195
|
+
if (!info) {
|
|
196
|
+
return { handled: false }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { handled: true, diagnostics: [], data: [info] }
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
arrangers: {
|
|
204
|
+
record(document, context) {
|
|
205
|
+
if ((document.data as any).table !== 'sys_security_acl_role') {
|
|
206
|
+
return { handled: false }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const aclId = (document.data as any).data.sys_security_acl
|
|
210
|
+
const aclDoc = context.getDocument(aclId, 'record')
|
|
211
|
+
const aclDeleted = aclDoc && aclDoc.action === 'DELETE'
|
|
212
|
+
if (aclDeleted && document.action === 'INSERT_OR_UPDATE') {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Existing sys_security_acl_role with id ${document.guid} is referencing deleted ACL ${aclId}. This can be fixed by either deleting the sys_security_acl_role record or updating its sys_security_acl reference.`
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
handled: true,
|
|
220
|
+
result: aclId ? { kind: 'record', guid: aclId } : undefined,
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
generators: {
|
|
225
|
+
record(document, context, linkedDocuments) {
|
|
226
|
+
const documentType = (document.data as any)?.table
|
|
227
|
+
if (documentType === 'sys_security_acl_role') {
|
|
228
|
+
if (!document.parent) {
|
|
229
|
+
return undefined
|
|
230
|
+
}
|
|
231
|
+
const acl = linkedDocuments.find(
|
|
232
|
+
(doc) =>
|
|
233
|
+
(doc.data as AclDoc).table === 'sys_security_acl' &&
|
|
234
|
+
doc.guid === (document.data as AclRole).data.sys_security_acl
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return acl && acl.node ? { ...document, node: acl.node } : undefined
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (documentType !== 'sys_security_acl') {
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return linkDocument(
|
|
245
|
+
document,
|
|
246
|
+
generateCallExpressionExportForDocument(
|
|
247
|
+
context,
|
|
248
|
+
{
|
|
249
|
+
sourceFile: getOrCreateEntitySourceFile(
|
|
250
|
+
context,
|
|
251
|
+
getSysUpdateName(document, 'sys_security_acl')
|
|
252
|
+
),
|
|
253
|
+
moduleSpecifier: '@servicenow/sdk/core',
|
|
254
|
+
},
|
|
255
|
+
Acl,
|
|
256
|
+
{ $id: document.guid }
|
|
257
|
+
).getExpressionIfKindOrThrow(ts.SyntaxKind.CallExpression)
|
|
258
|
+
)
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
transformers: {
|
|
262
|
+
record: {
|
|
263
|
+
CallExpression(document, context) {
|
|
264
|
+
const documentType = (document.data as any)?.table
|
|
265
|
+
|
|
266
|
+
const [args] = document.node.getArguments()
|
|
267
|
+
if (!ts.Node.isObjectLiteralExpression(args)) {
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (documentType === 'sys_security_acl_role') {
|
|
272
|
+
if (!document.parent) {
|
|
273
|
+
return true
|
|
274
|
+
}
|
|
275
|
+
const acl2Role = (document.data as AclRole).data
|
|
276
|
+
if (document.action === 'DELETE') {
|
|
277
|
+
const roleId = acl2Role.sys_user_role
|
|
278
|
+
if (roleId) {
|
|
279
|
+
removeObjectFromArrayById(document, 'roles', context)
|
|
280
|
+
}
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
addObjectToArrayById(document, 'roles', acl2Role.sys_user_role, context)
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (document.action === 'DELETE') {
|
|
289
|
+
removeNode(document.node)
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (documentType !== 'sys_security_acl' || !document.changedData) {
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const modifiedAclData = (document.changedData as any).data as AclBase
|
|
298
|
+
const unmodifiedAclData = (document.data as any).data as AclData
|
|
299
|
+
const type = reverseLookup(AclTypes, unmodifiedAclData.type)
|
|
300
|
+
|
|
301
|
+
const aclData = {
|
|
302
|
+
...except(
|
|
303
|
+
modifiedAclData,
|
|
304
|
+
(key) =>
|
|
305
|
+
key.startsWith('sys_') ||
|
|
306
|
+
key === 'advanced' ||
|
|
307
|
+
key === 'name' ||
|
|
308
|
+
key === 'operation' ||
|
|
309
|
+
key === 'script'
|
|
310
|
+
),
|
|
311
|
+
...getTableOrName(type, unmodifiedAclData.name),
|
|
312
|
+
security_attribute:
|
|
313
|
+
reverseLookup(AclAttributes, unmodifiedAclData.security_attribute) ||
|
|
314
|
+
unmodifiedAclData.security_attribute,
|
|
315
|
+
operation: reverseLookup(AclOperations, unmodifiedAclData.operation) || unmodifiedAclData.operation,
|
|
316
|
+
type,
|
|
317
|
+
} as any
|
|
318
|
+
|
|
319
|
+
const { script } = modifiedAclData as { script?: string }
|
|
320
|
+
if (script !== undefined) {
|
|
321
|
+
processScript(args, 'script', script, document.guid, 'sys_security_acl', context)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return transformFunctionArguments(document.node, Acl, aclData)
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// security_attribute.is_localizsed ? 'Local' : 'Existing'
|
|
331
|
+
function performDiagnosticChecksOnSecurityAttribute<T>(
|
|
332
|
+
callExpression: ts.CallExpression,
|
|
333
|
+
aclData: AclBase,
|
|
334
|
+
roles: Array<T>,
|
|
335
|
+
mode: 'serialize' | 'transform'
|
|
336
|
+
) {
|
|
337
|
+
const diagnostics: FluentDiagnostic[] = []
|
|
338
|
+
|
|
339
|
+
if (roles.length < 1) {
|
|
340
|
+
const attributeRecord = aclData.security_attribute as DBRecord<'sys_security_attribute'>
|
|
341
|
+
if (!aclData.security_attribute && !aclData.script && !aclData.condition) {
|
|
342
|
+
diagnostics.push(
|
|
343
|
+
new FluentDiagnostic(
|
|
344
|
+
callExpression,
|
|
345
|
+
'ACLs must have at least one of the following: roles, security_attribute, condition, or script',
|
|
346
|
+
{ level: mode === 'transform' ? Diagnostic.Level.Warn : Diagnostic.Level.Error }
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
} else if (attributeRecord && attributeRecord.data) {
|
|
350
|
+
if (
|
|
351
|
+
aclData.local_or_existing === 'Existing' &&
|
|
352
|
+
(attributeRecord.data.type !== 'compound' || attributeRecord.data.is_localized)
|
|
353
|
+
) {
|
|
354
|
+
diagnostics.push(
|
|
355
|
+
new FluentDiagnostic(
|
|
356
|
+
findObjectPropertyValue(callExpression, 'security_attribute'),
|
|
357
|
+
`Invalid ACL with 'Existing' sys_security_attribute: Must have a security_attribute with a type of 'compound' and is_localized set to false.`
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
} else if (
|
|
361
|
+
(!aclData.local_or_existing || aclData.local_or_existing === 'Local') &&
|
|
362
|
+
!attributeRecord.data.is_localized
|
|
363
|
+
) {
|
|
364
|
+
diagnostics.push(
|
|
365
|
+
new FluentDiagnostic(
|
|
366
|
+
findObjectPropertyValue(callExpression, 'security_attribute'),
|
|
367
|
+
`Invalid ACL with 'Local' security_attribute: security_attribute must have is_localized set to true`
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return diagnostics
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getTableOrName(type: keyof typeof AclTypes, name: string) {
|
|
377
|
+
if (AclNamedTypes[type]) {
|
|
378
|
+
return { name }
|
|
379
|
+
}
|
|
380
|
+
const tablePathParts = name.split('.')
|
|
381
|
+
const table = tablePathParts[0] || ''
|
|
382
|
+
const field = tablePathParts[1] || ''
|
|
383
|
+
|
|
384
|
+
return { table, field }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// TODO: The casting and type guards in this function are a bit of a mess and prone to errors.
|
|
388
|
+
function getScript(acl: EntityData, context: Context) {
|
|
389
|
+
const scriptData = acl.getProperty('script')?.getValue() as ScriptInfo
|
|
390
|
+
let scriptVal: string
|
|
391
|
+
if (scriptData && scriptData.filePath) {
|
|
392
|
+
const { filePath, functionName, isDefault } = scriptData
|
|
393
|
+
scriptVal = buildScriptImport(
|
|
394
|
+
filePath,
|
|
395
|
+
functionName,
|
|
396
|
+
isDefault,
|
|
397
|
+
context,
|
|
398
|
+
(funcName) => `answer = ${funcName}(current)\n`
|
|
399
|
+
)
|
|
400
|
+
} else {
|
|
401
|
+
scriptVal = scriptData as unknown as string
|
|
402
|
+
}
|
|
403
|
+
return scriptVal
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function reverseLookup<T extends object>(obj: T, sysId): keyof T {
|
|
407
|
+
return (Object.entries(obj)
|
|
408
|
+
.filter(([_, id]) => id === sysId)
|
|
409
|
+
.map(([key]) => key)[0] || '') as keyof T
|
|
410
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { Role, RoleConfig, roleSchema } from '@servicenow/sdk-core/runtime/app'
|
|
2
|
+
import {
|
|
3
|
+
Context,
|
|
4
|
+
Plugin,
|
|
5
|
+
linkDocument,
|
|
6
|
+
extractCallExpression,
|
|
7
|
+
FluentDiagnostic,
|
|
8
|
+
findObjectPropertyValue,
|
|
9
|
+
EntityData,
|
|
10
|
+
ObjectData,
|
|
11
|
+
Data,
|
|
12
|
+
} from '@servicenow/sdk-build-core'
|
|
13
|
+
import { Record } from '@servicenow/sdk-core/runtime/db'
|
|
14
|
+
import {
|
|
15
|
+
generateCallExpressionExportForDocument,
|
|
16
|
+
getOrCreateEntitySourceFile,
|
|
17
|
+
transformFunctionArguments,
|
|
18
|
+
getSysUpdateName,
|
|
19
|
+
removeNode,
|
|
20
|
+
} from '@servicenow/sdk-build-core'
|
|
21
|
+
import { addObjectToArrayById, removeObjectFromArrayById } from './Util'
|
|
22
|
+
import { SyntaxKind, Node } from 'ts-morph'
|
|
23
|
+
import { except } from '../scripts/scriptUtils'
|
|
24
|
+
import { RecordPlugin } from '../db/RecordPlugin'
|
|
25
|
+
import { Diagnostic } from '@servicenow/sdk-project'
|
|
26
|
+
|
|
27
|
+
function mapRoleToRole(context, roleId, containedRoleId) {
|
|
28
|
+
const m2mId = context.keys.registerCompositeId('sys_user_role_contains', {
|
|
29
|
+
role: roleId,
|
|
30
|
+
contains: containedRoleId,
|
|
31
|
+
})
|
|
32
|
+
return Record({
|
|
33
|
+
table: 'sys_user_role_contains',
|
|
34
|
+
$id: m2mId,
|
|
35
|
+
data: {
|
|
36
|
+
contains: containedRoleId,
|
|
37
|
+
role: roleId,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type RoleDoc = {
|
|
43
|
+
table: string
|
|
44
|
+
data: {
|
|
45
|
+
sys_id: string
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type RoleContainsDoc = {
|
|
50
|
+
table: string
|
|
51
|
+
data: {
|
|
52
|
+
role: string
|
|
53
|
+
contains: string
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default Plugin({
|
|
58
|
+
name: 'Role',
|
|
59
|
+
ownedTables: {
|
|
60
|
+
sys_user_role: { diagnosticLevel: Diagnostic.Level.Error },
|
|
61
|
+
},
|
|
62
|
+
extractors: {
|
|
63
|
+
entity: {
|
|
64
|
+
CallExpression: (node, context) => {
|
|
65
|
+
const result = extractCallExpression(
|
|
66
|
+
Role as (role: RoleConfig) => RoleConfig,
|
|
67
|
+
'role',
|
|
68
|
+
node,
|
|
69
|
+
context,
|
|
70
|
+
({ $id }) => context.registerExplicitId('sys_user_role', $id as string)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if (!result.handled || !(0 in result.data)) {
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const role = result.data[0]
|
|
78
|
+
const diagnostics = result.diagnostics
|
|
79
|
+
const scope = context.app?.config?.scope
|
|
80
|
+
const name = role.getProperty('name').getValue()
|
|
81
|
+
if (!name.startsWith(`${scope}.`)) {
|
|
82
|
+
const nameValue = findObjectPropertyValue(node, 'name')
|
|
83
|
+
diagnostics.push(new FluentDiagnostic(nameValue, `Role name must begin with '${scope}.'`))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
handled: true,
|
|
88
|
+
diagnostics,
|
|
89
|
+
data: [role],
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
composers: {
|
|
95
|
+
entity: {
|
|
96
|
+
role(entity, context) {
|
|
97
|
+
const scope = context.app?.config?.scope
|
|
98
|
+
const guid = entity.getGuid()
|
|
99
|
+
const node = entity.getNode()
|
|
100
|
+
|
|
101
|
+
const role = roleSchema.safeParse(entity.getValue())
|
|
102
|
+
if (!role.success) {
|
|
103
|
+
return undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { $id: _, name, contains_roles, ...rest } = role.data
|
|
107
|
+
|
|
108
|
+
const roleToRoles: Record<'sys_user_role_contains'>[] = []
|
|
109
|
+
const containsRoles = entity.getProperty('contains_roles')
|
|
110
|
+
if (Data.isArray(containsRoles)) {
|
|
111
|
+
for (const containedRole of containsRoles.getElements()) {
|
|
112
|
+
const containedRoleSysId = Data.isEntity(containedRole)
|
|
113
|
+
? containedRole.getGuid()
|
|
114
|
+
: containedRole.getValue() // Should be a string
|
|
115
|
+
|
|
116
|
+
roleToRoles.push(mapRoleToRole(context, guid, containedRoleSysId))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const suffix = name.replace(new RegExp(`^${scope}\\.`), '')
|
|
121
|
+
|
|
122
|
+
const roleRecord = Record({
|
|
123
|
+
table: 'sys_user_role',
|
|
124
|
+
$id: guid,
|
|
125
|
+
data: {
|
|
126
|
+
name,
|
|
127
|
+
suffix,
|
|
128
|
+
...rest,
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return context.composeEntities(
|
|
133
|
+
[roleRecord, ...roleToRoles].map(
|
|
134
|
+
(r) => new EntityData('record', r.$id.toString(), ObjectData.fromObjectValue(r, node), node)
|
|
135
|
+
),
|
|
136
|
+
[RecordPlugin]
|
|
137
|
+
)
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
arrangers: {
|
|
142
|
+
record(document) {
|
|
143
|
+
if ((document.data as any).table !== 'sys_user_role_contains') {
|
|
144
|
+
return { handled: false }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const roleId = (document.data as any).data.role
|
|
148
|
+
return {
|
|
149
|
+
handled: true,
|
|
150
|
+
result: roleId ? { kind: 'record', guid: roleId } : undefined,
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
generators: {
|
|
155
|
+
record(document, context: Context, linkedDocuments) {
|
|
156
|
+
const documentType = (document.data as any)?.table
|
|
157
|
+
if (documentType === 'sys_user_role_contains') {
|
|
158
|
+
const role = linkedDocuments.find(
|
|
159
|
+
(doc) =>
|
|
160
|
+
(doc.data as RoleDoc).table === 'sys_user_role' &&
|
|
161
|
+
doc.guid === (document.data as RoleContainsDoc).data.role
|
|
162
|
+
)
|
|
163
|
+
return role && role.node ? { ...document, node: role.node } : undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (documentType !== 'sys_user_role') {
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return linkDocument(
|
|
171
|
+
document,
|
|
172
|
+
generateCallExpressionExportForDocument(
|
|
173
|
+
context,
|
|
174
|
+
{
|
|
175
|
+
sourceFile: getOrCreateEntitySourceFile(context, getSysUpdateName(document, 'sys_user_role')),
|
|
176
|
+
moduleSpecifier: '@servicenow/sdk/core',
|
|
177
|
+
},
|
|
178
|
+
Role,
|
|
179
|
+
{ $id: document.guid }
|
|
180
|
+
).getExpressionIfKindOrThrow(SyntaxKind.CallExpression)
|
|
181
|
+
)
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
transformers: {
|
|
185
|
+
record: {
|
|
186
|
+
CallExpression(document, context) {
|
|
187
|
+
const documentType = (document.data as any)?.table
|
|
188
|
+
if (documentType === 'sys_user_role_contains') {
|
|
189
|
+
const [args] = document.node.getArguments()
|
|
190
|
+
if (!Node.isObjectLiteralExpression(args) || !document.parent) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
if (document.action === 'DELETE') {
|
|
194
|
+
removeObjectFromArrayById(document, 'contains_roles', context)
|
|
195
|
+
return true
|
|
196
|
+
}
|
|
197
|
+
const m2mData = (document.data as RoleContainsDoc).data
|
|
198
|
+
addObjectToArrayById(document, 'contains_roles', m2mData.contains, context)
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (documentType !== 'sys_user_role') {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (document.action === 'DELETE') {
|
|
207
|
+
removeNode(document.node)
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const rawRoleData = (document.changedData as any).data
|
|
212
|
+
const roleData = except(
|
|
213
|
+
rawRoleData,
|
|
214
|
+
(key) =>
|
|
215
|
+
key.startsWith('sys_') ||
|
|
216
|
+
key === 'requires_subscription' ||
|
|
217
|
+
key === 'suffix' ||
|
|
218
|
+
key === 'includes_roles'
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return transformFunctionArguments(document.node, Role, roleData)
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
})
|