|
|
|
@@ -1,51 +1,77 @@
|
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an Azure application by its display name.
|
|
|
|
|
*
|
|
|
|
|
* @param { Object } client
|
|
|
|
|
* @param { string } displayName
|
|
|
|
|
* @returns { Promise<Object|null> }
|
|
|
|
|
*/
|
|
|
|
|
export async function getApp(client, displayName) {
|
|
|
|
|
type GraphObject = Record<string, unknown>;
|
|
|
|
|
|
|
|
|
|
type GraphResult<T = GraphObject> = {
|
|
|
|
|
value?: T[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type AppQueryOptions = {
|
|
|
|
|
displayName?: string;
|
|
|
|
|
appId?: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type RequiredResourceAccessItem = {
|
|
|
|
|
type?: string;
|
|
|
|
|
id?: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type RequiredResourceAccess = {
|
|
|
|
|
resourceAppId?: string;
|
|
|
|
|
resourceAccess?: RequiredResourceAccessItem[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type GraphPermission = {
|
|
|
|
|
id?: string;
|
|
|
|
|
value?: string;
|
|
|
|
|
displayName?: string;
|
|
|
|
|
adminConsentDisplayName?: string;
|
|
|
|
|
userConsentDisplayName?: string;
|
|
|
|
|
isEnabled?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ServicePrincipal = {
|
|
|
|
|
id?: string;
|
|
|
|
|
appId?: string;
|
|
|
|
|
displayName?: string;
|
|
|
|
|
oauth2PermissionScopes?: GraphPermission[];
|
|
|
|
|
appRoles?: GraphPermission[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ResourcePermissionsOptions = {
|
|
|
|
|
appId?: string;
|
|
|
|
|
displayName?: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function getApp(client: any, displayName: string): Promise<GraphObject | null> {
|
|
|
|
|
const result = await client
|
|
|
|
|
.api("/applications")
|
|
|
|
|
.filter(`displayName eq '${displayName}'`)
|
|
|
|
|
.get();
|
|
|
|
|
.get() as GraphResult;
|
|
|
|
|
|
|
|
|
|
// Return the first application found or null if none exists
|
|
|
|
|
return result.value.length > 0 ? result.value[0] : null;
|
|
|
|
|
return Array.isArray(result.value) && result.value.length > 0 ? result.value[0] : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createApp(client, displayName) {
|
|
|
|
|
export async function createApp(client: any, displayName: string): Promise<GraphObject> {
|
|
|
|
|
const app = await client.api("/applications").post({
|
|
|
|
|
displayName,
|
|
|
|
|
});
|
|
|
|
|
}) as GraphObject;
|
|
|
|
|
|
|
|
|
|
if (!app || !app.appId) {
|
|
|
|
|
if (!app || typeof app.appId !== "string") {
|
|
|
|
|
throw new Error("Failed to create application");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return app;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function deleteApp(client, appObjectId) {
|
|
|
|
|
export async function deleteApp(client: any, appObjectId: string): Promise<void> {
|
|
|
|
|
await client.api(`/applications/${appObjectId}`).delete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List Azure applications, optionally filtered by display name and/or app ID.
|
|
|
|
|
*
|
|
|
|
|
* @param { Object } client
|
|
|
|
|
* @param { Object } [options]
|
|
|
|
|
* @param { string } [options.displayName]
|
|
|
|
|
* @param { string } [options.appId]
|
|
|
|
|
* @returns { Promise<Array> }
|
|
|
|
|
*/
|
|
|
|
|
export async function listApps(client, options = {}) {
|
|
|
|
|
export async function listApps(client: any, options: AppQueryOptions = {}): Promise<GraphObject[]> {
|
|
|
|
|
const { displayName, appId } = options;
|
|
|
|
|
let request = client.api("/applications");
|
|
|
|
|
const filters = [];
|
|
|
|
|
const filters: string[] = [];
|
|
|
|
|
|
|
|
|
|
if (displayName) {
|
|
|
|
|
filters.push(`displayName eq '${displayName}'`);
|
|
|
|
@@ -58,18 +84,11 @@ export async function listApps(client, options = {}) {
|
|
|
|
|
request = request.filter(filters.join(" and "));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await request.get();
|
|
|
|
|
const result = await request.get() as GraphResult;
|
|
|
|
|
return Array.isArray(result?.value) ? result.value : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List required resource access configuration for an application by appId.
|
|
|
|
|
*
|
|
|
|
|
* @param { Object } client
|
|
|
|
|
* @param { string } appId
|
|
|
|
|
* @returns { Promise<Array> }
|
|
|
|
|
*/
|
|
|
|
|
export async function listAppPermissions(client, appId) {
|
|
|
|
|
export async function listAppPermissions(client: any, appId: string): Promise<RequiredResourceAccess[]> {
|
|
|
|
|
if (!appId) {
|
|
|
|
|
throw new Error("appId is required");
|
|
|
|
|
}
|
|
|
|
@@ -78,7 +97,7 @@ export async function listAppPermissions(client, appId) {
|
|
|
|
|
.api("/applications")
|
|
|
|
|
.filter(`appId eq '${appId}'`)
|
|
|
|
|
.select("id,appId,displayName,requiredResourceAccess")
|
|
|
|
|
.get();
|
|
|
|
|
.get() as GraphResult<GraphObject>;
|
|
|
|
|
|
|
|
|
|
const app = Array.isArray(result?.value) && result.value.length > 0
|
|
|
|
|
? result.value[0]
|
|
|
|
@@ -88,19 +107,13 @@ export async function listAppPermissions(client, appId) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.isArray(app.requiredResourceAccess)
|
|
|
|
|
? app.requiredResourceAccess
|
|
|
|
|
const requiredResourceAccess = app.requiredResourceAccess;
|
|
|
|
|
return Array.isArray(requiredResourceAccess)
|
|
|
|
|
? requiredResourceAccess as RequiredResourceAccess[]
|
|
|
|
|
: [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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) {
|
|
|
|
|
export async function listAppPermissionsResolved(client: any, appId: string): Promise<Array<Record<string, unknown>>> {
|
|
|
|
|
const requiredResourceAccess = await listAppPermissions(client, appId);
|
|
|
|
|
if (!Array.isArray(requiredResourceAccess) || requiredResourceAccess.length === 0) {
|
|
|
|
|
return [];
|
|
|
|
@@ -109,7 +122,7 @@ export async function listAppPermissionsResolved(client, appId) {
|
|
|
|
|
const resourceAppIds = [...new Set(
|
|
|
|
|
requiredResourceAccess
|
|
|
|
|
.map((entry) => entry?.resourceAppId)
|
|
|
|
|
.filter(Boolean),
|
|
|
|
|
.filter((value): value is string => typeof value === "string" && value.length > 0),
|
|
|
|
|
)];
|
|
|
|
|
|
|
|
|
|
const resourceDefinitions = await Promise.all(resourceAppIds.map(async (resourceAppId) => {
|
|
|
|
@@ -117,17 +130,21 @@ export async function listAppPermissionsResolved(client, appId) {
|
|
|
|
|
.api("/servicePrincipals")
|
|
|
|
|
.filter(`appId eq '${resourceAppId}'`)
|
|
|
|
|
.select("appId,displayName,oauth2PermissionScopes,appRoles")
|
|
|
|
|
.get();
|
|
|
|
|
.get() as GraphResult<ServicePrincipal>;
|
|
|
|
|
|
|
|
|
|
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]),
|
|
|
|
|
(sp?.oauth2PermissionScopes ?? [])
|
|
|
|
|
.filter((scope) => typeof scope.id === "string")
|
|
|
|
|
.map((scope) => [scope.id as string, scope]),
|
|
|
|
|
);
|
|
|
|
|
const rolesById = new Map(
|
|
|
|
|
(sp?.appRoles ?? []).map((role) => [role.id, role]),
|
|
|
|
|
(sp?.appRoles ?? [])
|
|
|
|
|
.filter((role) => typeof role.id === "string")
|
|
|
|
|
.map((role) => [role.id as string, role]),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
@@ -142,9 +159,10 @@ export async function listAppPermissionsResolved(client, appId) {
|
|
|
|
|
resourceDefinitions.map((entry) => [entry.resourceAppId, entry]),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const rows = [];
|
|
|
|
|
const rows: Array<Record<string, unknown>> = [];
|
|
|
|
|
for (const resourceEntry of requiredResourceAccess) {
|
|
|
|
|
const resourceMeta = byResourceAppId.get(resourceEntry.resourceAppId);
|
|
|
|
|
const resourceAppId = resourceEntry.resourceAppId ?? "";
|
|
|
|
|
const resourceMeta = byResourceAppId.get(resourceAppId);
|
|
|
|
|
const resourceAccessItems = Array.isArray(resourceEntry?.resourceAccess)
|
|
|
|
|
? resourceEntry.resourceAccess
|
|
|
|
|
: [];
|
|
|
|
@@ -153,8 +171,8 @@ export async function listAppPermissionsResolved(client, appId) {
|
|
|
|
|
const permissionType = item?.type ?? null;
|
|
|
|
|
const permissionId = item?.id ?? null;
|
|
|
|
|
const resolved = permissionType === "Scope"
|
|
|
|
|
? resourceMeta?.scopesById.get(permissionId)
|
|
|
|
|
: resourceMeta?.rolesById.get(permissionId);
|
|
|
|
|
? resourceMeta?.scopesById.get(permissionId ?? "")
|
|
|
|
|
: resourceMeta?.rolesById.get(permissionId ?? "");
|
|
|
|
|
|
|
|
|
|
rows.push({
|
|
|
|
|
resourceAppId: resourceEntry.resourceAppId ?? null,
|
|
|
|
@@ -174,14 +192,7 @@ export async function listAppPermissionsResolved(client, appId) {
|
|
|
|
|
return rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List delegated OAuth2 permission grants for an application by appId.
|
|
|
|
|
*
|
|
|
|
|
* @param { Object } client
|
|
|
|
|
* @param { string } appId
|
|
|
|
|
* @returns { Promise<Array> }
|
|
|
|
|
*/
|
|
|
|
|
export async function listAppGrants(client, appId) {
|
|
|
|
|
export async function listAppGrants(client: any, appId: string): Promise<GraphObject[]> {
|
|
|
|
|
if (!appId) {
|
|
|
|
|
throw new Error("appId is required");
|
|
|
|
|
}
|
|
|
|
@@ -190,7 +201,7 @@ export async function listAppGrants(client, appId) {
|
|
|
|
|
.api("/servicePrincipals")
|
|
|
|
|
.filter(`appId eq '${appId}'`)
|
|
|
|
|
.select("id,appId,displayName")
|
|
|
|
|
.get();
|
|
|
|
|
.get() as GraphResult<ServicePrincipal>;
|
|
|
|
|
|
|
|
|
|
const servicePrincipal = Array.isArray(spResult?.value) && spResult.value.length > 0
|
|
|
|
|
? spResult.value[0]
|
|
|
|
@@ -203,21 +214,12 @@ export async function listAppGrants(client, appId) {
|
|
|
|
|
const grantsResult = await client
|
|
|
|
|
.api("/oauth2PermissionGrants")
|
|
|
|
|
.filter(`clientId eq '${servicePrincipal.id}'`)
|
|
|
|
|
.get();
|
|
|
|
|
.get() as GraphResult;
|
|
|
|
|
|
|
|
|
|
return Array.isArray(grantsResult?.value) ? grantsResult.value : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List available delegated scopes and app roles for a resource app.
|
|
|
|
|
*
|
|
|
|
|
* @param { Object } client
|
|
|
|
|
* @param { Object } options
|
|
|
|
|
* @param { string } [options.appId]
|
|
|
|
|
* @param { string } [options.displayName]
|
|
|
|
|
* @returns { Promise<Array> }
|
|
|
|
|
*/
|
|
|
|
|
export async function listResourcePermissions(client, options = {}) {
|
|
|
|
|
export async function listResourcePermissions(client: any, options: ResourcePermissionsOptions = {}): Promise<Array<Record<string, unknown>>> {
|
|
|
|
|
const { appId, displayName } = options;
|
|
|
|
|
if (!appId && !displayName) {
|
|
|
|
|
throw new Error("appId or displayName is required");
|
|
|
|
@@ -233,9 +235,9 @@ export async function listResourcePermissions(client, options = {}) {
|
|
|
|
|
request = request.filter(`displayName eq '${displayName}'`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await request.get();
|
|
|
|
|
const result = await request.get() as GraphResult<ServicePrincipal>;
|
|
|
|
|
const servicePrincipals = Array.isArray(result?.value) ? result.value : [];
|
|
|
|
|
const rows = [];
|
|
|
|
|
const rows: Array<Record<string, unknown>> = [];
|
|
|
|
|
|
|
|
|
|
for (const sp of servicePrincipals) {
|
|
|
|
|
for (const scope of sp?.oauth2PermissionScopes ?? []) {
|