@knowttl/mg-axi 0.1.0
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 +1138 -0
- package/dist/api.js +953 -0
- package/dist/app-auth.js +47 -0
- package/dist/auth.js +59 -0
- package/dist/bin/mg-axi.js +7 -0
- package/dist/catalogue.js +2191 -0
- package/dist/cli.js +1314 -0
- package/dist/docs.js +559 -0
- package/dist/doctor.js +75 -0
- package/dist/entra-access-reviews.js +425 -0
- package/dist/entra-apps.js +374 -0
- package/dist/entra-audit-logs.js +381 -0
- package/dist/entra-auth-methods.js +296 -0
- package/dist/entra-ca-policy-update.js +353 -0
- package/dist/entra-certificate-auth.js +384 -0
- package/dist/entra-conditional-access.js +317 -0
- package/dist/entra-contacts.js +827 -0
- package/dist/entra-contracts.js +252 -0
- package/dist/entra-custom-security-attributes.js +464 -0
- package/dist/entra-data-policy-operations.js +274 -0
- package/dist/entra-delegated-admin.js +468 -0
- package/dist/entra-deleted-items.js +461 -0
- package/dist/entra-directory-objects.js +274 -0
- package/dist/entra-directory.js +411 -0
- package/dist/entra-domains.js +399 -0
- package/dist/entra-entitlement-management.js +645 -0
- package/dist/entra-federation-configurations.js +290 -0
- package/dist/entra-grants.js +272 -0
- package/dist/entra-group-lifecycle.js +349 -0
- package/dist/entra-group-member-add.js +278 -0
- package/dist/entra-groups.js +336 -0
- package/dist/entra-identity-providers.js +297 -0
- package/dist/entra-lifecycle-workflows.js +673 -0
- package/dist/entra-multi-tenant-organization.js +309 -0
- package/dist/entra-on-premises-synchronization.js +231 -0
- package/dist/entra-organization.js +398 -0
- package/dist/entra-risk-dismiss.js +191 -0
- package/dist/entra-risk.js +404 -0
- package/dist/entra-roles.js +310 -0
- package/dist/entra-subscriptions.js +289 -0
- package/dist/entra-tenant-information.js +152 -0
- package/dist/entra-terms-of-use.js +380 -0
- package/dist/entra-user-revoke-sessions.js +170 -0
- package/dist/entra-user-update.js +170 -0
- package/dist/entra-users.js +211 -0
- package/dist/graph-session.js +1034 -0
- package/dist/msal-app-provider.js +42 -0
- package/dist/msal-provider.js +82 -0
- package/dist/mutations.js +529 -0
- package/dist/profiles.js +139 -0
- package/dist/setup.js +116 -0
- package/dist/version.js +2 -0
- package/docs/coverage.md +1028 -0
- package/inventory/operations.json +4 -0
- package/inventory/schema.json +71 -0
- package/package.json +32 -0
- package/skills/mg-axi/SKILL.md +293 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
// EXT-01 certificate-auth subfamily: the read mapping behind
|
|
3
|
+
// `mg-axi entra certificate-auth-pki list/show/count` and
|
|
4
|
+
// `mg-axi entra certificate-authority list/show/count`. Operation
|
|
5
|
+
// construction stays beside its command; the shared session owns URLs,
|
|
6
|
+
// credentials, paging, retries and error translation, and the SDK owns TOON
|
|
7
|
+
// rendering. This module only maps flags to session calls and projects rows
|
|
8
|
+
// for compact output.
|
|
9
|
+
//
|
|
10
|
+
// Reviewed against the v1.0
|
|
11
|
+
// publickeyinfrastructureroot-list-certificatebasedauthconfigurations,
|
|
12
|
+
// certificatebasedauthpki-get,
|
|
13
|
+
// certificatebasedauthpki-list-certificateauthorities and
|
|
14
|
+
// certificateauthoritydetail-get operation documentation and the
|
|
15
|
+
// certificateBasedAuthPki and certificateAuthorityDetail resource references
|
|
16
|
+
// on 2026-10-05. Every read takes D/A PublicKeyInfrastructure.Read.All;
|
|
17
|
+
// delegated callers additionally need Privileged Authentication Administrator
|
|
18
|
+
// or Authentication Administrator. Personal Microsoft accounts are not
|
|
19
|
+
// supported. No P1/P2 prerequisite is stated for these reads. The PKI list
|
|
20
|
+
// documents $filter (eq, startswith), $top, $orderby and $count, so --filter
|
|
21
|
+
// passes through as plain $filter with no ConsistencyLevel contract; the
|
|
22
|
+
// single GETs document $select only. The root /certificateBasedAuthConfiguration
|
|
23
|
+
// reads carry no documented v1.0 operation contract (the documented
|
|
24
|
+
// certificateBasedAuthConfiguration contract is org-scoped) and stay out,
|
|
25
|
+
// as do the org-scoped certificate-auth reads, beta routes, the upload
|
|
26
|
+
// action and every mutation. No mutation lives here.
|
|
27
|
+
//
|
|
28
|
+
// Secrecy by construction: certificate-authority entries carry public
|
|
29
|
+
// certificates only, but the base64 `certificate` blob (up to 8 KB per CA
|
|
30
|
+
// file) is omitted from every default select and returned only on an
|
|
31
|
+
// explicit --select naming it, still under the truncation marker with a
|
|
32
|
+
// --full escape hatch. No private key material exists on these resources.
|
|
33
|
+
// Every PKI property this slice may request or display, matching the
|
|
34
|
+
// reviewed resource. Anything else fails before credentials.
|
|
35
|
+
export const KNOWN_PKI_FIELDS = [
|
|
36
|
+
"id",
|
|
37
|
+
"deletedDateTime",
|
|
38
|
+
"displayName",
|
|
39
|
+
"status",
|
|
40
|
+
"statusDetails",
|
|
41
|
+
"lastModifiedDateTime",
|
|
42
|
+
];
|
|
43
|
+
const KNOWN_PKIS = new Set(KNOWN_PKI_FIELDS);
|
|
44
|
+
// Every certificate-authority property this slice may request or display,
|
|
45
|
+
// matching the reviewed resource. `certificate` is the public CA key: it is
|
|
46
|
+
// selectable only through an explicit --select and never rides a default.
|
|
47
|
+
export const KNOWN_CA_FIELDS = [
|
|
48
|
+
"id",
|
|
49
|
+
"deletedDateTime",
|
|
50
|
+
"certificateAuthorityType",
|
|
51
|
+
"certificate",
|
|
52
|
+
"displayName",
|
|
53
|
+
"issuer",
|
|
54
|
+
"issuerSubjectKeyIdentifier",
|
|
55
|
+
"createdDateTime",
|
|
56
|
+
"expirationDateTime",
|
|
57
|
+
"thumbprint",
|
|
58
|
+
"certificateRevocationListUrl",
|
|
59
|
+
"deltacertificateRevocationListUrl",
|
|
60
|
+
"isIssuerHintEnabled",
|
|
61
|
+
];
|
|
62
|
+
const KNOWN_CAS = new Set(KNOWN_CA_FIELDS);
|
|
63
|
+
// Compact PKI rows: identifier, name and the async upload/delete status
|
|
64
|
+
// that decides whether the authorities below are ready.
|
|
65
|
+
const DEFAULT_PKI_LIST_SELECT = ["id", "displayName", "status"];
|
|
66
|
+
// Show rows: the full reviewed PKI set.
|
|
67
|
+
const DEFAULT_PKI_SHOW_SELECT = [...KNOWN_PKI_FIELDS];
|
|
68
|
+
// Compact CA rows: identifier, name, kind and expiry; the thumbprint and
|
|
69
|
+
// issuer name the authority on the show view.
|
|
70
|
+
const DEFAULT_CA_LIST_SELECT = ["id", "displayName", "certificateAuthorityType", "expirationDateTime"];
|
|
71
|
+
// Show rows: the full reviewed CA set except the public-certificate blob,
|
|
72
|
+
// which needs an explicit --select naming it.
|
|
73
|
+
const DEFAULT_CA_SHOW_SELECT = KNOWN_CA_FIELDS.filter(field => field !== "certificate");
|
|
74
|
+
// Delegated defaults are operation-specific; application profiles use their
|
|
75
|
+
// configured .default audience and reject --scopes.
|
|
76
|
+
export const DEFAULT_CERT_AUTH_SCOPES = ["https://graph.microsoft.com/PublicKeyInfrastructure.Read.All"];
|
|
77
|
+
const TRUNCATE_AT = 500;
|
|
78
|
+
function fieldList(raw, known, knownList, flag, help, noun) {
|
|
79
|
+
const parts = String(raw)
|
|
80
|
+
.split(",")
|
|
81
|
+
.map(part => part.trim())
|
|
82
|
+
.filter(part => part.length > 0);
|
|
83
|
+
if (!parts.length)
|
|
84
|
+
throw new AxiError(`--${flag} needs at least one property`, "VALIDATION_ERROR", [help]);
|
|
85
|
+
const fields = [];
|
|
86
|
+
for (const part of parts) {
|
|
87
|
+
if (!known.has(part)) {
|
|
88
|
+
throw new AxiError(`Unknown certificate-auth property ${part} in --${flag}`, "VALIDATION_ERROR", [
|
|
89
|
+
help,
|
|
90
|
+
`Known ${noun} properties: ${knownList.join(", ")}`,
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
if (!fields.includes(part))
|
|
94
|
+
fields.push(part);
|
|
95
|
+
}
|
|
96
|
+
return fields;
|
|
97
|
+
}
|
|
98
|
+
function scopesFor(flags, defaults, profile, help) {
|
|
99
|
+
if (profile.mode === "application") {
|
|
100
|
+
if (flags.scopes !== undefined) {
|
|
101
|
+
throw new AxiError("Application profiles use the configured Graph .default audience; delegated scopes are unavailable", "VALIDATION_ERROR", [help, "mg-axi profile show --profile <name>"]);
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
if (flags.scopes === undefined)
|
|
106
|
+
return [...defaults];
|
|
107
|
+
const scopes = String(flags.scopes)
|
|
108
|
+
.split(",")
|
|
109
|
+
.map(scope => scope.trim())
|
|
110
|
+
.filter(scope => scope.length > 0);
|
|
111
|
+
return scopes;
|
|
112
|
+
}
|
|
113
|
+
function truncateValue(value, full) {
|
|
114
|
+
const truncate = (text) => full || text.length <= TRUNCATE_AT
|
|
115
|
+
? { value: text, truncated: false }
|
|
116
|
+
: { value: `${text.slice(0, TRUNCATE_AT)}... (truncated, ${text.length} chars total)`, truncated: true };
|
|
117
|
+
if (typeof value === "string")
|
|
118
|
+
return truncate(value);
|
|
119
|
+
if (Array.isArray(value) && value.every(entry => typeof entry === "string")) {
|
|
120
|
+
const projected = value.map(truncate);
|
|
121
|
+
return { value: projected.map(entry => entry.value), truncated: projected.some(entry => entry.truncated) };
|
|
122
|
+
}
|
|
123
|
+
return { value, truncated: false };
|
|
124
|
+
}
|
|
125
|
+
// Local projection preserves Graph's null/missing distinction: an explicit
|
|
126
|
+
// null stays null, an absent property stays absent and is never synthesized.
|
|
127
|
+
function project(row, fields, full) {
|
|
128
|
+
const source = row !== null && typeof row === "object" && !Array.isArray(row) ? row : {};
|
|
129
|
+
const projected = {};
|
|
130
|
+
let truncated = false;
|
|
131
|
+
for (const field of fields) {
|
|
132
|
+
if (!Object.hasOwn(source, field))
|
|
133
|
+
continue;
|
|
134
|
+
const result = truncateValue(source[field], full);
|
|
135
|
+
projected[field] = result.value;
|
|
136
|
+
truncated = truncated || result.truncated;
|
|
137
|
+
}
|
|
138
|
+
return { row: projected, truncated };
|
|
139
|
+
}
|
|
140
|
+
function selectedFields(flags, known, knownList, defaults, help, noun) {
|
|
141
|
+
const select = flags.select === undefined ? [...defaults] : fieldList(flags.select, known, knownList, "select", help, noun);
|
|
142
|
+
const fields = flags.fields === undefined ? [...select] : fieldList(flags.fields, known, knownList, "fields", help, noun);
|
|
143
|
+
const missing = fields.find(field => !select.includes(field));
|
|
144
|
+
if (missing) {
|
|
145
|
+
throw new AxiError(`--fields ${missing} was not fetched; request it with --select`, "VALIDATION_ERROR", [help]);
|
|
146
|
+
}
|
|
147
|
+
return { select, fields };
|
|
148
|
+
}
|
|
149
|
+
function profileHint(profileName) {
|
|
150
|
+
return `--profile ${shellValue(profileName)}`;
|
|
151
|
+
}
|
|
152
|
+
function shellValue(value) {
|
|
153
|
+
return /^[A-Za-z0-9_.,:/@=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
154
|
+
}
|
|
155
|
+
function fullHint(command, flags, profileName) {
|
|
156
|
+
const args = Object.entries({ ...flags, profile: profileName, full: true })
|
|
157
|
+
.map(([name, value]) => value === true ? `--${name}` : `--${name} ${shellValue(String(value))}`);
|
|
158
|
+
return `mg-axi ${command} ${args.join(" ")}`;
|
|
159
|
+
}
|
|
160
|
+
// Denials carry the session's generic grant/role/licence cause; each
|
|
161
|
+
// operation adds the scope, roles and licensing that actually unlock it,
|
|
162
|
+
// because a 403 alone never says which prerequisite is missing.
|
|
163
|
+
function withGuidance(hints, run) {
|
|
164
|
+
return run().catch(error => {
|
|
165
|
+
if (error instanceof AxiError && error.code === "GRAPH_ERROR" && /\(403\)/.test(error.message)) {
|
|
166
|
+
throw new AxiError(error.message, "GRAPH_ERROR", [...hints, ...error.suggestions]);
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const CERT_AUTH_DENIAL_HINTS = [
|
|
172
|
+
"Certificate-auth reads need PublicKeyInfrastructure.Read.All plus a supported Entra role for delegated access (Privileged Authentication Administrator or Authentication Administrator), or admin-consented PublicKeyInfrastructure.Read.All for application access",
|
|
173
|
+
"Personal Microsoft accounts are not supported for certificate-auth reads",
|
|
174
|
+
"No P1/P2 prerequisite is stated for certificate-auth reads; never diagnose licence solely from HTTP 403",
|
|
175
|
+
];
|
|
176
|
+
const EMPTY_PKI_NOTE = "An empty PKI list may mean certificate-based authentication is not configured in the tenant; the absence of rows is the answer, not an error";
|
|
177
|
+
const CERT_BLOB_NOTE = "CA public-certificate blobs are omitted by default; request certificate explicitly with --select certificate";
|
|
178
|
+
// Restoring the saved select/filter keeps cursor resumes lossless when
|
|
179
|
+
// --select or --filter is omitted. Both collections document $filter, so
|
|
180
|
+
// --filter passes through as plain $filter with no ConsistencyLevel
|
|
181
|
+
// contract; strict input validation still refuses unknown flags before
|
|
182
|
+
// credentials.
|
|
183
|
+
function collectionCommon(session, flags, known, knownList, defaults, noun, operation, help, profile) {
|
|
184
|
+
const cursor = flags.cursor === undefined ? undefined : String(flags.cursor);
|
|
185
|
+
if (cursor !== undefined && !cursor.trim())
|
|
186
|
+
throw new AxiError("--cursor needs the opaque cursor from a partial result", "VALIDATION_ERROR", [help]);
|
|
187
|
+
const saved = cursor === undefined ? undefined : session.cursorQuery(operation, cursor);
|
|
188
|
+
const { select, fields } = selectedFields(flags, known, knownList, saved?.["$select"] === undefined ? defaults : fieldList(saved["$select"], known, knownList, "select", help, noun), help, noun);
|
|
189
|
+
const filter = flags.filter === undefined ? saved?.["$filter"] : String(flags.filter);
|
|
190
|
+
return { cursor, select, fields, scopes: scopesFor(flags, DEFAULT_CERT_AUTH_SCOPES, profile, help), full: flags.full === true, filter };
|
|
191
|
+
}
|
|
192
|
+
function collectArgs(profile, operation, common, flags, help, params) {
|
|
193
|
+
const query = { $select: common.select.join(",") };
|
|
194
|
+
if (common.filter !== undefined)
|
|
195
|
+
query.$filter = common.filter;
|
|
196
|
+
const args = { profile, operation, query, scopes: common.scopes };
|
|
197
|
+
if (params !== undefined)
|
|
198
|
+
args.params = params;
|
|
199
|
+
if (common.cursor !== undefined)
|
|
200
|
+
args.cursor = common.cursor;
|
|
201
|
+
if (flags.all === true) {
|
|
202
|
+
if (flags.limit !== undefined)
|
|
203
|
+
throw new AxiError("--limit and --all cannot be combined", "VALIDATION_ERROR", [help]);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
args.limit = flags.limit === undefined ? 100 : Number(flags.limit);
|
|
207
|
+
}
|
|
208
|
+
return args;
|
|
209
|
+
}
|
|
210
|
+
function resumeHint(profileName) {
|
|
211
|
+
return `Resume losslessly with the same flags plus --cursor <cursor-from-output> ${profileHint(profileName)}`;
|
|
212
|
+
}
|
|
213
|
+
function pkiId(flags, help, flag) {
|
|
214
|
+
const id = String(flags[flag]);
|
|
215
|
+
if (!id.trim())
|
|
216
|
+
throw new AxiError(`--${flag} needs the certificate-auth PKI object ID`, "VALIDATION_ERROR", [help]);
|
|
217
|
+
return id;
|
|
218
|
+
}
|
|
219
|
+
function singleResult(raw, fields, full, kind) {
|
|
220
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
221
|
+
throw new AxiError(`Graph returned a malformed ${kind} body`, "GRAPH_ERROR", [
|
|
222
|
+
`Single-${kind} reads carry one ${kind} object; treat anything else as unknown, not empty`,
|
|
223
|
+
]);
|
|
224
|
+
}
|
|
225
|
+
return project(raw, fields, full);
|
|
226
|
+
}
|
|
227
|
+
export async function listCertificateAuthPkis(session, flags, profile, operation, help, profileName) {
|
|
228
|
+
const common = collectionCommon(session, flags, KNOWN_PKIS, KNOWN_PKI_FIELDS, DEFAULT_PKI_LIST_SELECT, "PKI", operation, help, profile);
|
|
229
|
+
const result = await withGuidance(CERT_AUTH_DENIAL_HINTS, () => session.collect(collectArgs(profile, operation, common, flags, help)));
|
|
230
|
+
const effectiveFlags = { ...flags, select: result.query.$select ?? DEFAULT_PKI_LIST_SELECT.join(",") };
|
|
231
|
+
if (result.query.$filter !== undefined)
|
|
232
|
+
effectiveFlags.filter = result.query.$filter;
|
|
233
|
+
const pkis = [];
|
|
234
|
+
let truncated = false;
|
|
235
|
+
for (const row of result.value) {
|
|
236
|
+
const projected = project(row, common.fields, common.full);
|
|
237
|
+
pkis.push(projected.row);
|
|
238
|
+
truncated = truncated || projected.truncated;
|
|
239
|
+
}
|
|
240
|
+
const showHint = `mg-axi entra certificate-auth-pki show --id <pki-id> ${profileHint(profileName)}`;
|
|
241
|
+
const truncationHints = truncated ? [fullHint("entra certificate-auth-pki list", effectiveFlags, profileName)] : [];
|
|
242
|
+
if (!result.complete) {
|
|
243
|
+
return {
|
|
244
|
+
certificateAuthPkis: pkis,
|
|
245
|
+
count: { returned: pkis.length, complete: false, reason: result.reason },
|
|
246
|
+
cursor: result.cursor,
|
|
247
|
+
help: [...truncationHints, resumeHint(profileName), showHint, EMPTY_PKI_NOTE],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const count = { returned: pkis.length, complete: true };
|
|
251
|
+
if (!pkis.length) {
|
|
252
|
+
return { certificateAuthPkis: pkis, count, help: [EMPTY_PKI_NOTE] };
|
|
253
|
+
}
|
|
254
|
+
return { certificateAuthPkis: pkis, count, help: [...truncationHints, showHint, EMPTY_PKI_NOTE] };
|
|
255
|
+
}
|
|
256
|
+
export async function showCertificateAuthPki(session, flags, profile, operation, help, profileName) {
|
|
257
|
+
const { select, fields } = selectedFields(flags, KNOWN_PKIS, KNOWN_PKI_FIELDS, DEFAULT_PKI_SHOW_SELECT, help, "PKI");
|
|
258
|
+
const scopes = scopesFor(flags, DEFAULT_CERT_AUTH_SCOPES, profile, help);
|
|
259
|
+
const full = flags.full === true;
|
|
260
|
+
const id = pkiId(flags, help, "id");
|
|
261
|
+
const raw = await withGuidance(CERT_AUTH_DENIAL_HINTS, () => session.execute({
|
|
262
|
+
profile,
|
|
263
|
+
operation,
|
|
264
|
+
params: { "certificateBasedAuthPki-id": id },
|
|
265
|
+
query: { $select: select.join(",") },
|
|
266
|
+
scopes,
|
|
267
|
+
}));
|
|
268
|
+
const { row, truncated } = singleResult(raw, fields, full, "certificate-auth PKI");
|
|
269
|
+
const helpHints = [
|
|
270
|
+
...(truncated ? [fullHint("entra certificate-auth-pki show", flags, profileName)] : []),
|
|
271
|
+
`mg-axi entra certificate-authority list --pki ${shellValue(id)} ${profileHint(profileName)}`,
|
|
272
|
+
];
|
|
273
|
+
return { certificateAuthPki: row, help: helpHints };
|
|
274
|
+
}
|
|
275
|
+
// The $count route returns a text/plain integer scalar rather than a JSON
|
|
276
|
+
// collection, so the leaf reads it through session.execute with scalar mode
|
|
277
|
+
// and accepts only a non-negative integer. --filter narrows the count
|
|
278
|
+
// server-side; there is no --select/--limit/--cursor contract on the count.
|
|
279
|
+
export async function countCertificateAuthPkis(session, flags, profile, operation, help, profileName) {
|
|
280
|
+
void profileName;
|
|
281
|
+
const scopes = scopesFor(flags, DEFAULT_CERT_AUTH_SCOPES, profile, help);
|
|
282
|
+
const query = {};
|
|
283
|
+
if (flags.filter !== undefined)
|
|
284
|
+
query.$filter = String(flags.filter);
|
|
285
|
+
const raw = await withGuidance(CERT_AUTH_DENIAL_HINTS, () => session.execute({ profile, operation, query, scopes, scalar: true }));
|
|
286
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
|
|
287
|
+
throw new AxiError("Graph returned a malformed certificate-auth PKI count body", "GRAPH_ERROR", [
|
|
288
|
+
"PKI counts carry one non-negative integer scalar; treat anything else as unknown, not empty",
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
if (flags.filter !== undefined) {
|
|
292
|
+
return {
|
|
293
|
+
count: { returned: raw, complete: true },
|
|
294
|
+
help: [`Count reflects --filter ${shellValue(String(flags.filter))}; drop --filter for the tenant total`, EMPTY_PKI_NOTE],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return { count: { returned: raw, complete: true }, help: [EMPTY_PKI_NOTE] };
|
|
298
|
+
}
|
|
299
|
+
function authorityId(flags, help) {
|
|
300
|
+
const id = String(flags.id);
|
|
301
|
+
if (!id.trim())
|
|
302
|
+
throw new AxiError("--id needs the certificate-authority object ID", "VALIDATION_ERROR", [help]);
|
|
303
|
+
return id;
|
|
304
|
+
}
|
|
305
|
+
function authorityParams(flags, help) {
|
|
306
|
+
return { "certificateBasedAuthPki-id": pkiId(flags, help, "pki") };
|
|
307
|
+
}
|
|
308
|
+
export async function listCertificateAuthorities(session, flags, profile, operation, help, profileName) {
|
|
309
|
+
const common = collectionCommon(session, flags, KNOWN_CAS, KNOWN_CA_FIELDS, DEFAULT_CA_LIST_SELECT, "certificate-authority", operation, help, profile);
|
|
310
|
+
const pki = pkiId(flags, help, "pki");
|
|
311
|
+
const result = await withGuidance(CERT_AUTH_DENIAL_HINTS, () => session.collect(collectArgs(profile, operation, common, flags, help, authorityParams(flags, help))));
|
|
312
|
+
const effectiveFlags = { ...flags, select: result.query.$select ?? DEFAULT_CA_LIST_SELECT.join(",") };
|
|
313
|
+
if (result.query.$filter !== undefined)
|
|
314
|
+
effectiveFlags.filter = result.query.$filter;
|
|
315
|
+
const authorities = [];
|
|
316
|
+
let truncated = false;
|
|
317
|
+
for (const row of result.value) {
|
|
318
|
+
const projected = project(row, common.fields, common.full);
|
|
319
|
+
authorities.push(projected.row);
|
|
320
|
+
truncated = truncated || projected.truncated;
|
|
321
|
+
}
|
|
322
|
+
const showHint = `mg-axi entra certificate-authority show --pki ${shellValue(pki)} --id <authority-id> ${profileHint(profileName)}`;
|
|
323
|
+
const truncationHints = truncated ? [fullHint("entra certificate-authority list", effectiveFlags, profileName)] : [];
|
|
324
|
+
if (!result.complete) {
|
|
325
|
+
return {
|
|
326
|
+
certificateAuthorities: authorities,
|
|
327
|
+
count: { returned: authorities.length, complete: false, reason: result.reason },
|
|
328
|
+
cursor: result.cursor,
|
|
329
|
+
help: [...truncationHints, resumeHint(profileName), showHint, CERT_BLOB_NOTE],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const count = { returned: authorities.length, complete: true };
|
|
333
|
+
if (!authorities.length) {
|
|
334
|
+
return {
|
|
335
|
+
certificateAuthorities: authorities,
|
|
336
|
+
count,
|
|
337
|
+
help: ["0 certificate authorities matched; the absence of results is the answer, not an error", CERT_BLOB_NOTE],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return { certificateAuthorities: authorities, count, help: [...truncationHints, showHint, CERT_BLOB_NOTE] };
|
|
341
|
+
}
|
|
342
|
+
export async function showCertificateAuthority(session, flags, profile, operation, help, profileName) {
|
|
343
|
+
const { select, fields } = selectedFields(flags, KNOWN_CAS, KNOWN_CA_FIELDS, DEFAULT_CA_SHOW_SELECT, help, "certificate-authority");
|
|
344
|
+
const scopes = scopesFor(flags, DEFAULT_CERT_AUTH_SCOPES, profile, help);
|
|
345
|
+
const full = flags.full === true;
|
|
346
|
+
const params = { ...authorityParams(flags, help), "certificateAuthorityDetail-id": authorityId(flags, help) };
|
|
347
|
+
const raw = await withGuidance(CERT_AUTH_DENIAL_HINTS, () => session.execute({
|
|
348
|
+
profile,
|
|
349
|
+
operation,
|
|
350
|
+
params,
|
|
351
|
+
query: { $select: select.join(",") },
|
|
352
|
+
scopes,
|
|
353
|
+
}));
|
|
354
|
+
const { row, truncated } = singleResult(raw, fields, full, "certificate authority");
|
|
355
|
+
const helpHints = [...(truncated ? [fullHint("entra certificate-authority show", flags, profileName)] : []), CERT_BLOB_NOTE];
|
|
356
|
+
return { certificateAuthority: row, help: helpHints };
|
|
357
|
+
}
|
|
358
|
+
export async function countCertificateAuthorities(session, flags, profile, operation, help, profileName) {
|
|
359
|
+
void profileName;
|
|
360
|
+
const scopes = scopesFor(flags, DEFAULT_CERT_AUTH_SCOPES, profile, help);
|
|
361
|
+
const query = {};
|
|
362
|
+
if (flags.filter !== undefined)
|
|
363
|
+
query.$filter = String(flags.filter);
|
|
364
|
+
const raw = await withGuidance(CERT_AUTH_DENIAL_HINTS, () => session.execute({
|
|
365
|
+
profile,
|
|
366
|
+
operation,
|
|
367
|
+
params: authorityParams(flags, help),
|
|
368
|
+
query,
|
|
369
|
+
scopes,
|
|
370
|
+
scalar: true,
|
|
371
|
+
}));
|
|
372
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
|
|
373
|
+
throw new AxiError("Graph returned a malformed certificate-authority count body", "GRAPH_ERROR", [
|
|
374
|
+
"Authority counts carry one non-negative integer scalar; treat anything else as unknown, not empty",
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
if (flags.filter !== undefined) {
|
|
378
|
+
return {
|
|
379
|
+
count: { returned: raw, complete: true },
|
|
380
|
+
help: [`Count reflects --filter ${shellValue(String(flags.filter))}; drop --filter for the PKI total`, CERT_BLOB_NOTE],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return { count: { returned: raw, complete: true }, help: [CERT_BLOB_NOTE] };
|
|
384
|
+
}
|