@pnp/cli-microsoft365
Version:
Manage Microsoft 365 and SharePoint Framework projects on any platform
242 lines • 11.7 kB
JavaScript
import { cli } from '../../../../cli/cli.js';
import request from '../../../../request.js';
import appGetCommand from '../../../entra/commands/app/app-get.js';
import AppCommand from '../../../base/AppCommand.js';
import commands from '../../commands.js';
var GetServicePrincipal;
(function (GetServicePrincipal) {
GetServicePrincipal[GetServicePrincipal["withPermissions"] = 0] = "withPermissions";
GetServicePrincipal[GetServicePrincipal["withPermissionDefinitions"] = 1] = "withPermissionDefinitions";
})(GetServicePrincipal || (GetServicePrincipal = {}));
class AppPermissionListCommand extends AppCommand {
get name() {
return commands.PERMISSION_LIST;
}
get description() {
return 'Lists API permissions for the current Microsoft Entra app';
}
async commandAction(logger) {
try {
const servicePrincipal = await this.getServicePrincipal({ appId: this.appId }, logger, GetServicePrincipal.withPermissions);
let permissions;
if (servicePrincipal) {
// service principal found, get permissions from the service principal
permissions = await this.getServicePrincipalPermissions(servicePrincipal, logger);
}
else {
// service principal not found, get permissions from app registration
permissions = await this.getAppRegPermissions(this.appId, logger);
}
await logger.log(permissions);
}
catch (err) {
this.handleRejectedODataJsonPromise(err);
}
}
async getServicePrincipal(servicePrincipalInfo, logger, mode) {
if (this.verbose) {
await logger.logToStderr(`Retrieving service principal ${servicePrincipalInfo.appId ?? servicePrincipalInfo.id}`);
}
const lookupUrl = servicePrincipalInfo.appId ? `?$filter=appId eq '${servicePrincipalInfo.appId}'&` : `/${servicePrincipalInfo.id}?`;
const requestOptions = {
url: `${this.resource}/v1.0/servicePrincipals${lookupUrl}$select=appId,id,displayName`,
headers: {
accept: 'application/json;odata.metadata=none'
},
responseType: 'json'
};
const response = await request.get(requestOptions);
if ((servicePrincipalInfo.id && !response) ||
(servicePrincipalInfo.appId && response.value.length === 0)) {
return undefined;
}
const servicePrincipal = servicePrincipalInfo.appId ?
response.value[0] :
response;
if (this.verbose) {
await logger.logToStderr(`Retrieving permissions for service principal ${servicePrincipal.id}...`);
}
const permissionsPromises = [];
switch (mode) {
case GetServicePrincipal.withPermissions:
const appRoleAssignmentsRequestOptions = {
url: `${this.resource}/v1.0/servicePrincipals/${servicePrincipal.id}/appRoleAssignments`,
headers: {
accept: 'application/json;odata.metadata=none'
},
responseType: 'json'
};
const oauth2PermissionGrantsRequestOptions = {
url: `${this.resource}/v1.0/servicePrincipals/${servicePrincipal.id}/oauth2PermissionGrants`,
headers: {
accept: 'application/json;odata.metadata=none'
},
responseType: 'json'
};
permissionsPromises.push(...[
request.get(appRoleAssignmentsRequestOptions),
request.get(oauth2PermissionGrantsRequestOptions)
]);
break;
case GetServicePrincipal.withPermissionDefinitions:
const oauth2PermissionScopesRequestOptions = {
url: `${this.resource}/v1.0/servicePrincipals/${servicePrincipal.id}/oauth2PermissionScopes`,
headers: {
accept: 'application/json;odata.metadata=none'
},
responseType: 'json'
};
const appRolesRequestOptions = {
url: `${this.resource}/v1.0/servicePrincipals/${servicePrincipal.id}/appRoles`,
headers: {
accept: 'application/json;odata.metadata=none'
},
responseType: 'json'
};
permissionsPromises.push(...[
request.get(oauth2PermissionScopesRequestOptions),
request.get(appRolesRequestOptions)
]);
break;
}
const permissions = await Promise.all(permissionsPromises);
switch (mode) {
case GetServicePrincipal.withPermissions:
servicePrincipal.appRoleAssignments = permissions[0].value;
servicePrincipal.oauth2PermissionGrants = permissions[1].value;
break;
case GetServicePrincipal.withPermissionDefinitions:
servicePrincipal.oauth2PermissionScopes = permissions[0].value;
servicePrincipal.appRoles = permissions[1].value;
break;
}
return servicePrincipal;
}
async getServicePrincipalPermissions(servicePrincipal, logger) {
if (this.verbose) {
await logger.logToStderr(`Resolving permissions for the service principal...`);
}
const apiPermissions = [];
// hash table for resolving resource IDs to names
const resourceLookup = {};
// list of service principals for which to load permissions
const servicePrincipalsToResolve = [];
const appRoleAssignments = servicePrincipal.appRoleAssignments;
apiPermissions.push(...appRoleAssignments.map(appRoleAssignment => {
// store resource name for resolving OAuth2 grants
resourceLookup[appRoleAssignment.resourceId] = appRoleAssignment.resourceDisplayName;
// add to the list of service principals to load to get the app role
// display name
if (!servicePrincipalsToResolve.find(r => r.id === appRoleAssignment.resourceId)) {
servicePrincipalsToResolve.push({ id: appRoleAssignment.resourceId });
}
return {
resource: appRoleAssignment.resourceDisplayName,
// we store the app role ID temporarily and will later resolve to display name
permission: appRoleAssignment.appRoleId,
type: 'Application'
};
}));
const oauth2Grants = servicePrincipal.oauth2PermissionGrants;
oauth2Grants.forEach(oauth2Grant => {
// see if we can resolve the resource name from the resources
// retrieved from app role assignments
const resource = resourceLookup[oauth2Grant.resourceId] ?? oauth2Grant.resourceId;
if (resource === oauth2Grant.resourceId &&
!servicePrincipalsToResolve.find(r => r.id === oauth2Grant.resourceId)) {
// resource name not found in the resources
// add it to the list of resources to resolve
servicePrincipalsToResolve.push({ id: oauth2Grant.resourceId });
}
const scopes = oauth2Grant.scope.split(' ');
scopes.forEach(scope => {
apiPermissions.push({
resource,
permission: scope,
type: 'Delegated'
});
});
});
if (servicePrincipalsToResolve.length > 0) {
const servicePrincipals = await Promise
.all(servicePrincipalsToResolve
.map(servicePrincipalInfo => this.getServicePrincipal(servicePrincipalInfo, logger, GetServicePrincipal.withPermissionDefinitions)));
servicePrincipals.forEach(servicePrincipal => {
apiPermissions.forEach(apiPermission => {
if (apiPermission.resource === servicePrincipal.id) {
apiPermission.resource = servicePrincipal.displayName;
}
if (apiPermission.resource === servicePrincipal.displayName &&
apiPermission.type === 'Application') {
apiPermission.permission = servicePrincipal.appRoles
.find(appRole => appRole.id === apiPermission.permission)?.value ?? apiPermission.permission;
}
});
});
}
return apiPermissions;
}
async getAppRegistration(appId, logger) {
if (this.verbose) {
await logger.logToStderr(`Retrieving Microsoft Entra application registration ${appId}`);
}
const options = {
appId: appId,
output: 'json',
debug: this.debug,
verbose: this.verbose
};
const output = await cli.executeCommandWithOutput(appGetCommand, { options: { ...options, _: [] } });
if (this.debug) {
await logger.logToStderr(output.stderr);
}
return JSON.parse(output.stdout);
}
async getAppRegPermissions(appId, logger) {
const application = await this.getAppRegistration(appId, logger);
if (application.requiredResourceAccess.length === 0) {
return [];
}
const servicePrincipalsToResolve = application.requiredResourceAccess
.map(resourceAccess => {
return {
appId: resourceAccess.resourceAppId
};
});
const servicePrincipals = await Promise
.all(servicePrincipalsToResolve.map(servicePrincipalInfo => this.getServicePrincipal(servicePrincipalInfo, logger, GetServicePrincipal.withPermissionDefinitions)));
const apiPermissions = [];
application.requiredResourceAccess.forEach(requiredResourceAccess => {
const servicePrincipal = servicePrincipals
.find(servicePrincipal => servicePrincipal?.appId === requiredResourceAccess.resourceAppId);
const resourceName = servicePrincipal?.displayName ?? requiredResourceAccess.resourceAppId;
requiredResourceAccess.resourceAccess.forEach(permission => {
apiPermissions.push({
resource: resourceName,
permission: this.getPermissionName(permission.id, permission.type, servicePrincipal),
type: permission.type === 'Role' ? 'Application' : 'Delegated'
});
});
});
return apiPermissions;
}
getPermissionName(permissionId, permissionType, servicePrincipal) {
if (!servicePrincipal) {
return permissionId;
}
switch (permissionType) {
case 'Role':
return servicePrincipal.appRoles
.find(appRole => appRole.id === permissionId)?.value ?? permissionId;
case 'Scope':
return servicePrincipal.oauth2PermissionScopes
.find(permissionScope => permissionScope.id === permissionId)?.value ?? permissionId;
}
/* c8 ignore next 4 */
// permissionType is either 'Scope' or 'Role' but we need a safe default
// to avoid building errors. This code will never be reached.
return permissionId;
}
}
export default new AppPermissionListCommand();
//# sourceMappingURL=permission-list.js.map