@pnp/cli-microsoft365
Version:
Manage Microsoft 365 and SharePoint Framework projects on any platform
103 lines • 5.31 kB
JavaScript
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';
import auth from '../../../../Auth.js';
import config from '../../../../config.js';
import { urlUtil } from '../../../../utils/urlUtil.js';
import request from '../../../../request.js';
import { cli } from '../../../../cli/cli.js';
import { settingsNames } from '../../../../settingsNames.js';
import { browserUtil } from '../../../../utils/browserUtil.js';
import { entraApp } from '../../../../utils/entraApp.js';
import { entraServicePrincipal } from '../../../../utils/entraServicePrincipal.js';
class CliAppReconsentCommand extends GraphCommand {
get name() {
return commands.APP_RECONSENT;
}
get description() {
return 'Reconsent all permission scopes used in CLI for Microsoft 365';
}
async commandAction(logger) {
try {
const appId = auth.connection.appId;
if (this.verbose) {
await logger.logToStderr(`Adding all missing permission scopes used in CLI for Microsoft 365 to application with ID '${appId}'...`);
}
const application = await entraApp.getAppRegistrationByAppId(appId, ['requiredResourceAccess', 'id']);
await this.addCliAppScopes(logger, application.requiredResourceAccess);
await this.updateAppScopes(logger, application.id, application.requiredResourceAccess);
const consentUrl = `https://login.microsoftonline.com/${auth.connection.tenant}/adminconsent?client_id=${appId}`;
await logger.log(`To consent to the new scopes for your Microsoft Entra application registration, please navigate to the following URL: ${consentUrl}`);
if (cli.getSettingWithDefaultValue(settingsNames.autoOpenLinksInBrowser, false)) {
await browserUtil.open(consentUrl);
}
}
catch (err) {
this.handleRejectedODataJsonPromise(err);
}
}
async addCliAppScopes(logger, appScopes) {
const allCliScopes = config.allScopes;
const servicePrincipals = await entraServicePrincipal.getServicePrincipals('displayName,appId,oauth2PermissionScopes,servicePrincipalNames');
if (this.verbose) {
await logger.logToStderr(`Verifying if all ${allCliScopes.length} permission scopes are present in the app registration...`);
}
for (const cliScope of allCliScopes) {
// Extract service principal name and scope from the URL string
const spName = urlUtil.removeTrailingSlashes(cliScope.substring(0, cliScope.lastIndexOf('/')));
const scopeName = cliScope.substring(cliScope.lastIndexOf('/') + 1);
// Find the matching service principal by name
const servicePrincipal = servicePrincipals.find(sp => sp.servicePrincipalNames?.some(name => urlUtil.removeTrailingSlashes(name).toLowerCase() === spName.toLowerCase()));
if (!servicePrincipal) {
if (this.verbose) {
await logger.logToStderr(`Service principal with name '${spName}' not found. Skipping scope '${scopeName}'.`);
}
continue;
}
// Find the matching scope in the service principal
const scope = servicePrincipal.oauth2PermissionScopes?.find(s => s.value?.toLowerCase() === scopeName.toLowerCase());
if (!scope) {
if (this.verbose) {
await logger.logToStderr(`Scope '${scopeName}' not found in service principal '${spName}'. Skipping scope...`);
}
continue;
}
// Check if the service principal is already present in the app registration
let appSp = appScopes.find(sp => sp.resourceAppId?.toLowerCase() === servicePrincipal.appId.toLowerCase());
if (!appSp) {
// Service principal is not present in the app registration, let's add it
appSp = {
resourceAppId: servicePrincipal.appId,
resourceAccess: []
};
appScopes.push(appSp);
}
// Check if the scope is already present in the app registration
const isAppScopePresent = appSp.resourceAccess.some(s => s.id?.toLowerCase() === scope.id.toLowerCase());
if (!isAppScopePresent) {
// Scope is not present in the app registration, let's add it
appSp.resourceAccess.push({
id: scope.id,
type: 'Scope'
});
}
}
}
async updateAppScopes(logger, appId, appScopes) {
if (this.verbose) {
await logger.logToStderr(`Updating permission scopes of application with ID '${appId}'...`);
}
const requestOptions = {
url: `${this.resource}/v1.0/applications/${appId}`,
headers: {
accept: 'application/json;odata.metadata=none'
},
responseType: 'json',
data: {
requiredResourceAccess: appScopes
}
};
await request.patch(requestOptions);
}
}
export default new CliAppReconsentCommand();
//# sourceMappingURL=app-reconsent.js.map