@knowttl/mg-axi 0.1.1 → 0.1.3

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