@knowttl/mg-axi 0.1.2 → 0.1.4
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 +127 -45
- package/dist/api.js +119 -0
- package/dist/app-auth.js +5 -1
- package/dist/catalogue.js +311 -3
- package/dist/cli.js +44 -12
- package/dist/docs.js +139 -47
- package/dist/entra-apps.js +158 -6
- package/dist/entra-audit-logs.js +93 -12
- package/dist/entra-conditional-access.js +416 -11
- 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-identity-providers.js +7 -4
- package/dist/entra-lifecycle-workflows.js +16 -4
- package/dist/entra-multi-tenant-organization.js +7 -4
- package/dist/entra-on-premises-synchronization.js +6 -4
- package/dist/entra-risk-prevention.js +10 -4
- package/dist/entra-risk.js +11 -4
- package/dist/entra-roles.js +194 -8
- package/dist/entra-terms-of-use.js +13 -8
- package/dist/graph-session.js +1 -0
- package/dist/msal-app-provider.js +96 -13
- package/dist/profiles.js +55 -9
- package/dist/setup.js +5 -2
- package/docs/coverage.md +721 -654
- package/inventory/operations.json +2 -2
- package/inventory/schema.json +1 -1
- package/package.json +1 -1
- package/skills/mg-axi/SKILL.md +34 -1
package/dist/entra-apps.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { KNOWN_APP_ROLE_FIELDS } from "./entra-grants.js";
|
|
2
3
|
import { listTotals } from "./list-totals.js";
|
|
3
4
|
import { SAFE_CREDENTIAL_FIELDS } from "./graph-session.js";
|
|
4
5
|
// READ-07: the Entra application and service-principal read mapping behind
|
|
5
6
|
// `mg-axi entra application list/show`, `mg-axi entra service-principal
|
|
6
|
-
// 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
|
|
7
10
|
// construction stays beside its command; the shared session owns URLs,
|
|
8
11
|
// credentials, paging, retries and error translation, and the SDK owns TOON
|
|
9
12
|
// rendering. This module only maps flags to session calls and projects rows
|
|
@@ -14,14 +17,18 @@ import { SAFE_CREDENTIAL_FIELDS } from "./graph-session.js";
|
|
|
14
17
|
// servicePrincipal-list-owners operation documentation on 2026-10-04.
|
|
15
18
|
// Delegated and application reads take Application.Read.All; the object id
|
|
16
19
|
// (`id`) is never conflated with the client ID (`appId`) - both ride in the
|
|
17
|
-
// compact list defaults so callers can see the distinction.
|
|
18
|
-
// (
|
|
19
|
-
// 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.
|
|
20
27
|
//
|
|
21
28
|
// Credential safety belongs to the shared session's SAFE_CREDENTIAL_FIELDS
|
|
22
29
|
// projection, applied before collection buffering and again on cursor decode.
|
|
23
30
|
// Local projection uses that same allowlist for the displayed view.
|
|
24
|
-
// The
|
|
31
|
+
// The thirteen GET operations below are the only ones this module ever binds;
|
|
25
32
|
// secret-minting routes (addPassword, addKey) and every write stay refused before
|
|
26
33
|
// credentials via checkReadOperation.
|
|
27
34
|
// Every application property this slice may request or display. Anything
|
|
@@ -52,6 +59,20 @@ export const KNOWN_SP_FIELDS = [
|
|
|
52
59
|
"passwordCredentials",
|
|
53
60
|
];
|
|
54
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);
|
|
55
76
|
// Owner rows are directoryObjects of mixed types; richer per-type fields
|
|
56
77
|
// need single-object reads, so only these stay selectable.
|
|
57
78
|
export const KNOWN_OWNER_FIELDS = ["id", "displayName", "mail"];
|
|
@@ -68,6 +89,17 @@ const DEFAULT_APP_SHOW_SELECT = [...KNOWN_APP_FIELDS];
|
|
|
68
89
|
const DEFAULT_SP_SHOW_SELECT = [...KNOWN_SP_FIELDS];
|
|
69
90
|
// Compact owner rows: identifier, display name and address.
|
|
70
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];
|
|
71
103
|
// Application.Read.All covers applications, service principals and owners in
|
|
72
104
|
// both modes; delegated reads default to it unless --scopes overrides it.
|
|
73
105
|
export const DEFAULT_DELEGATED_SCOPES = ["https://graph.microsoft.com/Application.Read.All"];
|
|
@@ -78,9 +110,16 @@ const READ_OPERATIONS = {
|
|
|
78
110
|
"GET:/applications": "application",
|
|
79
111
|
"GET:/applications/{application-id}": "application",
|
|
80
112
|
"GET:/applications/{application-id}/owners": "application",
|
|
113
|
+
"GET:/applications/{application-id}/federatedIdentityCredentials": "application",
|
|
114
|
+
"GET:/applications/{application-id}/federatedIdentityCredentials/{federatedIdentityCredential-id}": "application",
|
|
81
115
|
"GET:/servicePrincipals": "service-principal",
|
|
82
116
|
"GET:/servicePrincipals/{servicePrincipal-id}": "service-principal",
|
|
83
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",
|
|
84
123
|
};
|
|
85
124
|
function checkReadOperation(operation, help) {
|
|
86
125
|
// Version stays the session's decision: beta reads remain preview-gated
|
|
@@ -88,7 +127,7 @@ function checkReadOperation(operation, help) {
|
|
|
88
127
|
// fabricated secret-minting or write route can never reach credentials.
|
|
89
128
|
const route = `${operation.method}:${operation.path}`;
|
|
90
129
|
if (operation.method !== "GET" || !Object.hasOwn(READ_OPERATIONS, route)) {
|
|
91
|
-
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", [
|
|
92
131
|
help,
|
|
93
132
|
"Secret-minting routes (addPassword, addKey) and writes are never constructed here; consent grants belong to READ-08",
|
|
94
133
|
]);
|
|
@@ -377,3 +416,116 @@ export async function listApplicationOwners(session, flags, profile, operation,
|
|
|
377
416
|
export async function listServicePrincipalOwners(session, flags, profile, operation, help, profileName) {
|
|
378
417
|
return listOwners(session, flags, profile, operation, help, profileName, "entra service-principal owner list", "servicePrincipal-id", "service-principal");
|
|
379
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,18 +1,24 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
2
|
import { listTotals } from "./list-totals.js";
|
|
3
|
-
// READ-05: the Entra sign-in
|
|
4
|
-
// `mg-axi entra sign-in list/show
|
|
5
|
-
// list/show
|
|
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
|
|
6
7
|
// session owns URLs, credentials, paging, retries and error translation, and
|
|
7
8
|
// the SDK owns TOON rendering. This module only maps flags to session calls,
|
|
8
9
|
// bounds every collection query in time, and projects rows for compact output.
|
|
9
10
|
//
|
|
10
11
|
// Reviewed against the v1.0 signin-list and directoryaudit-list operation
|
|
11
|
-
// documentation on 2026-10-04
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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
|
|
16
22
|
// without a CA-data permission/role, so an absent property is reported as
|
|
17
23
|
// unavailable rather than invented. Sign-in/audit Graph reporting carries a
|
|
18
24
|
// conservative P1/P2 deployment prerequisite; retention and premium fields
|
|
@@ -55,13 +61,45 @@ export const KNOWN_AUDIT_FIELDS = [
|
|
|
55
61
|
];
|
|
56
62
|
const SIGNIN_KNOWN = new Set(KNOWN_SIGNIN_FIELDS);
|
|
57
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);
|
|
58
88
|
// Compact list rows: identifier, time, who/what happened, and outcome.
|
|
59
89
|
const DEFAULT_SIGNIN_LIST_SELECT = ["id", "createdDateTime", "userPrincipalName", "appDisplayName"];
|
|
60
90
|
const DEFAULT_AUDIT_LIST_SELECT = ["id", "activityDateTime", "activityDisplayName", "result"];
|
|
91
|
+
const DEFAULT_PROVISIONING_LIST_SELECT = ["id", "activityDateTime", "action", "provisioningStatusInfo"];
|
|
61
92
|
// Show rows: the full reviewed server set for one object.
|
|
62
93
|
const DEFAULT_SIGNIN_SHOW_SELECT = [...KNOWN_SIGNIN_FIELDS];
|
|
63
94
|
const DEFAULT_AUDIT_SHOW_SELECT = [...KNOWN_AUDIT_FIELDS];
|
|
95
|
+
const DEFAULT_PROVISIONING_SHOW_SELECT = [...KNOWN_PROVISIONING_FIELDS];
|
|
64
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
|
+
];
|
|
65
103
|
const TRUNCATE_AT = 500;
|
|
66
104
|
// Graph omits this property without CA-data access instead of failing, so an
|
|
67
105
|
// absent value reports the missing prerequisite rather than an empty result.
|
|
@@ -88,7 +126,7 @@ function fieldList(raw, flag, known, knownList, help) {
|
|
|
88
126
|
}
|
|
89
127
|
return fields;
|
|
90
128
|
}
|
|
91
|
-
function scopesFor(flags, profile, help) {
|
|
129
|
+
function scopesFor(flags, profile, help, defaultScopes = DEFAULT_DELEGATED_SCOPES) {
|
|
92
130
|
if (profile.mode === "application") {
|
|
93
131
|
if (flags.scopes !== undefined) {
|
|
94
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>"]);
|
|
@@ -96,7 +134,7 @@ function scopesFor(flags, profile, help) {
|
|
|
96
134
|
return undefined;
|
|
97
135
|
}
|
|
98
136
|
if (flags.scopes === undefined)
|
|
99
|
-
return [...
|
|
137
|
+
return [...defaultScopes];
|
|
100
138
|
const scopes = String(flags.scopes)
|
|
101
139
|
.split(",")
|
|
102
140
|
.map(scope => scope.trim())
|
|
@@ -227,11 +265,16 @@ const AUDIT_DENIAL_HINTS = [
|
|
|
227
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",
|
|
228
266
|
"Audit Graph reporting carries a conservative P1/P2 deployment prerequisite; retention stays a separate constraint",
|
|
229
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
|
+
];
|
|
230
272
|
const SIGNIN_LIST = {
|
|
231
273
|
noun: "sign-in",
|
|
232
274
|
plural: "sign-ins",
|
|
233
275
|
key: "signIns",
|
|
234
276
|
dateField: "createdDateTime",
|
|
277
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
235
278
|
known: SIGNIN_KNOWN,
|
|
236
279
|
knownList: KNOWN_SIGNIN_FIELDS,
|
|
237
280
|
defaultSelect: DEFAULT_SIGNIN_LIST_SELECT,
|
|
@@ -248,6 +291,7 @@ const AUDIT_LIST = {
|
|
|
248
291
|
plural: "directory audits",
|
|
249
292
|
key: "directoryAudits",
|
|
250
293
|
dateField: "activityDateTime",
|
|
294
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
251
295
|
known: AUDIT_KNOWN,
|
|
252
296
|
knownList: KNOWN_AUDIT_FIELDS,
|
|
253
297
|
defaultSelect: DEFAULT_AUDIT_LIST_SELECT,
|
|
@@ -259,6 +303,23 @@ const AUDIT_LIST = {
|
|
|
259
303
|
"0 directory audits matched in this window; widen --since/--until or loosen --filter - the absence of results is the answer, not an error",
|
|
260
304
|
],
|
|
261
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
|
+
};
|
|
262
323
|
async function listLogs(shape, session, flags, profile, operation, help, profileName) {
|
|
263
324
|
const cursor = flags.cursor === undefined ? undefined : String(flags.cursor);
|
|
264
325
|
if (cursor !== undefined && !cursor.trim())
|
|
@@ -280,7 +341,7 @@ async function listLogs(shape, session, flags, profile, operation, help, profile
|
|
|
280
341
|
}
|
|
281
342
|
const savedSelect = saved?.$select;
|
|
282
343
|
const { select, fields } = selectedFields(flags, savedSelect === undefined ? shape.defaultSelect : fieldList(savedSelect, "select", shape.known, shape.knownList, help), shape.known, shape.knownList, help);
|
|
283
|
-
const scopes = scopesFor(flags, profile, help);
|
|
344
|
+
const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
|
|
284
345
|
const full = flags.full === true;
|
|
285
346
|
const filter = boundedFilter(shape.dateField, flags, help);
|
|
286
347
|
const query = { $select: select.join(",") };
|
|
@@ -332,6 +393,7 @@ const SIGNIN_SHOW = {
|
|
|
332
393
|
noun: "sign-in",
|
|
333
394
|
key: "signIn",
|
|
334
395
|
param: "signIn-id",
|
|
396
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
335
397
|
known: SIGNIN_KNOWN,
|
|
336
398
|
knownList: KNOWN_SIGNIN_FIELDS,
|
|
337
399
|
defaultSelect: DEFAULT_SIGNIN_SHOW_SELECT,
|
|
@@ -343,6 +405,7 @@ const AUDIT_SHOW = {
|
|
|
343
405
|
noun: "directory-audit",
|
|
344
406
|
key: "directoryAudit",
|
|
345
407
|
param: "directoryAudit-id",
|
|
408
|
+
defaultScopes: DEFAULT_DELEGATED_SCOPES,
|
|
346
409
|
known: AUDIT_KNOWN,
|
|
347
410
|
knownList: KNOWN_AUDIT_FIELDS,
|
|
348
411
|
defaultSelect: DEFAULT_AUDIT_SHOW_SELECT,
|
|
@@ -350,9 +413,21 @@ const AUDIT_SHOW = {
|
|
|
350
413
|
denialHints: AUDIT_DENIAL_HINTS,
|
|
351
414
|
idFlag: "<directory-audit-id>",
|
|
352
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
|
+
};
|
|
353
428
|
async function showLog(shape, session, flags, profile, operation, help, profileName) {
|
|
354
429
|
const { select, fields } = selectedFields(flags, shape.defaultSelect, shape.known, shape.knownList, help);
|
|
355
|
-
const scopes = scopesFor(flags, profile, help);
|
|
430
|
+
const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
|
|
356
431
|
const full = flags.full === true;
|
|
357
432
|
const raw = await withGuidance(shape.denialHints, () => session.execute({
|
|
358
433
|
profile,
|
|
@@ -383,3 +458,9 @@ export async function listDirectoryAudits(session, flags, profile, operation, he
|
|
|
383
458
|
export async function showDirectoryAudit(session, flags, profile, operation, help, profileName) {
|
|
384
459
|
return showLog(AUDIT_SHOW, session, flags, profile, operation, help, profileName);
|
|
385
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
|
+
}
|