@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.
- package/README.md +251 -77
- package/dist/api.js +253 -8
- package/dist/bin/mg-axi-hook.js +31 -0
- package/dist/catalogue.js +693 -2
- package/dist/cli.js +155 -17
- package/dist/docs.js +175 -46
- package/dist/doctor.js +2 -2
- package/dist/entra-access-reviews.js +11 -4
- package/dist/entra-apps.js +171 -14
- package/dist/entra-audit-logs.js +101 -16
- package/dist/entra-auth-methods.js +8 -4
- package/dist/entra-certificate-auth.js +12 -8
- package/dist/entra-conditional-access.js +425 -15
- package/dist/entra-contacts.js +19 -12
- package/dist/entra-contracts.js +7 -4
- package/dist/entra-custom-security-attributes.js +19 -12
- package/dist/entra-data-policy-operations.js +7 -4
- package/dist/entra-delegated-admin.js +55 -6
- package/dist/entra-deleted-items.js +6 -4
- package/dist/entra-directory-objects.js +6 -4
- package/dist/entra-directory.js +19 -12
- package/dist/entra-domains.js +13 -8
- package/dist/entra-entitlement-management.js +12 -4
- package/dist/entra-federation-configurations.js +7 -4
- package/dist/entra-grants.js +8 -4
- package/dist/entra-group-lifecycle.js +13 -8
- package/dist/entra-group-pim.js +431 -0
- package/dist/entra-groups.js +13 -8
- package/dist/entra-identity-providers.js +7 -4
- package/dist/entra-lifecycle-workflows.js +152 -13
- package/dist/entra-multi-tenant-organization.js +7 -4
- package/dist/entra-on-premises-synchronization.js +6 -4
- package/dist/entra-organization.js +13 -8
- package/dist/entra-risk-prevention.js +442 -0
- package/dist/entra-risk.js +224 -9
- package/dist/entra-roles.js +204 -12
- package/dist/entra-subscriptions.js +6 -4
- package/dist/entra-terms-of-use.js +13 -8
- package/dist/entra-users.js +7 -4
- package/dist/graph-session.js +30 -5
- package/dist/hook.js +84 -0
- package/dist/list-totals.js +52 -0
- package/dist/setup.js +9 -1
- package/docs/coverage.md +743 -610
- package/inventory/operations.json +2 -2
- package/inventory/schema.json +1 -1
- package/package.json +4 -3
- package/skills/mg-axi/SKILL.md +86 -1
package/dist/entra-apps.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { KNOWN_APP_ROLE_FIELDS } from "./entra-grants.js";
|
|
3
|
+
import { listTotals } from "./list-totals.js";
|
|
2
4
|
import { SAFE_CREDENTIAL_FIELDS } from "./graph-session.js";
|
|
3
5
|
// READ-07: the Entra application and service-principal read mapping behind
|
|
4
6
|
// `mg-axi entra application list/show`, `mg-axi entra service-principal
|
|
5
|
-
// list/show
|
|
7
|
+
// list/show`, the two `owner list` relationship reads, the service-principal
|
|
8
|
+
// `app-role-assigned-to` and `app-role-assignment show` assignment reads and
|
|
9
|
+
// the two `federated-credential` collections. Operation
|
|
6
10
|
// construction stays beside its command; the shared session owns URLs,
|
|
7
11
|
// credentials, paging, retries and error translation, and the SDK owns TOON
|
|
8
12
|
// rendering. This module only maps flags to session calls and projects rows
|
|
@@ -13,14 +17,18 @@ import { SAFE_CREDENTIAL_FIELDS } from "./graph-session.js";
|
|
|
13
17
|
// servicePrincipal-list-owners operation documentation on 2026-10-04.
|
|
14
18
|
// Delegated and application reads take Application.Read.All; the object id
|
|
15
19
|
// (`id`) is never conflated with the client ID (`appId`) - both ride in the
|
|
16
|
-
// compact list defaults so callers can see the distinction.
|
|
17
|
-
// (
|
|
18
|
-
// this slice; only owners
|
|
20
|
+
// compact list defaults so callers can see the distinction. Delegated
|
|
21
|
+
// permission grants (oauth2PermissionGrants) are READ-08 and stay out of
|
|
22
|
+
// this slice; only owners, direct assignments and federated credentials are
|
|
23
|
+
// projected as relationships. Alternate-key lookups by appId and
|
|
24
|
+
// federated-credential lookups by name stay out too: they need a validated
|
|
25
|
+
// function-argument binding the session guard does not have, and widening
|
|
26
|
+
// the guard is out of scope for a read slice.
|
|
19
27
|
//
|
|
20
28
|
// Credential safety belongs to the shared session's SAFE_CREDENTIAL_FIELDS
|
|
21
29
|
// projection, applied before collection buffering and again on cursor decode.
|
|
22
30
|
// Local projection uses that same allowlist for the displayed view.
|
|
23
|
-
// The
|
|
31
|
+
// The thirteen GET operations below are the only ones this module ever binds;
|
|
24
32
|
// secret-minting routes (addPassword, addKey) and every write stay refused before
|
|
25
33
|
// credentials via checkReadOperation.
|
|
26
34
|
// Every application property this slice may request or display. Anything
|
|
@@ -51,6 +59,20 @@ export const KNOWN_SP_FIELDS = [
|
|
|
51
59
|
"passwordCredentials",
|
|
52
60
|
];
|
|
53
61
|
const KNOWN_SPS = new Set(KNOWN_SP_FIELDS);
|
|
62
|
+
const KNOWN_ASSIGNED = new Set(KNOWN_APP_ROLE_FIELDS);
|
|
63
|
+
// Federated-credential rows carry workload-identity metadata only: issuer,
|
|
64
|
+
// subject and audiences name the trusted token source. There is no secret
|
|
65
|
+
// material on this resource, and the projection still drops anything outside
|
|
66
|
+
// this set so a future property can never leak through.
|
|
67
|
+
export const KNOWN_FEDERATED_FIELDS = [
|
|
68
|
+
"id",
|
|
69
|
+
"name",
|
|
70
|
+
"issuer",
|
|
71
|
+
"subject",
|
|
72
|
+
"description",
|
|
73
|
+
"audiences",
|
|
74
|
+
];
|
|
75
|
+
const KNOWN_FEDERATED = new Set(KNOWN_FEDERATED_FIELDS);
|
|
54
76
|
// Owner rows are directoryObjects of mixed types; richer per-type fields
|
|
55
77
|
// need single-object reads, so only these stay selectable.
|
|
56
78
|
export const KNOWN_OWNER_FIELDS = ["id", "displayName", "mail"];
|
|
@@ -67,6 +89,17 @@ const DEFAULT_APP_SHOW_SELECT = [...KNOWN_APP_FIELDS];
|
|
|
67
89
|
const DEFAULT_SP_SHOW_SELECT = [...KNOWN_SP_FIELDS];
|
|
68
90
|
// Compact owner rows: identifier, display name and address.
|
|
69
91
|
const DEFAULT_OWNER_SELECT = ["id", "displayName", "mail"];
|
|
92
|
+
// Compact federated-credential rows: the credential id, its name and the
|
|
93
|
+
// trusted token source. The full set adds the human description and the
|
|
94
|
+
// audience list.
|
|
95
|
+
const DEFAULT_FEDERATED_LIST_SELECT = ["id", "name", "issuer", "subject"];
|
|
96
|
+
const DEFAULT_FEDERATED_SHOW_SELECT = [...KNOWN_FEDERATED_FIELDS];
|
|
97
|
+
// Compact assignment rows: the assignment id, the granted role and who
|
|
98
|
+
// holds it. principalId is constant for one service principal's
|
|
99
|
+
// appRoleAssignments list but varies across appRoleAssignedTo rows.
|
|
100
|
+
const DEFAULT_ASSIGNED_LIST_SELECT = ["id", "appRoleId", "principalDisplayName", "principalId", "principalType", "resourceDisplayName", "resourceId"];
|
|
101
|
+
;
|
|
102
|
+
const DEFAULT_ASSIGNED_SHOW_SELECT = [...KNOWN_APP_ROLE_FIELDS];
|
|
70
103
|
// Application.Read.All covers applications, service principals and owners in
|
|
71
104
|
// both modes; delegated reads default to it unless --scopes overrides it.
|
|
72
105
|
export const DEFAULT_DELEGATED_SCOPES = ["https://graph.microsoft.com/Application.Read.All"];
|
|
@@ -77,9 +110,16 @@ const READ_OPERATIONS = {
|
|
|
77
110
|
"GET:/applications": "application",
|
|
78
111
|
"GET:/applications/{application-id}": "application",
|
|
79
112
|
"GET:/applications/{application-id}/owners": "application",
|
|
113
|
+
"GET:/applications/{application-id}/federatedIdentityCredentials": "application",
|
|
114
|
+
"GET:/applications/{application-id}/federatedIdentityCredentials/{federatedIdentityCredential-id}": "application",
|
|
80
115
|
"GET:/servicePrincipals": "service-principal",
|
|
81
116
|
"GET:/servicePrincipals/{servicePrincipal-id}": "service-principal",
|
|
82
117
|
"GET:/servicePrincipals/{servicePrincipal-id}/owners": "service-principal",
|
|
118
|
+
"GET:/servicePrincipals/{servicePrincipal-id}/appRoleAssignedTo": "service-principal",
|
|
119
|
+
"GET:/servicePrincipals/{servicePrincipal-id}/appRoleAssignedTo/{appRoleAssignment-id}": "service-principal",
|
|
120
|
+
"GET:/servicePrincipals/{servicePrincipal-id}/appRoleAssignments/{appRoleAssignment-id}": "service-principal",
|
|
121
|
+
"GET:/servicePrincipals/{servicePrincipal-id}/federatedIdentityCredentials": "service-principal",
|
|
122
|
+
"GET:/servicePrincipals/{servicePrincipal-id}/federatedIdentityCredentials/{federatedIdentityCredential-id}": "service-principal",
|
|
83
123
|
};
|
|
84
124
|
function checkReadOperation(operation, help) {
|
|
85
125
|
// Version stays the session's decision: beta reads remain preview-gated
|
|
@@ -87,7 +127,7 @@ function checkReadOperation(operation, help) {
|
|
|
87
127
|
// fabricated secret-minting or write route can never reach credentials.
|
|
88
128
|
const route = `${operation.method}:${operation.path}`;
|
|
89
129
|
if (operation.method !== "GET" || !Object.hasOwn(READ_OPERATIONS, route)) {
|
|
90
|
-
throw new AxiError(`Refused non-read route ${operation.id}: READ-07 serves only the
|
|
130
|
+
throw new AxiError(`Refused non-read route ${operation.id}: READ-07 serves only the thirteen catalogued application and service-principal GETs`, "VALIDATION_ERROR", [
|
|
91
131
|
help,
|
|
92
132
|
"Secret-minting routes (addPassword, addKey) and writes are never constructed here; consent grants belong to READ-08",
|
|
93
133
|
]);
|
|
@@ -270,23 +310,25 @@ async function listCollection(session, flags, profile, operation, help, profileN
|
|
|
270
310
|
if (!result.complete) {
|
|
271
311
|
return {
|
|
272
312
|
[key]: rows,
|
|
273
|
-
|
|
313
|
+
...listTotals(rows.length, result.total, emptyNoun, false),
|
|
314
|
+
complete: false,
|
|
315
|
+
reason: result.reason,
|
|
274
316
|
cursor: result.cursor,
|
|
275
317
|
help: [...truncationHints, resumeHint(profileName), showHint],
|
|
276
318
|
};
|
|
277
319
|
}
|
|
278
|
-
const count = { returned: rows.length, complete: true };
|
|
279
320
|
if (!rows.length) {
|
|
280
321
|
return {
|
|
281
322
|
[key]: rows,
|
|
282
|
-
|
|
323
|
+
...listTotals(rows.length, result.total, emptyNoun, true),
|
|
324
|
+
complete: true,
|
|
283
325
|
help: [
|
|
284
326
|
`0 ${emptyNoun} matched; the absence of results is the answer, not an error`,
|
|
285
327
|
showHint,
|
|
286
328
|
],
|
|
287
329
|
};
|
|
288
330
|
}
|
|
289
|
-
return { [key]: rows,
|
|
331
|
+
return { [key]: rows, ...listTotals(rows.length, result.total, emptyNoun, true), complete: true, help: [...truncationHints, showHint] };
|
|
290
332
|
}
|
|
291
333
|
async function showSingle(session, flags, profile, operation, help, profileName, command, key, known, knownList, defaults, param, flag) {
|
|
292
334
|
checkReadOperation(operation, help);
|
|
@@ -351,20 +393,22 @@ async function listOwners(session, flags, profile, operation, help, profileName,
|
|
|
351
393
|
if (!result.complete) {
|
|
352
394
|
return {
|
|
353
395
|
owners: rows,
|
|
354
|
-
|
|
396
|
+
...listTotals(rows.length, result.total, "owners", false),
|
|
397
|
+
complete: false,
|
|
398
|
+
reason: result.reason,
|
|
355
399
|
cursor: result.cursor,
|
|
356
400
|
help: [...truncationHints, ...limitedHints, resumeHint(profileName)],
|
|
357
401
|
};
|
|
358
402
|
}
|
|
359
|
-
const count = { returned: rows.length, complete: true };
|
|
360
403
|
if (!rows.length) {
|
|
361
404
|
return {
|
|
362
405
|
owners: rows,
|
|
363
|
-
|
|
406
|
+
...listTotals(rows.length, result.total, "owners", true),
|
|
407
|
+
complete: true,
|
|
364
408
|
help: [...limitedHints, "0 owners matched; the absence of results is the answer, not an error"],
|
|
365
409
|
};
|
|
366
410
|
}
|
|
367
|
-
return { owners: rows,
|
|
411
|
+
return { owners: rows, ...listTotals(rows.length, result.total, "owners", true), complete: true, help: [...truncationHints, ...limitedHints] };
|
|
368
412
|
}
|
|
369
413
|
export async function listApplicationOwners(session, flags, profile, operation, help, profileName) {
|
|
370
414
|
return listOwners(session, flags, profile, operation, help, profileName, "entra application owner list", "application-id", "application");
|
|
@@ -372,3 +416,116 @@ export async function listApplicationOwners(session, flags, profile, operation,
|
|
|
372
416
|
export async function listServicePrincipalOwners(session, flags, profile, operation, help, profileName) {
|
|
373
417
|
return listOwners(session, flags, profile, operation, help, profileName, "entra service-principal owner list", "servicePrincipal-id", "service-principal");
|
|
374
418
|
}
|
|
419
|
+
async function listChildCollection(session, flags, profile, operation, help, profileName, child, known, knownList, defaults) {
|
|
420
|
+
checkReadOperation(operation, help);
|
|
421
|
+
const parent = String(flags[child.parentFlag]);
|
|
422
|
+
if (!parent.trim())
|
|
423
|
+
throw new AxiError(`--${child.parentFlag} needs ${child.parentLabel}`, "VALIDATION_ERROR", [help]);
|
|
424
|
+
const common = collectionCommon(session, flags, known, knownList, defaults, operation, help, profile);
|
|
425
|
+
const args = collectArgs(profile, operation, common, flags, help);
|
|
426
|
+
args.params = { [child.parentParam]: parent };
|
|
427
|
+
const result = await session.collect(args);
|
|
428
|
+
const effectiveFlags = { ...flags, select: result.query.$select ?? defaults.join(",") };
|
|
429
|
+
if (result.query.$filter !== undefined)
|
|
430
|
+
effectiveFlags.filter = result.query.$filter;
|
|
431
|
+
const rows = [];
|
|
432
|
+
let truncated = false;
|
|
433
|
+
for (const row of result.value) {
|
|
434
|
+
const projected = project(row, common.fields, common.full);
|
|
435
|
+
rows.push(projected.row);
|
|
436
|
+
truncated = truncated || projected.truncated;
|
|
437
|
+
}
|
|
438
|
+
const truncationHints = truncated ? [fullHint(child.command, effectiveFlags, profileName)] : [];
|
|
439
|
+
if (!result.complete) {
|
|
440
|
+
return {
|
|
441
|
+
[child.key]: rows,
|
|
442
|
+
...listTotals(rows.length, result.total, child.emptyNoun, false),
|
|
443
|
+
complete: false,
|
|
444
|
+
reason: result.reason,
|
|
445
|
+
cursor: result.cursor,
|
|
446
|
+
help: [...truncationHints, resumeHint(profileName), child.showHint],
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
if (!rows.length) {
|
|
450
|
+
return {
|
|
451
|
+
[child.key]: rows,
|
|
452
|
+
...listTotals(rows.length, result.total, child.emptyNoun, true),
|
|
453
|
+
complete: true,
|
|
454
|
+
help: [
|
|
455
|
+
`0 ${child.emptyNoun} matched; the absence of results is the answer, not an error`,
|
|
456
|
+
child.showHint,
|
|
457
|
+
],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return { [child.key]: rows, ...listTotals(rows.length, result.total, child.emptyNoun, true), complete: true, help: [...truncationHints, child.showHint] };
|
|
461
|
+
}
|
|
462
|
+
async function showChildObject(session, flags, profile, operation, help, profileName, command, key, known, knownList, defaults, parentParam, parentFlag, parentLabel, childParam) {
|
|
463
|
+
checkReadOperation(operation, help);
|
|
464
|
+
const parent = String(flags[parentFlag]);
|
|
465
|
+
if (!parent.trim())
|
|
466
|
+
throw new AxiError(`--${parentFlag} needs ${parentLabel}`, "VALIDATION_ERROR", [help]);
|
|
467
|
+
const { select, fields } = selectedFields(flags, known, knownList, defaults, help);
|
|
468
|
+
const scopes = scopesFor(flags, profile, help);
|
|
469
|
+
const full = flags.full === true;
|
|
470
|
+
const raw = await session.execute({
|
|
471
|
+
profile,
|
|
472
|
+
operation,
|
|
473
|
+
params: { [parentParam]: parent, [childParam]: String(flags.id) },
|
|
474
|
+
query: { $select: select.join(",") },
|
|
475
|
+
scopes,
|
|
476
|
+
});
|
|
477
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
478
|
+
throw new AxiError("Graph returned a malformed child object body", "GRAPH_ERROR", [
|
|
479
|
+
"Single-object reads carry one object; treat anything else as unknown, not empty",
|
|
480
|
+
]);
|
|
481
|
+
}
|
|
482
|
+
const { row, truncated } = project(raw, fields, full);
|
|
483
|
+
if (truncated)
|
|
484
|
+
return { [key]: row, help: [fullHint(command, flags, profileName)] };
|
|
485
|
+
return { [key]: row };
|
|
486
|
+
}
|
|
487
|
+
export async function listApplicationFederatedCredentials(session, flags, profile, operation, help, profileName) {
|
|
488
|
+
return listChildCollection(session, flags, profile, operation, help, profileName, {
|
|
489
|
+
command: "entra application federated-credential list",
|
|
490
|
+
key: "federatedCredentials",
|
|
491
|
+
emptyNoun: "federated credentials",
|
|
492
|
+
showHint: `mg-axi entra application federated-credential show --application <application-object-id> --id <credential-id> ${profileHint(profileName)}`,
|
|
493
|
+
parentParam: "application-id",
|
|
494
|
+
parentFlag: "application",
|
|
495
|
+
parentLabel: "the owning application object ID",
|
|
496
|
+
}, KNOWN_FEDERATED, KNOWN_FEDERATED_FIELDS, DEFAULT_FEDERATED_LIST_SELECT);
|
|
497
|
+
}
|
|
498
|
+
export async function showApplicationFederatedCredential(session, flags, profile, operation, help, profileName) {
|
|
499
|
+
return showChildObject(session, flags, profile, operation, help, profileName, "entra application federated-credential show", "federatedCredential", KNOWN_FEDERATED, KNOWN_FEDERATED_FIELDS, DEFAULT_FEDERATED_SHOW_SELECT, "application-id", "application", "the owning application object ID", "federatedIdentityCredential-id");
|
|
500
|
+
}
|
|
501
|
+
export async function listServicePrincipalFederatedCredentials(session, flags, profile, operation, help, profileName) {
|
|
502
|
+
return listChildCollection(session, flags, profile, operation, help, profileName, {
|
|
503
|
+
command: "entra service-principal federated-credential list",
|
|
504
|
+
key: "federatedCredentials",
|
|
505
|
+
emptyNoun: "federated credentials",
|
|
506
|
+
showHint: `mg-axi entra service-principal federated-credential show --service-principal <service-principal-object-id> --id <credential-id> ${profileHint(profileName)}`,
|
|
507
|
+
parentParam: "servicePrincipal-id",
|
|
508
|
+
parentFlag: "service-principal",
|
|
509
|
+
parentLabel: "the owning service-principal object ID",
|
|
510
|
+
}, KNOWN_FEDERATED, KNOWN_FEDERATED_FIELDS, DEFAULT_FEDERATED_LIST_SELECT);
|
|
511
|
+
}
|
|
512
|
+
export async function showServicePrincipalFederatedCredential(session, flags, profile, operation, help, profileName) {
|
|
513
|
+
return showChildObject(session, flags, profile, operation, help, profileName, "entra service-principal federated-credential show", "federatedCredential", KNOWN_FEDERATED, KNOWN_FEDERATED_FIELDS, DEFAULT_FEDERATED_SHOW_SELECT, "servicePrincipal-id", "service-principal", "the owning service-principal object ID", "federatedIdentityCredential-id");
|
|
514
|
+
}
|
|
515
|
+
export async function listServicePrincipalAssignedTo(session, flags, profile, operation, help, profileName) {
|
|
516
|
+
return listChildCollection(session, flags, profile, operation, help, profileName, {
|
|
517
|
+
command: "entra service-principal app-role-assigned-to list",
|
|
518
|
+
key: "appRoleAssignedTo",
|
|
519
|
+
emptyNoun: "app role assignments",
|
|
520
|
+
showHint: `mg-axi entra service-principal app-role-assigned-to show --service-principal <service-principal-object-id> --id <assignment-id> ${profileHint(profileName)}`,
|
|
521
|
+
parentParam: "servicePrincipal-id",
|
|
522
|
+
parentFlag: "service-principal",
|
|
523
|
+
parentLabel: "the resource service-principal object ID",
|
|
524
|
+
}, KNOWN_ASSIGNED, KNOWN_APP_ROLE_FIELDS, DEFAULT_ASSIGNED_LIST_SELECT);
|
|
525
|
+
}
|
|
526
|
+
export async function showServicePrincipalAssignedTo(session, flags, profile, operation, help, profileName) {
|
|
527
|
+
return showChildObject(session, flags, profile, operation, help, profileName, "entra service-principal app-role-assigned-to show", "appRoleAssignedTo", KNOWN_ASSIGNED, KNOWN_APP_ROLE_FIELDS, DEFAULT_ASSIGNED_SHOW_SELECT, "servicePrincipal-id", "service-principal", "the resource service-principal object ID", "appRoleAssignment-id");
|
|
528
|
+
}
|
|
529
|
+
export async function showServicePrincipalAppRoleAssignment(session, flags, profile, operation, help, profileName) {
|
|
530
|
+
return showChildObject(session, flags, profile, operation, help, profileName, "entra service-principal app-role-assignment show", "appRoleAssignment", KNOWN_ASSIGNED, KNOWN_APP_ROLE_FIELDS, DEFAULT_ASSIGNED_SHOW_SELECT, "servicePrincipal-id", "service-principal", "the client service-principal object ID", "appRoleAssignment-id");
|
|
531
|
+
}
|
package/dist/entra-audit-logs.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
import { listTotals } from "./list-totals.js";
|
|
3
|
+
// READ-05: the Entra sign-in, directory-audit and provisioning-log read
|
|
4
|
+
// mapping behind `mg-axi entra sign-in list/show`, `mg-axi entra
|
|
5
|
+
// directory-audit list/show` and `mg-axi entra provisioning list/show`.
|
|
6
|
+
// Operation construction stays beside its command; the shared
|
|
5
7
|
// session owns URLs, credentials, paging, retries and error translation, and
|
|
6
8
|
// the SDK owns TOON rendering. This module only maps flags to session calls,
|
|
7
9
|
// bounds every collection query in time, and projects rows for compact output.
|
|
8
10
|
//
|
|
9
11
|
// Reviewed against the v1.0 signin-list and directoryaudit-list operation
|
|
10
|
-
// documentation on 2026-10-04
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// documentation on 2026-10-04 and the provisioningobjectsummary-list
|
|
13
|
+
// documentation on 2026-10-06. Sign-in and directory-audit collections read
|
|
14
|
+
// D/A AuditLog.Read.All; delegated sign-in reads additionally accept Global
|
|
15
|
+
// Reader, Reports Reader, Security Administrator, Security Operator or
|
|
16
|
+
// Security Reader, while delegated directory-audit reads accept Reports
|
|
17
|
+
// Reader, Security Administrator or Security Reader. The provisioning
|
|
18
|
+
// collection reads D/A AuditLog.Read.All and Directory.Read.All together;
|
|
19
|
+
// delegated reads accept the same five roles as sign-ins, Reports Reader is
|
|
20
|
+
// the least privileged role for the logs, and the tenant needs a P1 or P2
|
|
21
|
+
// licence. Graph omits appliedConditionalAccessPolicies
|
|
15
22
|
// without a CA-data permission/role, so an absent property is reported as
|
|
16
23
|
// unavailable rather than invented. Sign-in/audit Graph reporting carries a
|
|
17
24
|
// conservative P1/P2 deployment prerequisite; retention and premium fields
|
|
@@ -54,13 +61,45 @@ export const KNOWN_AUDIT_FIELDS = [
|
|
|
54
61
|
];
|
|
55
62
|
const SIGNIN_KNOWN = new Set(KNOWN_SIGNIN_FIELDS);
|
|
56
63
|
const AUDIT_KNOWN = new Set(KNOWN_AUDIT_FIELDS);
|
|
64
|
+
// The full reviewed provisioningObjectSummary property set. None of these
|
|
65
|
+
// is credential material (no password, key or secret properties), and the
|
|
66
|
+
// known-set gate rejects anything else before credentials; the session's
|
|
67
|
+
// shared redaction stays the backstop for secret-shaped values.
|
|
68
|
+
export const KNOWN_PROVISIONING_FIELDS = [
|
|
69
|
+
"id",
|
|
70
|
+
"activityDateTime",
|
|
71
|
+
"tenantId",
|
|
72
|
+
"jobId",
|
|
73
|
+
"cycleId",
|
|
74
|
+
"changeId",
|
|
75
|
+
"action",
|
|
76
|
+
"durationInMilliseconds",
|
|
77
|
+
"sourceSystem",
|
|
78
|
+
"sourceIdentity",
|
|
79
|
+
"targetSystem",
|
|
80
|
+
"targetIdentity",
|
|
81
|
+
"provisioningStatusInfo",
|
|
82
|
+
"provisioningSteps",
|
|
83
|
+
"modifiedProperties",
|
|
84
|
+
"servicePrincipal",
|
|
85
|
+
"initiatedBy",
|
|
86
|
+
];
|
|
87
|
+
const PROVISIONING_KNOWN = new Set(KNOWN_PROVISIONING_FIELDS);
|
|
57
88
|
// Compact list rows: identifier, time, who/what happened, and outcome.
|
|
58
89
|
const DEFAULT_SIGNIN_LIST_SELECT = ["id", "createdDateTime", "userPrincipalName", "appDisplayName"];
|
|
59
90
|
const DEFAULT_AUDIT_LIST_SELECT = ["id", "activityDateTime", "activityDisplayName", "result"];
|
|
91
|
+
const DEFAULT_PROVISIONING_LIST_SELECT = ["id", "activityDateTime", "action", "provisioningStatusInfo"];
|
|
60
92
|
// Show rows: the full reviewed server set for one object.
|
|
61
93
|
const DEFAULT_SIGNIN_SHOW_SELECT = [...KNOWN_SIGNIN_FIELDS];
|
|
62
94
|
const DEFAULT_AUDIT_SHOW_SELECT = [...KNOWN_AUDIT_FIELDS];
|
|
95
|
+
const DEFAULT_PROVISIONING_SHOW_SELECT = [...KNOWN_PROVISIONING_FIELDS];
|
|
63
96
|
export const DEFAULT_DELEGATED_SCOPES = ["https://graph.microsoft.com/AuditLog.Read.All"];
|
|
97
|
+
// Provisioning reads need both directory scopes; the pair is the documented
|
|
98
|
+
// least-privileged delegated set and both names already sit in READ_SCOPES.
|
|
99
|
+
export const DEFAULT_PROVISIONING_SCOPES = [
|
|
100
|
+
"https://graph.microsoft.com/AuditLog.Read.All",
|
|
101
|
+
"https://graph.microsoft.com/Directory.Read.All",
|
|
102
|
+
];
|
|
64
103
|
const TRUNCATE_AT = 500;
|
|
65
104
|
// Graph omits this property without CA-data access instead of failing, so an
|
|
66
105
|
// absent value reports the missing prerequisite rather than an empty result.
|
|
@@ -87,7 +126,7 @@ function fieldList(raw, flag, known, knownList, help) {
|
|
|
87
126
|
}
|
|
88
127
|
return fields;
|
|
89
128
|
}
|
|
90
|
-
function scopesFor(flags, profile, help) {
|
|
129
|
+
function scopesFor(flags, profile, help, defaultScopes = DEFAULT_DELEGATED_SCOPES) {
|
|
91
130
|
if (profile.mode === "application") {
|
|
92
131
|
if (flags.scopes !== undefined) {
|
|
93
132
|
throw new AxiError("Application profiles use the configured Graph .default audience; delegated scopes are unavailable", "VALIDATION_ERROR", [help, "mg-axi profile show --profile <name>"]);
|
|
@@ -95,7 +134,7 @@ function scopesFor(flags, profile, help) {
|
|
|
95
134
|
return undefined;
|
|
96
135
|
}
|
|
97
136
|
if (flags.scopes === undefined)
|
|
98
|
-
return [...
|
|
137
|
+
return [...defaultScopes];
|
|
99
138
|
const scopes = String(flags.scopes)
|
|
100
139
|
.split(",")
|
|
101
140
|
.map(scope => scope.trim())
|
|
@@ -226,10 +265,16 @@ const AUDIT_DENIAL_HINTS = [
|
|
|
226
265
|
"Directory-audit reads need AuditLog.Read.All plus a supported directory role: Reports Reader, Security Administrator or Security Reader for delegated access, or admin-consented AuditLog.Read.All for application access",
|
|
227
266
|
"Audit Graph reporting carries a conservative P1/P2 deployment prerequisite; retention stays a separate constraint",
|
|
228
267
|
];
|
|
268
|
+
const PROVISIONING_DENIAL_HINTS = [
|
|
269
|
+
"Provisioning-log reads need AuditLog.Read.All and Directory.Read.All plus a supported directory role: Global Reader, Reports Reader, Security Administrator, Security Operator or Security Reader for delegated access, or admin-consented AuditLog.Read.All and Directory.Read.All for application access",
|
|
270
|
+
"Provisioning logs need a Microsoft Entra ID P1 or P2 licence on the tenant; retention is 30 days on premium and 7 days on free",
|
|
271
|
+
];
|
|
229
272
|
const SIGNIN_LIST = {
|
|
230
273
|
noun: "sign-in",
|
|
274
|
+
plural: "sign-ins",
|
|
231
275
|
key: "signIns",
|
|
232
276
|
dateField: "createdDateTime",
|
|
277
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
233
278
|
known: SIGNIN_KNOWN,
|
|
234
279
|
knownList: KNOWN_SIGNIN_FIELDS,
|
|
235
280
|
defaultSelect: DEFAULT_SIGNIN_LIST_SELECT,
|
|
@@ -243,8 +288,10 @@ const SIGNIN_LIST = {
|
|
|
243
288
|
};
|
|
244
289
|
const AUDIT_LIST = {
|
|
245
290
|
noun: "directory-audit",
|
|
291
|
+
plural: "directory audits",
|
|
246
292
|
key: "directoryAudits",
|
|
247
293
|
dateField: "activityDateTime",
|
|
294
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
248
295
|
known: AUDIT_KNOWN,
|
|
249
296
|
knownList: KNOWN_AUDIT_FIELDS,
|
|
250
297
|
defaultSelect: DEFAULT_AUDIT_LIST_SELECT,
|
|
@@ -256,6 +303,23 @@ const AUDIT_LIST = {
|
|
|
256
303
|
"0 directory audits matched in this window; widen --since/--until or loosen --filter - the absence of results is the answer, not an error",
|
|
257
304
|
],
|
|
258
305
|
};
|
|
306
|
+
const PROVISIONING_LIST = {
|
|
307
|
+
noun: "provisioning",
|
|
308
|
+
plural: "provisioning events",
|
|
309
|
+
key: "provisioning",
|
|
310
|
+
dateField: "activityDateTime",
|
|
311
|
+
defaultScopes: DEFAULT_PROVISIONING_SCOPES,
|
|
312
|
+
known: PROVISIONING_KNOWN,
|
|
313
|
+
knownList: KNOWN_PROVISIONING_FIELDS,
|
|
314
|
+
defaultSelect: DEFAULT_PROVISIONING_LIST_SELECT,
|
|
315
|
+
unavailable: {},
|
|
316
|
+
denialHints: PROVISIONING_DENIAL_HINTS,
|
|
317
|
+
showHint: "mg-axi entra provisioning show --id <provisioning-id>",
|
|
318
|
+
emptyHints: profileName => [
|
|
319
|
+
`mg-axi entra provisioning list --since <earlier-iso-time> ${profileHint(profileName)}`,
|
|
320
|
+
"0 provisioning events matched in this window; widen --since/--until or loosen --filter - the absence of results is the answer, not an error",
|
|
321
|
+
],
|
|
322
|
+
};
|
|
259
323
|
async function listLogs(shape, session, flags, profile, operation, help, profileName) {
|
|
260
324
|
const cursor = flags.cursor === undefined ? undefined : String(flags.cursor);
|
|
261
325
|
if (cursor !== undefined && !cursor.trim())
|
|
@@ -277,7 +341,7 @@ async function listLogs(shape, session, flags, profile, operation, help, profile
|
|
|
277
341
|
}
|
|
278
342
|
const savedSelect = saved?.$select;
|
|
279
343
|
const { select, fields } = selectedFields(flags, savedSelect === undefined ? shape.defaultSelect : fieldList(savedSelect, "select", shape.known, shape.knownList, help), shape.known, shape.knownList, help);
|
|
280
|
-
const scopes = scopesFor(flags, profile, help);
|
|
344
|
+
const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
|
|
281
345
|
const full = flags.full === true;
|
|
282
346
|
const filter = boundedFilter(shape.dateField, flags, help);
|
|
283
347
|
const query = { $select: select.join(",") };
|
|
@@ -313,21 +377,23 @@ async function listLogs(shape, session, flags, profile, operation, help, profile
|
|
|
313
377
|
if (!result.complete) {
|
|
314
378
|
return {
|
|
315
379
|
[shape.key]: rows,
|
|
316
|
-
|
|
380
|
+
...listTotals(rows.length, result.total, shape.plural, false),
|
|
381
|
+
complete: false,
|
|
382
|
+
reason: result.reason,
|
|
317
383
|
cursor: result.cursor,
|
|
318
384
|
help: [...truncationHints, `Resume losslessly with the same flags plus --cursor - ${profileHint(profileName)} and supply the returned cursor on stdin`, showHint],
|
|
319
385
|
};
|
|
320
386
|
}
|
|
321
|
-
const count = { returned: rows.length, complete: true };
|
|
322
387
|
if (!rows.length) {
|
|
323
|
-
return { [shape.key]: rows,
|
|
388
|
+
return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.plural, true), complete: true, help: shape.emptyHints(profileName) };
|
|
324
389
|
}
|
|
325
|
-
return { [shape.key]: rows,
|
|
390
|
+
return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.plural, true), complete: true, help: [...truncationHints, showHint] };
|
|
326
391
|
}
|
|
327
392
|
const SIGNIN_SHOW = {
|
|
328
393
|
noun: "sign-in",
|
|
329
394
|
key: "signIn",
|
|
330
395
|
param: "signIn-id",
|
|
396
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
331
397
|
known: SIGNIN_KNOWN,
|
|
332
398
|
knownList: KNOWN_SIGNIN_FIELDS,
|
|
333
399
|
defaultSelect: DEFAULT_SIGNIN_SHOW_SELECT,
|
|
@@ -339,6 +405,7 @@ const AUDIT_SHOW = {
|
|
|
339
405
|
noun: "directory-audit",
|
|
340
406
|
key: "directoryAudit",
|
|
341
407
|
param: "directoryAudit-id",
|
|
408
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
342
409
|
known: AUDIT_KNOWN,
|
|
343
410
|
knownList: KNOWN_AUDIT_FIELDS,
|
|
344
411
|
defaultSelect: DEFAULT_AUDIT_SHOW_SELECT,
|
|
@@ -346,9 +413,21 @@ const AUDIT_SHOW = {
|
|
|
346
413
|
denialHints: AUDIT_DENIAL_HINTS,
|
|
347
414
|
idFlag: "<directory-audit-id>",
|
|
348
415
|
};
|
|
416
|
+
const PROVISIONING_SHOW = {
|
|
417
|
+
noun: "provisioning",
|
|
418
|
+
key: "provisioning",
|
|
419
|
+
param: "provisioningObjectSummary-id",
|
|
420
|
+
defaultScopes: DEFAULT_PROVISIONING_SCOPES,
|
|
421
|
+
known: PROVISIONING_KNOWN,
|
|
422
|
+
knownList: KNOWN_PROVISIONING_FIELDS,
|
|
423
|
+
defaultSelect: DEFAULT_PROVISIONING_SHOW_SELECT,
|
|
424
|
+
unavailable: {},
|
|
425
|
+
denialHints: PROVISIONING_DENIAL_HINTS,
|
|
426
|
+
idFlag: "<provisioning-id>",
|
|
427
|
+
};
|
|
349
428
|
async function showLog(shape, session, flags, profile, operation, help, profileName) {
|
|
350
429
|
const { select, fields } = selectedFields(flags, shape.defaultSelect, shape.known, shape.knownList, help);
|
|
351
|
-
const scopes = scopesFor(flags, profile, help);
|
|
430
|
+
const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
|
|
352
431
|
const full = flags.full === true;
|
|
353
432
|
const raw = await withGuidance(shape.denialHints, () => session.execute({
|
|
354
433
|
profile,
|
|
@@ -379,3 +458,9 @@ export async function listDirectoryAudits(session, flags, profile, operation, he
|
|
|
379
458
|
export async function showDirectoryAudit(session, flags, profile, operation, help, profileName) {
|
|
380
459
|
return showLog(AUDIT_SHOW, session, flags, profile, operation, help, profileName);
|
|
381
460
|
}
|
|
461
|
+
export async function listProvisioning(session, flags, profile, operation, help, profileName) {
|
|
462
|
+
return listLogs(PROVISIONING_LIST, session, flags, profile, operation, help, profileName);
|
|
463
|
+
}
|
|
464
|
+
export async function showProvisioning(session, flags, profile, operation, help, profileName) {
|
|
465
|
+
return showLog(PROVISIONING_SHOW, session, flags, profile, operation, help, profileName);
|
|
466
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { listTotals } from "./list-totals.js";
|
|
2
3
|
// READ-04: the Entra authentication-method and registration-report read
|
|
3
4
|
// mapping behind `mg-axi entra user authentication-method list` and
|
|
4
5
|
// `mg-axi entra registration list`. Operation construction stays beside its
|
|
@@ -203,6 +204,7 @@ const REPORT_DENIAL_HINTS = [
|
|
|
203
204
|
const METHOD_LIST = {
|
|
204
205
|
command: "entra user authentication-method list",
|
|
205
206
|
key: "authenticationMethods",
|
|
207
|
+
noun: "authentication methods",
|
|
206
208
|
known: KNOWN_METHODS,
|
|
207
209
|
knownList: KNOWN_METHOD_FIELDS,
|
|
208
210
|
defaultSelect: DEFAULT_METHOD_SELECT,
|
|
@@ -218,6 +220,7 @@ const METHOD_LIST = {
|
|
|
218
220
|
const REPORT_LIST = {
|
|
219
221
|
command: "entra registration list",
|
|
220
222
|
key: "registrationDetails",
|
|
223
|
+
noun: "registration rows",
|
|
221
224
|
known: KNOWN_REPORT,
|
|
222
225
|
knownList: KNOWN_REPORT_FIELDS,
|
|
223
226
|
defaultSelect: DEFAULT_REPORT_SELECT,
|
|
@@ -274,16 +277,17 @@ async function listCollection(shape, session, flags, profile, operation, help, p
|
|
|
274
277
|
if (!result.complete) {
|
|
275
278
|
return {
|
|
276
279
|
[shape.key]: rows,
|
|
277
|
-
|
|
280
|
+
...listTotals(rows.length, result.total, shape.noun, false),
|
|
281
|
+
complete: false,
|
|
282
|
+
reason: result.reason,
|
|
278
283
|
cursor: result.cursor,
|
|
279
284
|
help: [...truncationHints, `Resume losslessly with the same flags plus --cursor <cursor-from-output> ${profileHint(profileName)}`, ...standing],
|
|
280
285
|
};
|
|
281
286
|
}
|
|
282
|
-
const count = { returned: rows.length, complete: true };
|
|
283
287
|
if (!rows.length) {
|
|
284
|
-
return { [shape.key]: rows,
|
|
288
|
+
return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.noun, true), complete: true, help: [shape.emptyNote, ...standing] };
|
|
285
289
|
}
|
|
286
|
-
return { [shape.key]: rows,
|
|
290
|
+
return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.noun, true), complete: true, help: [...truncationHints, ...standing] };
|
|
287
291
|
}
|
|
288
292
|
export async function listAuthenticationMethods(session, flags, profile, operation, help, profileName) {
|
|
289
293
|
const user = String(flags.user);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { listTotals } from "./list-totals.js";
|
|
2
3
|
// EXT-01 certificate-auth subfamily: the read mapping behind
|
|
3
4
|
// `mg-axi entra certificate-auth-pki list/show/count` and
|
|
4
5
|
// `mg-axi entra certificate-authority list/show/count`. Operation
|
|
@@ -242,16 +243,17 @@ export async function listCertificateAuthPkis(session, flags, profile, operation
|
|
|
242
243
|
if (!result.complete) {
|
|
243
244
|
return {
|
|
244
245
|
certificateAuthPkis: pkis,
|
|
245
|
-
|
|
246
|
+
...listTotals(pkis.length, result.total, "PKI configurations", false),
|
|
247
|
+
complete: false,
|
|
248
|
+
reason: result.reason,
|
|
246
249
|
cursor: result.cursor,
|
|
247
250
|
help: [...truncationHints, resumeHint(profileName), showHint, EMPTY_PKI_NOTE],
|
|
248
251
|
};
|
|
249
252
|
}
|
|
250
|
-
const count = { returned: pkis.length, complete: true };
|
|
251
253
|
if (!pkis.length) {
|
|
252
|
-
return { certificateAuthPkis: pkis,
|
|
254
|
+
return { certificateAuthPkis: pkis, ...listTotals(pkis.length, result.total, "PKI configurations", true), complete: true, help: [EMPTY_PKI_NOTE] };
|
|
253
255
|
}
|
|
254
|
-
return { certificateAuthPkis: pkis,
|
|
256
|
+
return { certificateAuthPkis: pkis, ...listTotals(pkis.length, result.total, "PKI configurations", true), complete: true, help: [...truncationHints, showHint, EMPTY_PKI_NOTE] };
|
|
255
257
|
}
|
|
256
258
|
export async function showCertificateAuthPki(session, flags, profile, operation, help, profileName) {
|
|
257
259
|
const { select, fields } = selectedFields(flags, KNOWN_PKIS, KNOWN_PKI_FIELDS, DEFAULT_PKI_SHOW_SELECT, help, "PKI");
|
|
@@ -324,20 +326,22 @@ export async function listCertificateAuthorities(session, flags, profile, operat
|
|
|
324
326
|
if (!result.complete) {
|
|
325
327
|
return {
|
|
326
328
|
certificateAuthorities: authorities,
|
|
327
|
-
|
|
329
|
+
...listTotals(authorities.length, result.total, "certificate authorities", false),
|
|
330
|
+
complete: false,
|
|
331
|
+
reason: result.reason,
|
|
328
332
|
cursor: result.cursor,
|
|
329
333
|
help: [...truncationHints, resumeHint(profileName), showHint, CERT_BLOB_NOTE],
|
|
330
334
|
};
|
|
331
335
|
}
|
|
332
|
-
const count = { returned: authorities.length, complete: true };
|
|
333
336
|
if (!authorities.length) {
|
|
334
337
|
return {
|
|
335
338
|
certificateAuthorities: authorities,
|
|
336
|
-
|
|
339
|
+
...listTotals(authorities.length, result.total, "certificate authorities", true),
|
|
340
|
+
complete: true,
|
|
337
341
|
help: ["0 certificate authorities matched; the absence of results is the answer, not an error", CERT_BLOB_NOTE],
|
|
338
342
|
};
|
|
339
343
|
}
|
|
340
|
-
return { certificateAuthorities: authorities,
|
|
344
|
+
return { certificateAuthorities: authorities, ...listTotals(authorities.length, result.total, "certificate authorities", true), complete: true, help: [...truncationHints, showHint, CERT_BLOB_NOTE] };
|
|
341
345
|
}
|
|
342
346
|
export async function showCertificateAuthority(session, flags, profile, operation, help, profileName) {
|
|
343
347
|
const { select, fields } = selectedFields(flags, KNOWN_CAS, KNOWN_CA_FIELDS, DEFAULT_CA_SHOW_SELECT, help, "certificate-authority");
|