@knowttl/mg-axi 0.1.1 → 0.1.2

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.
Files changed (40) hide show
  1. package/README.md +149 -38
  2. package/dist/api.js +134 -8
  3. package/dist/bin/mg-axi-hook.js +31 -0
  4. package/dist/catalogue.js +390 -2
  5. package/dist/cli.js +116 -8
  6. package/dist/docs.js +45 -5
  7. package/dist/doctor.js +2 -2
  8. package/dist/entra-access-reviews.js +11 -4
  9. package/dist/entra-apps.js +13 -8
  10. package/dist/entra-audit-logs.js +8 -4
  11. package/dist/entra-auth-methods.js +8 -4
  12. package/dist/entra-certificate-auth.js +12 -8
  13. package/dist/entra-conditional-access.js +9 -4
  14. package/dist/entra-contacts.js +19 -12
  15. package/dist/entra-contracts.js +7 -4
  16. package/dist/entra-custom-security-attributes.js +19 -12
  17. package/dist/entra-data-policy-operations.js +7 -4
  18. package/dist/entra-delegated-admin.js +55 -6
  19. package/dist/entra-deleted-items.js +6 -4
  20. package/dist/entra-directory-objects.js +6 -4
  21. package/dist/entra-directory.js +19 -12
  22. package/dist/entra-domains.js +13 -8
  23. package/dist/entra-entitlement-management.js +12 -4
  24. package/dist/entra-group-pim.js +431 -0
  25. package/dist/entra-groups.js +13 -8
  26. package/dist/entra-lifecycle-workflows.js +136 -9
  27. package/dist/entra-organization.js +13 -8
  28. package/dist/entra-risk-prevention.js +436 -0
  29. package/dist/entra-risk.js +213 -5
  30. package/dist/entra-roles.js +10 -4
  31. package/dist/entra-subscriptions.js +6 -4
  32. package/dist/entra-users.js +7 -4
  33. package/dist/graph-session.js +29 -5
  34. package/dist/hook.js +84 -0
  35. package/dist/list-totals.js +52 -0
  36. package/dist/setup.js +5 -0
  37. package/docs/coverage.md +79 -13
  38. package/inventory/operations.json +2 -2
  39. package/package.json +4 -3
  40. package/skills/mg-axi/SKILL.md +56 -1
@@ -0,0 +1,436 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ // EXT-03 risk-prevention subfamily: the read mapping behind
3
+ // `mg-axi entra fraud-protection-provider list/show/count`,
4
+ // `mg-axi entra web-application-firewall-provider list/show/count` and
5
+ // `mg-axi entra web-application-firewall-verification list/show/count`
6
+ // plus `... verification provider show`. Operation construction stays
7
+ // beside its command; the shared session owns URLs, credentials, paging,
8
+ // retries and error translation, and the SDK owns TOON rendering. This
9
+ // module only maps flags to session calls and projects rows for compact
10
+ // output.
11
+ //
12
+ // Reviewed against the v1.0 riskpreventioncontainer-list-fraudprotectionproviders,
13
+ // fraudprotectionprovider-get,
14
+ // riskpreventioncontainer-list-webapplicationfirewallproviders,
15
+ // webapplicationfirewallprovider-get,
16
+ // riskpreventioncontainer-list-webapplicationfirewallverifications and
17
+ // webapplicationfirewallverificationmodel-get operation documentation and
18
+ // the fraudProtectionProvider, arkoseFraudProtectionProvider,
19
+ // humanSecurityFraudProtectionProvider, webApplicationFirewallProvider,
20
+ // akamaiWebApplicationFirewallProvider,
21
+ // cloudFlareWebApplicationFirewallProvider and
22
+ // webApplicationFirewallVerificationModel resource references on
23
+ // 2026-10-06. All six operation pages document the same access contract,
24
+ // so one scope and one denial-hint set serve all ten reads. Every read
25
+ // takes delegated RiskPreventionProviders.Read.All
26
+ // (RiskPreventionProviders.ReadWrite.All is the documented
27
+ // higher-privileged alternative); Graph documents no supported
28
+ // application permission, so application profiles are refused before
29
+ // credentials rather than sent to a certain denial. Delegated callers
30
+ // additionally need a directory role that can read risk-prevention
31
+ // configuration (Security Reader is the least-privileged read-only role;
32
+ // External ID User Flow Administrator, Application Administrator,
33
+ // Directory Reader and Security Administrator also work). Personal
34
+ // Microsoft accounts are not supported. No per-operation licence
35
+ // prerequisite is stated for these reads. The fraud list documents
36
+ // $select only, so it offers no --filter; the WAF and verification
37
+ // lists document $filter and offer it as plain $filter with no $count
38
+ // or ConsistencyLevel contract. $expand stays out everywhere: the
39
+ // verification's provider has its own sub-read and file/detail
40
+ // navigations need their own review. The three $count scalars take no
41
+ // --filter/--select/--limit/--cursor. The provider sub-read has no
42
+ // dedicated operation page; access follows the WAF provider-get
43
+ // contract and the verificationModel resource reference. Beta stays out
44
+ // ("beta needs its own review") and no mutation lives here: provider
45
+ // create/update/delete and verification submission (POST
46
+ // .../webApplicationFirewallProviders/{id}/verify) belong to no read
47
+ // slice.
48
+ //
49
+ // Secrecy by construction: API keys, client secrets and other key
50
+ // material the provider shapes carry (Arkose privateKey and publicKey,
51
+ // HUMAN serverToken, Akamai clientSecret/clientToken/accessToken,
52
+ // Cloudflare apiToken) are not selectable, so --select/--fields naming
53
+ // them fails before credentials, and rows the server returns carrying
54
+ // any of them are scrubbed before projection. Secrets therefore can
55
+ // never reach stdout, errors, previews or logs through this slice.
56
+ // publicKey is key material even though it is public: it follows the
57
+ // identity-provider certificateData precedent and is never returned.
58
+ // @odata.type is preserved on fraud and WAF rows without being
59
+ // selectable: it names the provider kind (for example
60
+ // #microsoft.graph.arkoseFraudProtectionProvider).
61
+ // Every fraud-protection-provider property this slice may request or
62
+ // display: the base properties plus the non-secret derived properties
63
+ // (Arkose clientSubDomain/verifySubDomain, HUMAN appId). Anything else
64
+ // fails before credentials.
65
+ export const KNOWN_FRAUD_FIELDS = [
66
+ "id",
67
+ "displayName",
68
+ "clientSubDomain",
69
+ "verifySubDomain",
70
+ "appId",
71
+ ];
72
+ const KNOWN_FRAUD = new Set(KNOWN_FRAUD_FIELDS);
73
+ // Every WAF-provider property this slice may request or display: the
74
+ // base properties plus the non-secret derived properties (Akamai
75
+ // hostPrefix, Cloudflare zoneId). Anything else fails before
76
+ // credentials.
77
+ export const KNOWN_WAF_FIELDS = [
78
+ "id",
79
+ "displayName",
80
+ "hostPrefix",
81
+ "zoneId",
82
+ ];
83
+ const KNOWN_WAF = new Set(KNOWN_WAF_FIELDS);
84
+ // Every verification property this slice may request or display,
85
+ // matching the reviewed resource. verificationResult and
86
+ // verifiedDetails ride whole as complex objects. Anything else fails
87
+ // before credentials.
88
+ export const KNOWN_VERIFICATION_FIELDS = [
89
+ "id",
90
+ "verifiedHost",
91
+ "providerType",
92
+ "verificationResult",
93
+ "verifiedDetails",
94
+ ];
95
+ const KNOWN_VERIFICATIONS = new Set(KNOWN_VERIFICATION_FIELDS);
96
+ // Secret-bearing keys are never selectable and never projected. The
97
+ // server is not expected to return them on GET, but any row carrying
98
+ // them is scrubbed before projection so key material can never reach
99
+ // output.
100
+ const SECRET_RISK_FIELDS = [
101
+ "privateKey",
102
+ "publicKey",
103
+ "serverToken",
104
+ "clientSecret",
105
+ "clientToken",
106
+ "accessToken",
107
+ "apiToken",
108
+ ];
109
+ // @odata.type is preserved on fraud and WAF rows without being
110
+ // selectable: it names the derived provider kind.
111
+ const RISK_TYPE_PROPERTY = "@odata.type";
112
+ // Compact rows: identifier and name; the kind rides as @odata.type.
113
+ const DEFAULT_FRAUD_LIST_SELECT = ["id", "displayName"];
114
+ // Show rows: the full reviewed set.
115
+ const DEFAULT_FRAUD_SHOW_SELECT = [...KNOWN_FRAUD_FIELDS];
116
+ // Compact rows: identifier and name; the kind rides as @odata.type.
117
+ const DEFAULT_WAF_LIST_SELECT = ["id", "displayName"];
118
+ // Show rows: the full reviewed set.
119
+ const DEFAULT_WAF_SHOW_SELECT = [...KNOWN_WAF_FIELDS];
120
+ // Compact rows: identifier, verified host and provider type.
121
+ const DEFAULT_VERIFICATION_LIST_SELECT = ["id", "verifiedHost", "providerType"];
122
+ // Show rows: the full reviewed set.
123
+ const DEFAULT_VERIFICATION_SHOW_SELECT = [...KNOWN_VERIFICATION_FIELDS];
124
+ // Delegated-only reads: Graph documents no supported application
125
+ // permission, so application profiles are refused before credentials.
126
+ export const DEFAULT_RISK_SCOPES = ["https://graph.microsoft.com/RiskPreventionProviders.Read.All"];
127
+ const TRUNCATE_AT = 500;
128
+ const FRAUD_KIND = {
129
+ noun: "fraud-protection-provider",
130
+ known: KNOWN_FRAUD,
131
+ knownList: KNOWN_FRAUD_FIELDS,
132
+ listSelect: DEFAULT_FRAUD_LIST_SELECT,
133
+ showSelect: DEFAULT_FRAUD_SHOW_SELECT,
134
+ collectionKey: "fraudProtectionProviders",
135
+ singleKey: "fraudProtectionProvider",
136
+ keepType: true,
137
+ };
138
+ const WAF_KIND = {
139
+ noun: "web-application-firewall-provider",
140
+ known: KNOWN_WAF,
141
+ knownList: KNOWN_WAF_FIELDS,
142
+ listSelect: DEFAULT_WAF_LIST_SELECT,
143
+ showSelect: DEFAULT_WAF_SHOW_SELECT,
144
+ collectionKey: "webApplicationFirewallProviders",
145
+ singleKey: "webApplicationFirewallProvider",
146
+ keepType: true,
147
+ };
148
+ const VERIFICATION_KIND = {
149
+ noun: "web-application-firewall-verification",
150
+ known: KNOWN_VERIFICATIONS,
151
+ knownList: KNOWN_VERIFICATION_FIELDS,
152
+ listSelect: DEFAULT_VERIFICATION_LIST_SELECT,
153
+ showSelect: DEFAULT_VERIFICATION_SHOW_SELECT,
154
+ collectionKey: "webApplicationFirewallVerifications",
155
+ singleKey: "webApplicationFirewallVerification",
156
+ keepType: false,
157
+ };
158
+ function fieldList(raw, kind, flag, help) {
159
+ const parts = String(raw)
160
+ .split(",")
161
+ .map(part => part.trim())
162
+ .filter(part => part.length > 0);
163
+ if (!parts.length)
164
+ throw new AxiError(`--${flag} needs at least one property`, "VALIDATION_ERROR", [help]);
165
+ const fields = [];
166
+ for (const part of parts) {
167
+ if (!kind.known.has(part)) {
168
+ const secret = SECRET_RISK_FIELDS.includes(part);
169
+ throw new AxiError(secret
170
+ ? `--${flag} cannot request ${part}: secret-bearing provider fields are never returned`
171
+ : `Unknown ${kind.noun} property ${part} in --${flag}`, "VALIDATION_ERROR", [
172
+ help,
173
+ `Known ${kind.noun} properties: ${kind.knownList.join(", ")}`,
174
+ ]);
175
+ }
176
+ if (!fields.includes(part))
177
+ fields.push(part);
178
+ }
179
+ return fields;
180
+ }
181
+ function scopesFor(flags, profile, help, noun) {
182
+ if (profile.mode === "application") {
183
+ throw new AxiError(`${noun} reads need a delegated profile; Graph documents no supported application permission for this operation`, "VALIDATION_ERROR", [help, "mg-axi profile create --name <name> --tenant <tenant-id> --client <client-id> --cloud commercial"]);
184
+ }
185
+ if (flags.scopes === undefined)
186
+ return [...DEFAULT_RISK_SCOPES];
187
+ const scopes = String(flags.scopes)
188
+ .split(",")
189
+ .map(scope => scope.trim())
190
+ .filter(scope => scope.length > 0);
191
+ return scopes;
192
+ }
193
+ function truncateValue(value, full) {
194
+ const truncate = (text) => full || text.length <= TRUNCATE_AT
195
+ ? { value: text, truncated: false }
196
+ : { value: `${text.slice(0, TRUNCATE_AT)}... (truncated, ${text.length} chars total)`, truncated: true };
197
+ if (typeof value === "string")
198
+ return truncate(value);
199
+ if (Array.isArray(value)) {
200
+ const projected = value.map(entry => truncateValue(entry, full));
201
+ return { value: projected.map(entry => entry.value), truncated: projected.some(entry => entry.truncated) };
202
+ }
203
+ if (value !== null && typeof value === "object") {
204
+ const projected = Object.entries(value).map(([key, entry]) => [key, truncateValue(entry, full)]);
205
+ return {
206
+ value: Object.fromEntries(projected.map(([key, entry]) => [key, entry.value])),
207
+ truncated: projected.some(([, entry]) => entry.truncated),
208
+ };
209
+ }
210
+ return { value, truncated: false };
211
+ }
212
+ // Scrubbing runs before projection: any secret-bearing key the server
213
+ // returned is dropped so it can never be selected, rendered or leaked.
214
+ function scrub(row) {
215
+ const source = row !== null && typeof row === "object" && !Array.isArray(row) ? row : {};
216
+ const cleaned = {};
217
+ for (const [key, value] of Object.entries(source)) {
218
+ if (SECRET_RISK_FIELDS.includes(key))
219
+ continue;
220
+ cleaned[key] = value;
221
+ }
222
+ return cleaned;
223
+ }
224
+ // Local projection preserves Graph's null/missing distinction: an
225
+ // explicit null stays null, an absent property stays absent and is never
226
+ // synthesized. @odata.type survives projection on provider rows so the
227
+ // provider kind is never lost.
228
+ function project(row, kind, fields, full) {
229
+ const source = scrub(row);
230
+ const projected = {};
231
+ let truncated = false;
232
+ for (const field of fields) {
233
+ if (!Object.hasOwn(source, field))
234
+ continue;
235
+ const result = truncateValue(source[field], full);
236
+ projected[field] = result.value;
237
+ truncated = truncated || result.truncated;
238
+ }
239
+ if (kind.keepType && typeof source[RISK_TYPE_PROPERTY] === "string")
240
+ projected[RISK_TYPE_PROPERTY] = source[RISK_TYPE_PROPERTY];
241
+ return { row: projected, truncated };
242
+ }
243
+ function selectedFields(flags, kind, defaults, help) {
244
+ const select = flags.select === undefined ? [...defaults] : fieldList(flags.select, kind, "select", help);
245
+ const fields = flags.fields === undefined ? [...select] : fieldList(flags.fields, kind, "fields", help);
246
+ const missing = fields.find(field => !select.includes(field));
247
+ if (missing) {
248
+ throw new AxiError(`--fields ${missing} was not fetched; request it with --select`, "VALIDATION_ERROR", [help]);
249
+ }
250
+ return { select, fields };
251
+ }
252
+ function profileHint(profileName) {
253
+ return `--profile ${shellValue(profileName)}`;
254
+ }
255
+ function shellValue(value) {
256
+ return /^[A-Za-z0-9_.,:/@=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
257
+ }
258
+ function fullHint(command, flags, profileName) {
259
+ const args = Object.entries({ ...flags, profile: profileName, full: true })
260
+ .map(([name, value]) => value === true ? `--${name}` : `--${name} ${shellValue(String(value))}`);
261
+ return `mg-axi ${command} ${args.join(" ")}`;
262
+ }
263
+ // Denials carry the session's generic grant/role/licence cause; these
264
+ // reads add the scope, roles and licensing that actually unlock them,
265
+ // because a 403 alone never says which prerequisite is missing.
266
+ function withGuidance(hints, run) {
267
+ return run().catch(error => {
268
+ if (error instanceof AxiError && error.code === "GRAPH_ERROR" && /\(403\)/.test(error.message)) {
269
+ throw new AxiError(error.message, "GRAPH_ERROR", [...hints, ...error.suggestions]);
270
+ }
271
+ throw error;
272
+ });
273
+ }
274
+ const RISK_DENIAL_HINTS = [
275
+ "Risk-prevention reads need delegated RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative); delegated callers pass it as --scopes",
276
+ "Delegated reads additionally need a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role; External ID User Flow Administrator, Application Administrator, Directory Reader and Security Administrator also work); Graph documents no supported application permission, so application profiles are refused before credentials",
277
+ "Personal Microsoft accounts are not supported for risk-prevention reads",
278
+ "No per-operation licence prerequisite is stated for risk-prevention reads; never diagnose licence solely from HTTP 403",
279
+ ];
280
+ const WORKFORCE_NOTE = "Workforce tenant context only; verification submission and provider create/update/delete belong to no read slice and external-customer user flows, authentication event flows and provisioning stay scheduled";
281
+ // Restoring the saved select/filter keeps cursor resumes lossless when
282
+ // --select or --filter is omitted. The fraud list documents $select
283
+ // only, so its leaf declares no --filter flag and strict input
284
+ // validation refuses the flag before credentials; the WAF and
285
+ // verification lists document $filter, so --filter passes through as
286
+ // plain $filter with no $count or ConsistencyLevel contract.
287
+ function collectionCommon(session, flags, kind, operation, help, profile) {
288
+ const cursor = flags.cursor === undefined ? undefined : String(flags.cursor);
289
+ if (cursor !== undefined && !cursor.trim())
290
+ throw new AxiError("--cursor needs the opaque cursor from a partial result", "VALIDATION_ERROR", [help]);
291
+ const saved = cursor === undefined ? undefined : session.cursorQuery(operation, cursor);
292
+ const { select, fields } = selectedFields(flags, kind, saved?.["$select"] === undefined ? kind.listSelect : fieldList(saved["$select"], kind, "select", help), help);
293
+ const filter = flags.filter === undefined ? saved?.["$filter"] : String(flags.filter);
294
+ return { cursor, select, fields, scopes: scopesFor(flags, profile, help, "Risk-prevention"), full: flags.full === true, filter };
295
+ }
296
+ function collectArgs(profile, operation, common, flags, help) {
297
+ const query = { $select: common.select.join(",") };
298
+ if (common.filter !== undefined)
299
+ query.$filter = common.filter;
300
+ const args = { profile, operation, query, scopes: common.scopes };
301
+ if (common.cursor !== undefined)
302
+ args.cursor = common.cursor;
303
+ if (flags.all === true) {
304
+ if (flags.limit !== undefined)
305
+ throw new AxiError("--limit and --all cannot be combined", "VALIDATION_ERROR", [help]);
306
+ }
307
+ else {
308
+ args.limit = flags.limit === undefined ? 100 : Number(flags.limit);
309
+ }
310
+ return args;
311
+ }
312
+ function resumeHint(profileName) {
313
+ return `Resume losslessly with the same flags plus --cursor <cursor-from-output> ${profileHint(profileName)}`;
314
+ }
315
+ function resourceId(flags, help, noun) {
316
+ const id = String(flags.id);
317
+ if (!id.trim())
318
+ throw new AxiError(`--id needs the ${noun} ID`, "VALIDATION_ERROR", [help]);
319
+ return id;
320
+ }
321
+ function singleResult(raw, kind, fields, full) {
322
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
323
+ throw new AxiError(`Graph returned a malformed ${kind.noun} body`, "GRAPH_ERROR", [
324
+ `Single-${kind.noun} reads carry one ${kind.singleKey} object; treat anything else as unknown, not empty`,
325
+ ]);
326
+ }
327
+ return project(raw, kind, fields, full);
328
+ }
329
+ async function collectKind(session, flags, kind, command, showCommand, profile, operation, help, profileName) {
330
+ const common = collectionCommon(session, flags, kind, operation, help, profile);
331
+ const result = await withGuidance(RISK_DENIAL_HINTS, () => session.collect(collectArgs(profile, operation, common, flags, help)));
332
+ const effectiveFlags = { ...flags, select: result.query.$select ?? kind.listSelect.join(",") };
333
+ if (result.query.$filter !== undefined)
334
+ effectiveFlags.filter = result.query.$filter;
335
+ const rows = [];
336
+ let truncated = false;
337
+ for (const row of result.value) {
338
+ const projected = project(row, kind, common.fields, common.full);
339
+ rows.push(projected.row);
340
+ truncated = truncated || projected.truncated;
341
+ }
342
+ const showHint = `mg-axi ${showCommand} --id <${kind.noun}-id> ${profileHint(profileName)}`;
343
+ const truncationHints = truncated ? [fullHint(command, effectiveFlags, profileName)] : [];
344
+ if (!result.complete) {
345
+ return {
346
+ [kind.collectionKey]: rows,
347
+ count: { returned: rows.length, complete: false, reason: result.reason },
348
+ cursor: result.cursor,
349
+ help: [...truncationHints, resumeHint(profileName), showHint, WORKFORCE_NOTE],
350
+ };
351
+ }
352
+ const count = { returned: rows.length, complete: true };
353
+ if (!rows.length) {
354
+ return {
355
+ [kind.collectionKey]: rows,
356
+ count,
357
+ help: [`0 ${kind.noun}s matched; the absence of results is the answer, not an error`, WORKFORCE_NOTE],
358
+ };
359
+ }
360
+ return { [kind.collectionKey]: rows, count, help: [...truncationHints, showHint, WORKFORCE_NOTE] };
361
+ }
362
+ async function showKind(session, flags, kind, command, param, profile, operation, help, profileName) {
363
+ const { select, fields } = selectedFields(flags, kind, kind.showSelect, help);
364
+ const scopes = scopesFor(flags, profile, help, "Risk-prevention");
365
+ const full = flags.full === true;
366
+ const id = resourceId(flags, help, kind.noun);
367
+ const raw = await withGuidance(RISK_DENIAL_HINTS, () => session.execute({
368
+ profile,
369
+ operation,
370
+ params: { [param]: id },
371
+ query: { $select: select.join(",") },
372
+ scopes,
373
+ }));
374
+ const { row, truncated } = singleResult(raw, kind, fields, full);
375
+ if (truncated)
376
+ return { [kind.singleKey]: row, help: [fullHint(command, flags, profileName), WORKFORCE_NOTE] };
377
+ return { [kind.singleKey]: row, help: [WORKFORCE_NOTE] };
378
+ }
379
+ async function countKind(session, flags, kind, profile, operation, help) {
380
+ const scopes = scopesFor(flags, profile, help, "Risk-prevention");
381
+ const raw = await withGuidance(RISK_DENIAL_HINTS, () => session.execute({ profile, operation, scopes, scalar: true }));
382
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
383
+ throw new AxiError(`Graph returned a malformed ${kind.noun} count body`, "GRAPH_ERROR", [
384
+ `${kind.noun} counts carry one non-negative integer scalar; treat anything else as unknown, not empty`,
385
+ ]);
386
+ }
387
+ return { count: { returned: raw, complete: true }, help: [WORKFORCE_NOTE] };
388
+ }
389
+ export async function listFraudProtectionProviders(session, flags, profile, operation, help, profileName) {
390
+ return collectKind(session, flags, FRAUD_KIND, "entra fraud-protection-provider list", "entra fraud-protection-provider show", profile, operation, help, profileName);
391
+ }
392
+ export async function showFraudProtectionProvider(session, flags, profile, operation, help, profileName) {
393
+ return showKind(session, flags, FRAUD_KIND, "entra fraud-protection-provider show", "fraudProtectionProvider-id", profile, operation, help, profileName);
394
+ }
395
+ export async function countFraudProtectionProviders(session, flags, profile, operation, help) {
396
+ return countKind(session, flags, FRAUD_KIND, profile, operation, help);
397
+ }
398
+ export async function listWafProviders(session, flags, profile, operation, help, profileName) {
399
+ return collectKind(session, flags, WAF_KIND, "entra web-application-firewall-provider list", "entra web-application-firewall-provider show", profile, operation, help, profileName);
400
+ }
401
+ export async function showWafProvider(session, flags, profile, operation, help, profileName) {
402
+ return showKind(session, flags, WAF_KIND, "entra web-application-firewall-provider show", "webApplicationFirewallProvider-id", profile, operation, help, profileName);
403
+ }
404
+ export async function countWafProviders(session, flags, profile, operation, help) {
405
+ return countKind(session, flags, WAF_KIND, profile, operation, help);
406
+ }
407
+ export async function listWafVerifications(session, flags, profile, operation, help, profileName) {
408
+ return collectKind(session, flags, VERIFICATION_KIND, "entra web-application-firewall-verification list", "entra web-application-firewall-verification show", profile, operation, help, profileName);
409
+ }
410
+ export async function showWafVerification(session, flags, profile, operation, help, profileName) {
411
+ return showKind(session, flags, VERIFICATION_KIND, "entra web-application-firewall-verification show", "webApplicationFirewallVerificationModel-id", profile, operation, help, profileName);
412
+ }
413
+ export async function countWafVerifications(session, flags, profile, operation, help) {
414
+ return countKind(session, flags, VERIFICATION_KIND, profile, operation, help);
415
+ }
416
+ export async function showVerificationProvider(session, flags, profile, operation, help, profileName) {
417
+ const { select, fields } = selectedFields(flags, WAF_KIND, WAF_KIND.showSelect, help);
418
+ const scopes = scopesFor(flags, profile, help, "Risk-prevention");
419
+ const full = flags.full === true;
420
+ const id = resourceId(flags, help, VERIFICATION_KIND.noun);
421
+ const raw = await withGuidance(RISK_DENIAL_HINTS, () => session.execute({
422
+ profile,
423
+ operation,
424
+ params: { "webApplicationFirewallVerificationModel-id": id },
425
+ query: { $select: select.join(",") },
426
+ scopes,
427
+ }));
428
+ const { row, truncated } = singleResult(raw, WAF_KIND, fields, full);
429
+ if (truncated) {
430
+ return {
431
+ [WAF_KIND.singleKey]: row,
432
+ help: [fullHint("entra web-application-firewall-verification provider show", flags, profileName), WORKFORCE_NOTE],
433
+ };
434
+ }
435
+ return { [WAF_KIND.singleKey]: row, help: [WORKFORCE_NOTE] };
436
+ }