feat(graph): add --resolve for list-app-permissions
This commit is contained in:
15
src/cli.js
15
src/cli.js
@@ -7,7 +7,12 @@ import jmespath from "jmespath";
|
||||
|
||||
import { loadPublicConfig } from "./index.js";
|
||||
import { getGraphClient } from "./graph/auth.js";
|
||||
import { listApps, listAppPermissions, listAppGrants } from "./graph/app.js";
|
||||
import {
|
||||
listApps,
|
||||
listAppPermissions,
|
||||
listAppPermissionsResolved,
|
||||
listAppGrants,
|
||||
} from "./graph/app.js";
|
||||
import { toMarkdownTable } from "./markdown.js";
|
||||
|
||||
function usage() {
|
||||
@@ -15,13 +20,14 @@ function usage() {
|
||||
|
||||
Commands:
|
||||
list-apps [--display-name <name>]
|
||||
list-app-permissions --app-id <appId>
|
||||
list-app-permissions --app-id <appId> [--resolve]
|
||||
list-app-grants --app-id <appId>
|
||||
table [--pretty]
|
||||
|
||||
Options:
|
||||
--display-name <name> Filter apps by exact display name
|
||||
--app-id <appId> Application (client) ID
|
||||
--resolve Resolve permission GUIDs to human-readable values
|
||||
--query <jmespath> Filter output JSON using JMESPath
|
||||
--pretty Use normalized column widths for Markdown table output
|
||||
--output <format> Output format: json|table|prettytable (default: json)
|
||||
@@ -73,6 +79,7 @@ async function main() {
|
||||
help: { type: "boolean", short: "h" },
|
||||
"display-name": { type: "string" },
|
||||
"app-id": { type: "string" },
|
||||
resolve: { type: "boolean" },
|
||||
query: { type: "string" },
|
||||
pretty: { type: "boolean" },
|
||||
output: { type: "string" },
|
||||
@@ -117,7 +124,9 @@ async function main() {
|
||||
tenantId: config.tenantId,
|
||||
clientId: config.clientId,
|
||||
});
|
||||
result = await listAppPermissions(client, values["app-id"]);
|
||||
result = values.resolve
|
||||
? await listAppPermissionsResolved(client, values["app-id"])
|
||||
: await listAppPermissions(client, values["app-id"]);
|
||||
}
|
||||
break;
|
||||
case "list-app-grants":
|
||||
|
||||
@@ -84,6 +84,87 @@ export async function listAppPermissions(client, appId) {
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* List required resource access in a resolved, human-readable form.
|
||||
*
|
||||
* @param { Object } client
|
||||
* @param { string } appId
|
||||
* @returns { Promise<Array> }
|
||||
*/
|
||||
export async function listAppPermissionsResolved(client, appId) {
|
||||
const requiredResourceAccess = await listAppPermissions(client, appId);
|
||||
if (!Array.isArray(requiredResourceAccess) || requiredResourceAccess.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const resourceAppIds = [...new Set(
|
||||
requiredResourceAccess
|
||||
.map((entry) => entry?.resourceAppId)
|
||||
.filter(Boolean),
|
||||
)];
|
||||
|
||||
const resourceDefinitions = await Promise.all(resourceAppIds.map(async (resourceAppId) => {
|
||||
const result = await client
|
||||
.api("/servicePrincipals")
|
||||
.filter(`appId eq '${resourceAppId}'`)
|
||||
.select("appId,displayName,oauth2PermissionScopes,appRoles")
|
||||
.get();
|
||||
|
||||
const sp = Array.isArray(result?.value) && result.value.length > 0
|
||||
? result.value[0]
|
||||
: null;
|
||||
|
||||
const scopesById = new Map(
|
||||
(sp?.oauth2PermissionScopes ?? []).map((scope) => [scope.id, scope]),
|
||||
);
|
||||
const rolesById = new Map(
|
||||
(sp?.appRoles ?? []).map((role) => [role.id, role]),
|
||||
);
|
||||
|
||||
return {
|
||||
resourceAppId,
|
||||
resourceDisplayName: sp?.displayName ?? null,
|
||||
scopesById,
|
||||
rolesById,
|
||||
};
|
||||
}));
|
||||
|
||||
const byResourceAppId = new Map(
|
||||
resourceDefinitions.map((entry) => [entry.resourceAppId, entry]),
|
||||
);
|
||||
|
||||
const rows = [];
|
||||
for (const resourceEntry of requiredResourceAccess) {
|
||||
const resourceMeta = byResourceAppId.get(resourceEntry.resourceAppId);
|
||||
const resourceAccessItems = Array.isArray(resourceEntry?.resourceAccess)
|
||||
? resourceEntry.resourceAccess
|
||||
: [];
|
||||
|
||||
for (const item of resourceAccessItems) {
|
||||
const permissionType = item?.type ?? null;
|
||||
const permissionId = item?.id ?? null;
|
||||
const resolved = permissionType === "Scope"
|
||||
? resourceMeta?.scopesById.get(permissionId)
|
||||
: resourceMeta?.rolesById.get(permissionId);
|
||||
|
||||
rows.push({
|
||||
resourceAppId: resourceEntry.resourceAppId ?? null,
|
||||
resourceDisplayName: resourceMeta?.resourceDisplayName ?? null,
|
||||
permissionId,
|
||||
permissionType,
|
||||
permissionValue: resolved?.value ?? null,
|
||||
permissionDisplayName:
|
||||
resolved?.adminConsentDisplayName ??
|
||||
resolved?.userConsentDisplayName ??
|
||||
resolved?.displayName ??
|
||||
null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* List delegated OAuth2 permission grants for an application by appId.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user