From 232577519c6be2e4dd316aa18149fc4fa07848ed Mon Sep 17 00:00:00 2001 From: Nanddeep Nachan Date: Fri, 27 Sep 2024 15:39:54 +0000 Subject: [PATCH] Added properties option --- .../administrativeunit-get.mdx | 13 +++-- .../administrativeunit-list.mdx | 17 +++++- docs/docs/cmd/entra/app/app-get.mdx | 21 +++++--- docs/docs/cmd/entra/app/app-list.mdx | 17 +++++- docs/docs/cmd/entra/group/group-get.mdx | 13 +++-- docs/docs/cmd/entra/group/group-list.mdx | 11 +++- .../administrativeunit-get.spec.ts | 13 +++++ .../administrativeunit-get.ts | 28 ++++++++-- .../administrativeunit-list.spec.ts | 38 ++++++++++++++ .../administrativeunit-list.ts | 49 ++++++++++++++++- src/m365/entra/commands/app/app-get.spec.ts | 14 ++--- src/m365/entra/commands/app/app-get.ts | 28 ++++++++-- src/m365/entra/commands/app/app-list.spec.ts | 38 ++++++++++++++ src/m365/entra/commands/app/app-list.ts | 49 ++++++++++++++++- src/m365/entra/commands/group/group-get.ts | 13 +++-- .../entra/commands/group/group-list.spec.ts | 24 +++++++++ src/m365/entra/commands/group/group-list.ts | 24 ++++++++- src/utils/entraAdministrativeUnit.spec.ts | 20 +++++++ src/utils/entraAdministrativeUnit.ts | 20 ++++++- src/utils/entraGroup.spec.ts | 52 ++++++++++++------- src/utils/entraGroup.ts | 45 +++++++++++++--- 21 files changed, 478 insertions(+), 69 deletions(-) diff --git a/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx b/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx index 3e4748bf758..e4432dfbd75 100644 --- a/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx +++ b/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx @@ -26,22 +26,29 @@ m365 aad administrativeunit get [options] `-n, --displayName [displayName]` : The display name of the administrative unit. Specify either `id` or `displayName` but not both. + +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. ``` +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of administrative unit properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + ## Examples -Get information about the administrative unit by its id +Get information about the administrative unit by its id. ```sh m365 entra administrativeunit get --id 03c4c9dc-6f0c-4c4f-a4e6-0c9ed80f54c7 ``` -Get information about the administrative unit by its display name +Get information about the administrative unit by its display name with specified properties. ```sh -m365 entra administrativeunit get --displayName 'Marketing Division' +m365 entra administrativeunit get --displayName "Marketing Division" --properties "id,displayName" ``` ## Response diff --git a/docs/docs/cmd/entra/administrativeunit/administrativeunit-list.mdx b/docs/docs/cmd/entra/administrativeunit/administrativeunit-list.mdx index ac644383da6..9efbafebfd7 100644 --- a/docs/docs/cmd/entra/administrativeunit/administrativeunit-list.mdx +++ b/docs/docs/cmd/entra/administrativeunit/administrativeunit-list.mdx @@ -20,16 +20,31 @@ m365 aad administrativeunit list [options] ## Options +```md definition-list +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. +``` + +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of administrative unit properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + ## Examples -Retrieve a list of administrative units +Retrieve a list of administrative units. ```sh m365 entra administrativeunit list ``` +Retrieve a list of administrative units with specified properties. + +```sh +m365 entra administrativeunit list --properties "id,displayName" +``` + ## Response diff --git a/docs/docs/cmd/entra/app/app-get.mdx b/docs/docs/cmd/entra/app/app-get.mdx index d340157cd6a..2e94ffd0ad1 100644 --- a/docs/docs/cmd/entra/app/app-get.mdx +++ b/docs/docs/cmd/entra/app/app-get.mdx @@ -23,16 +23,19 @@ m365 entra appregistration get [options] ```md definition-list `--appId [appId]` -: Application (client) ID of the Entra application registration to get. Specify either `appId`, `objectId` or `name` +: Application (client) ID of the Entra application registration to get. Specify either `appId`, `objectId` or `name`. `--objectId [objectId]` -: Object ID of the Entra application registration to get. Specify either `appId`, `objectId` or `name` +: Object ID of the Entra application registration to get. Specify either `appId`, `objectId` or `name`. `--name [name]` -: Name of the Entra application registration to get. Specify either `appId`, `objectId` or `name` +: Name of the Entra application registration to get. Specify either `appId`, `objectId` or `name`. `--save` -: Use to store the information about the created app in a local file +: Use to store the information about the created app in a local file. + +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. ``` @@ -45,24 +48,26 @@ If the command finds multiple Entra application registrations with the specified If you want to store the information about the Entra app registration, use the `--save` option. This is useful when you build solutions connected to Microsoft 365 and want to easily manage app registrations used with your solution. When you use the `--save` option, after you get the app registration, the command will write its ID and name to the `.m365rc.json` file in the current directory. If the file already exists, it will add the information about the App registration to it if it's not already present, allowing you to track multiple apps. If the file doesn't exist, the command will create it. +Using the `--properties` option, you can specify a comma-separated list of app properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + ## Examples -Get the Entra application registration by its app (client) ID +Get the Entra application registration by its app (client) ID. ```sh m365 entra app get --appId d75be2e1-0204-4f95-857d-51a37cf40be8 ``` -Get the Entra application registration by its object ID +Get the Entra application registration by its object ID. ```sh m365 entra app get --objectId d75be2e1-0204-4f95-857d-51a37cf40be8 ``` -Get the Entra application registration by its name +Get the Entra application registration by its name with specified properties. ```sh -m365 entra app get --name "My app" +m365 entra app get --name "My app" --properties "appId,displayName" ``` Get the Entra application registration by its name. Store information about the retrieved app registration in the _.m365rc.json_ file in the current directory. diff --git a/docs/docs/cmd/entra/app/app-list.mdx b/docs/docs/cmd/entra/app/app-list.mdx index 62b6aa66b71..9ed2523f064 100644 --- a/docs/docs/cmd/entra/app/app-list.mdx +++ b/docs/docs/cmd/entra/app/app-list.mdx @@ -21,16 +21,31 @@ m365 entra appregistration list [options] ## Options +```md definition-list +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. +``` + +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of app properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + ## Examples -Retrieve a list of Entra app registrations +Retrieve a list of Entra app registrations. ```sh m365 entra app list ``` +Retrieve a list of Entra app registrations with specified properties. + +```sh +m365 entra app list --properties "appId,displayName" +``` + ## Response diff --git a/docs/docs/cmd/entra/group/group-get.mdx b/docs/docs/cmd/entra/group/group-get.mdx index 2f370ecf72d..90fe2879660 100644 --- a/docs/docs/cmd/entra/group/group-get.mdx +++ b/docs/docs/cmd/entra/group/group-get.mdx @@ -26,22 +26,29 @@ m365 aad group get [options] `-n, --displayName [displayName]` : The display name of the Entra group. Specify either `id` or `displayName` but not both. + +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. ``` +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of group properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + ## Examples -Get information about an Entra Group by id +Get information about an Entra Group by id. ```sh m365 entra group get --id 1caf7dcd-7e83-4c3a-94f7-932a1299c844 ``` -Get information about an Entra Group by its display name +Get information about an Entra Group by its display name with specified properties. ```sh -m365 entra group get --displayName Finance +m365 entra group get --displayName Finance --properties "mail,displayName" ``` ## Response diff --git a/docs/docs/cmd/entra/group/group-list.mdx b/docs/docs/cmd/entra/group/group-list.mdx index be9a4c7506b..f7cc045a7f2 100644 --- a/docs/docs/cmd/entra/group/group-list.mdx +++ b/docs/docs/cmd/entra/group/group-list.mdx @@ -23,10 +23,17 @@ m365 aad group list [options] ```md definition-list `--type [type]` : Filter the results to only groups of a given type. Allowed values: `microsoft365`, `security`, `distribution`, `mailEnabledSecurity`. By default, all groups are listed. + +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. ``` +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of group properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + ## Examples Lists all groups defined in Entra ID. @@ -35,10 +42,10 @@ Lists all groups defined in Entra ID. m365 entra group list ``` -List all security groups defined in Entra ID. +List all security groups defined in Entra ID with specified properties. ```sh -m365 entra group list --type security +m365 entra group list --type security --properties "mail,displayName" ``` ## Response diff --git a/src/m365/entra/commands/administrativeunit/administrativeunit-get.spec.ts b/src/m365/entra/commands/administrativeunit/administrativeunit-get.spec.ts index d5b26f3fac1..75b10d30c5a 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-get.spec.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-get.spec.ts @@ -106,6 +106,19 @@ describe(commands.ADMINISTRATIVEUNIT_GET, () => { assert(loggerLogSpy.calledOnceWithExactly(administrativeUnitsReponse.value[0])); }); + it('retrieves information about the specified administrative unit by id with specified properties', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/directory/administrativeUnits/${validId}?$select=id,displayName,visibility`) { + return administrativeUnitsReponse.value[0]; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { id: validId, properties: 'id,displayName,visibility' } }); + assert(loggerLogSpy.calledOnceWithExactly(administrativeUnitsReponse.value[0])); + }); + it('retrieves information about the specified administrative unit by displayName', async () => { sinon.stub(entraAdministrativeUnit, 'getAdministrativeUnitByDisplayName').resolves(administrativeUnitsReponse.value[0]); diff --git a/src/m365/entra/commands/administrativeunit/administrativeunit-get.ts b/src/m365/entra/commands/administrativeunit/administrativeunit-get.ts index 930c7015c83..473d2dc3d82 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-get.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-get.ts @@ -15,6 +15,7 @@ interface CommandArgs { export interface Options extends GlobalOptions { id?: string; displayName?: string; + properties?: string; } class EntraAdministrativeUnitGetCommand extends GraphCommand { @@ -44,7 +45,8 @@ class EntraAdministrativeUnitGetCommand extends GraphCommand { this.telemetry.push((args: CommandArgs) => { Object.assign(this.telemetryProperties, { id: typeof args.options.id !== 'undefined', - displayName: typeof args.options.displayName !== 'undefined' + displayName: typeof args.options.displayName !== 'undefined', + properties: typeof args.options.properties !== 'undefined' }); }); } @@ -56,6 +58,9 @@ class EntraAdministrativeUnitGetCommand extends GraphCommand { }, { option: '-n, --displayName [displayName]' + }, + { + option: '-p, --properties [properties]' } ); } @@ -87,7 +92,7 @@ class EntraAdministrativeUnitGetCommand extends GraphCommand { try { if (args.options.id) { - administrativeUnit = await this.getAdministrativeUnitById(args.options.id); + administrativeUnit = await this.getAdministrativeUnitById(args.options.id, args.options.properties); } else { administrativeUnit = await entraAdministrativeUnit.getAdministrativeUnitByDisplayName(args.options.displayName!); @@ -100,9 +105,24 @@ class EntraAdministrativeUnitGetCommand extends GraphCommand { } } - async getAdministrativeUnitById(id: string): Promise { + async getAdministrativeUnitById(id: string, properties?: string): Promise { + const queryParameters: string[] = []; + + if (properties) { + const allProperties = properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/directory/administrativeUnits/${id}`, + url: `${this.resource}/v1.0/directory/administrativeUnits/${id}${queryString}`, headers: { accept: 'application/json;odata.metadata=none' }, diff --git a/src/m365/entra/commands/administrativeunit/administrativeunit-list.spec.ts b/src/m365/entra/commands/administrativeunit/administrativeunit-list.spec.ts index f1d18809f7b..e7644f27f3a 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-list.spec.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-list.spec.ts @@ -116,6 +116,44 @@ describe(commands.ADMINISTRATIVEUNIT_LIST, () => { ); }); + it(`should get a list of administrative units with specified properties`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/directory/administrativeUnits?$select=id,displayName`) { + return { + value: [ + { + id: 'fc33aa61-cf0e-46b6-9506-f633347202ab', + displayName: 'European Division' + }, + { + id: 'a25b4c5e-e8b7-4f02-a23d-0965b6415098', + displayName: 'Asian Division' + } + ] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { properties: 'id,displayName' } + }); + + assert( + loggerLogSpy.calledWith([ + { + id: 'fc33aa61-cf0e-46b6-9506-f633347202ab', + displayName: 'European Division' + }, + { + id: 'a25b4c5e-e8b7-4f02-a23d-0965b6415098', + displayName: 'Asian Division' + } + ]) + ); + }); + it('handles error when retrieving administrative units list failed', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/directory/administrativeUnits`) { diff --git a/src/m365/entra/commands/administrativeunit/administrativeunit-list.ts b/src/m365/entra/commands/administrativeunit/administrativeunit-list.ts index 826bfe432dd..3ce880e5b79 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-list.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-list.ts @@ -4,6 +4,15 @@ import { odata } from '../../../../utils/odata.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; import aadCommands from '../../aadCommands.js'; +import GlobalOptions from '../../../../GlobalOptions.js'; + +interface CommandArgs { + options: Options; +} + +export interface Options extends GlobalOptions { + properties?: string; +} class EntraAdministrativeUnitListCommand extends GraphCommand { public get name(): string { @@ -22,11 +31,47 @@ class EntraAdministrativeUnitListCommand extends GraphCommand { return ['id', 'displayName', 'visibility']; } - public async commandAction(logger: Logger): Promise { + constructor() { + super(); + + this.#initTelemetry(); + this.#initOptions(); + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + properties: typeof args.options.properties !== 'undefined' + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { option: '-p, --properties [properties]' } + ); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { await this.showDeprecationWarning(logger, aadCommands.ADMINISTRATIVEUNIT_LIST, commands.ADMINISTRATIVEUNIT_LIST); + const queryParameters: string[] = []; + + if (args.options.properties) { + const allProperties = args.options.properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + try { - const results = await odata.getAllItems(`${this.resource}/v1.0/directory/administrativeUnits`); + const results = await odata.getAllItems(`${this.resource}/v1.0/directory/administrativeUnits${queryString}`); await logger.log(results); } catch (err: any) { diff --git a/src/m365/entra/commands/app/app-get.spec.ts b/src/m365/entra/commands/app/app-get.spec.ts index 50a93894b01..a2c4829c3e6 100644 --- a/src/m365/entra/commands/app/app-get.spec.ts +++ b/src/m365/entra/commands/app/app-get.spec.ts @@ -296,13 +296,11 @@ describe(commands.APP_GET, () => { }; } - if ((opts.url as string).indexOf('/v1.0/myorganization/applications/') > -1) { + if (opts.url === "https://graph.microsoft.com/v1.0/myorganization/applications/340a4aa3-1af6-43ac-87d8-189819003952?$select=id,appId,displayName") { return { "id": "340a4aa3-1af6-43ac-87d8-189819003952", "appId": "9b1b1e42-794b-4c71-93ac-5ed92488b67f", - "createdDateTime": "2019-10-29T17:46:55Z", - "displayName": "My App", - "description": null + "displayName": "My App" }; } @@ -312,7 +310,8 @@ describe(commands.APP_GET, () => { await command.action(logger, { options: { - appId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' + appId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + properties: 'id,appId,displayName' } }); const call: sinon.SinonSpyCall = loggerLogSpy.lastCall; @@ -366,7 +365,7 @@ describe(commands.APP_GET, () => { it(`should get an Microsoft Entra app registration by its object ID. Doesn't save the app info if not requested`, async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/myorganization/applications/340a4aa3-1af6-43ac-87d8-189819003952`) { + if (opts.url === `https://graph.microsoft.com/v1.0/myorganization/applications/340a4aa3-1af6-43ac-87d8-189819003952?$select=id,appId,displayName`) { return { "id": "340a4aa3-1af6-43ac-87d8-189819003952", "appId": "9b1b1e42-794b-4c71-93ac-5ed92488b67f", @@ -381,7 +380,8 @@ describe(commands.APP_GET, () => { await command.action(logger, { options: { - objectId: '340a4aa3-1af6-43ac-87d8-189819003952' + objectId: '340a4aa3-1af6-43ac-87d8-189819003952', + properties: 'id,appId,displayName' } }); const call: sinon.SinonSpyCall = loggerLogSpy.lastCall; diff --git a/src/m365/entra/commands/app/app-get.ts b/src/m365/entra/commands/app/app-get.ts index 969a5849d0f..9b0606fdfc4 100644 --- a/src/m365/entra/commands/app/app-get.ts +++ b/src/m365/entra/commands/app/app-get.ts @@ -20,6 +20,7 @@ export interface Options extends GlobalOptions { objectId?: string; name?: string; save?: boolean; + properties?: string; } class EntraAppGetCommand extends GraphCommand { @@ -49,7 +50,8 @@ class EntraAppGetCommand extends GraphCommand { Object.assign(this.telemetryProperties, { appId: typeof args.options.appId !== 'undefined', objectId: typeof args.options.objectId !== 'undefined', - name: typeof args.options.name !== 'undefined' + name: typeof args.options.name !== 'undefined', + properties: typeof args.options.properties !== 'undefined' }); }); } @@ -59,7 +61,8 @@ class EntraAppGetCommand extends GraphCommand { { option: '--appId [appId]' }, { option: '--objectId [objectId]' }, { option: '--name [name]' }, - { option: '--save' } + { option: '--save' }, + { option: '-p, --properties [properties]' } ); } @@ -88,7 +91,7 @@ class EntraAppGetCommand extends GraphCommand { try { const appObjectId = await this.getAppObjectId(args); - const appInfo = await this.getAppInfo(appObjectId); + const appInfo = await this.getAppInfo(appObjectId, args.options.properties); const res = await this.saveAppInfo(args, appInfo, logger); await logger.log(res); } @@ -132,9 +135,24 @@ class EntraAppGetCommand extends GraphCommand { return result.id; } - private async getAppInfo(appObjectId: string): Promise { + private async getAppInfo(appObjectId: string, properties?: string): Promise { + const queryParameters: string[] = []; + + if (properties) { + const allProperties = properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/myorganization/applications/${appObjectId}`, + url: `${this.resource}/v1.0/myorganization/applications/${appObjectId}${queryString}`, headers: { accept: 'application/json;odata.metadata=none' }, diff --git a/src/m365/entra/commands/app/app-list.spec.ts b/src/m365/entra/commands/app/app-list.spec.ts index cf858293c48..8cd34553b75 100644 --- a/src/m365/entra/commands/app/app-list.spec.ts +++ b/src/m365/entra/commands/app/app-list.spec.ts @@ -124,6 +124,44 @@ describe(commands.APP_LIST, () => { ); }); + it(`should get a list of Microsoft Entra app registrations with specified properties`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/applications?$select=id,displayName`) { + return { + value: [ + { + id: '340a4aa3-1af6-43ac-87d8-189819003952', + displayName: 'My App 1' + }, + { + id: '340a4aa3-1af6-43ac-87d8-189819003953', + displayName: 'My App 2' + } + ] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { properties: 'id,displayName' } + }); + + assert( + loggerLogSpy.calledWith([ + { + id: '340a4aa3-1af6-43ac-87d8-189819003952', + displayName: 'My App 1' + }, + { + id: '340a4aa3-1af6-43ac-87d8-189819003953', + displayName: 'My App 2' + } + ]) + ); + }); + it('handles error when retrieving app list failed', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/applications`) { diff --git a/src/m365/entra/commands/app/app-list.ts b/src/m365/entra/commands/app/app-list.ts index dd1c5fd9223..78d9ceea791 100644 --- a/src/m365/entra/commands/app/app-list.ts +++ b/src/m365/entra/commands/app/app-list.ts @@ -4,6 +4,15 @@ import { odata } from "../../../../utils/odata.js"; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; import aadCommands from '../../aadCommands.js'; +import GlobalOptions from '../../../../GlobalOptions.js'; + +interface CommandArgs { + options: Options; +} + +export interface Options extends GlobalOptions { + properties?: string; +} class EntraAppListCommand extends GraphCommand { public get name(): string { @@ -14,6 +23,13 @@ class EntraAppListCommand extends GraphCommand { return 'Retrieves a list of Entra app registrations'; } + constructor() { + super(); + + this.#initTelemetry(); + this.#initOptions(); + } + public alias(): string[] | undefined { return [aadCommands.APP_LIST, commands.APPREGISTRATION_LIST]; } @@ -22,11 +38,40 @@ class EntraAppListCommand extends GraphCommand { return ['appId', 'id', 'displayName', "signInAudience"]; } - public async commandAction(logger: Logger): Promise { + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + properties: typeof args.options.properties !== 'undefined' + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { option: '-p, --properties [properties]' } + ); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { await this.showDeprecationWarning(logger, aadCommands.APP_LIST, commands.APP_LIST); + const queryParameters: string[] = []; + + if (args.options.properties) { + const allProperties = args.options.properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + try { - const results = await odata.getAllItems(`${this.resource}/v1.0/applications`); + const results = await odata.getAllItems(`${this.resource}/v1.0/applications${queryString}`); await logger.log(results); } catch (err: any) { diff --git a/src/m365/entra/commands/group/group-get.ts b/src/m365/entra/commands/group/group-get.ts index fc2df0c0cc6..229f6160af0 100644 --- a/src/m365/entra/commands/group/group-get.ts +++ b/src/m365/entra/commands/group/group-get.ts @@ -14,6 +14,7 @@ interface CommandArgs { interface Options extends GlobalOptions { id?: string; displayName?: string; + properties?: string; } class EntraGroupGetCommand extends GraphCommand { @@ -45,6 +46,9 @@ class EntraGroupGetCommand extends GraphCommand { }, { option: '-n, --displayName [displayName]' + }, + { + option: '-p, --properties [properties]' } ); } @@ -71,7 +75,8 @@ class EntraGroupGetCommand extends GraphCommand { this.telemetry.push((args: CommandArgs) => { Object.assign(this.telemetryProperties, { id: typeof args.options.id !== 'undefined', - displayName: typeof args.options.displayName !== 'undefined' + displayName: typeof args.options.displayName !== 'undefined', + properties: typeof args.options.properties !== 'undefined' }); }); } @@ -83,10 +88,10 @@ class EntraGroupGetCommand extends GraphCommand { try { if (args.options.id) { - group = await entraGroup.getGroupById(args.options.id); + group = await entraGroup.getGroupById(args.options.id, args.options.properties); } else { - group = await entraGroup.getGroupByDisplayName(args.options.displayName!); + group = await entraGroup.getGroupByDisplayName(args.options.displayName!, args.options.properties); } await logger.log(group); @@ -97,4 +102,4 @@ class EntraGroupGetCommand extends GraphCommand { } } -export default new EntraGroupGetCommand(); +export default new EntraGroupGetCommand(); \ No newline at end of file diff --git a/src/m365/entra/commands/group/group-list.spec.ts b/src/m365/entra/commands/group/group-list.spec.ts index 5751651aba1..216de1e53d7 100644 --- a/src/m365/entra/commands/group/group-list.spec.ts +++ b/src/m365/entra/commands/group/group-list.spec.ts @@ -353,6 +353,30 @@ describe(commands.GROUP_LIST, () => { ])); }); + it('lists all microsoft365 groups in the tenant with specified properties', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified')?$select=id,displayName`) { + return { + "value": [ + { + "id": "00e21c97-7800-4bc1-8024-a400aba6f46d", + "description": "Code Challenge" + } + ] + }; + } + throw 'Invalid request'; + }); + + await command.action(logger, { options: { type: 'microsoft365', properties: 'id,displayName' } }); + assert(loggerLogSpy.calledOnceWithExactly([ + { + "id": "00e21c97-7800-4bc1-8024-a400aba6f46d", + "description": "Code Challenge" + } + ])); + }); + it('lists all distribution groups in the tenant', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=securityEnabled eq false and mailEnabled eq true`) { diff --git a/src/m365/entra/commands/group/group-list.ts b/src/m365/entra/commands/group/group-list.ts index b113840f31f..1cf713703cd 100644 --- a/src/m365/entra/commands/group/group-list.ts +++ b/src/m365/entra/commands/group/group-list.ts @@ -14,6 +14,7 @@ interface CommandArgs { interface Options extends GlobalOptions { type?: string; + properties?: string; } interface ExtendedGroup extends Group { @@ -50,7 +51,8 @@ class EntraGroupListCommand extends GraphCommand { #initTelemetry(): void { this.telemetry.push((args: CommandArgs) => { Object.assign(this.telemetryProperties, { - type: typeof args.options.type !== 'undefined' + type: typeof args.options.type !== 'undefined', + properties: typeof args.options.properties !== 'undefined' }); }); } @@ -60,6 +62,9 @@ class EntraGroupListCommand extends GraphCommand { { option: '--type [type]', autocomplete: EntraGroupListCommand.groupTypes + }, + { + option: '-p, --properties [properties]' } ); } @@ -103,6 +108,23 @@ class EntraGroupListCommand extends GraphCommand { } } + const queryParameters: string[] = []; + + if (args.options.properties) { + const allProperties = args.options.properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + + requestUrl += queryString; + let groups: Group[] = []; if (useConsistencyLevelHeader) { diff --git a/src/utils/entraAdministrativeUnit.spec.ts b/src/utils/entraAdministrativeUnit.spec.ts index 97eadeffa5e..0fff746934c 100644 --- a/src/utils/entraAdministrativeUnit.spec.ts +++ b/src/utils/entraAdministrativeUnit.spec.ts @@ -43,6 +43,26 @@ describe('utils/entraAdministrativeUnit', () => { assert.deepStrictEqual(actual, { id: administrativeUnitId, displayName: displayName }); }); + it('correctly get single administrative unit id by name using getAdministrativeUnitByDisplayName with specified properties', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/directory/administrativeUnits?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'?$select=id,displayName`) { + return { + value: [ + { + id: administrativeUnitId, + displayName: displayName + } + ] + }; + } + + return 'Invalid Request'; + }); + + const actual = await entraAdministrativeUnit.getAdministrativeUnitByDisplayName(displayName, 'id,displayName'); + assert.deepStrictEqual(actual, { id: administrativeUnitId, displayName: displayName }); + }); + it('handles selecting single administrative unit when multiple administrative units with the specified name found using getAdministrativeUnitByDisplayName and cli is set to prompt', async () => { sinon.stub(request, 'get').callsFake(async opts => { if (opts.url === `https://graph.microsoft.com/v1.0/directory/administrativeUnits?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`) { diff --git a/src/utils/entraAdministrativeUnit.ts b/src/utils/entraAdministrativeUnit.ts index 2d536fc564c..310aefb8889 100644 --- a/src/utils/entraAdministrativeUnit.ts +++ b/src/utils/entraAdministrativeUnit.ts @@ -7,12 +7,28 @@ export const entraAdministrativeUnit = { /** * Get an administrative unit by its display name. * @param displayName Administrative unit display name. + * @param properties Properties to include in the response. * @returns The administrative unit. * @throws Error when administrative unit was not found. */ - async getAdministrativeUnitByDisplayName(displayName: string): Promise { + async getAdministrativeUnitByDisplayName(displayName: string, properties?: string): Promise { + const queryParameters: string[] = []; + + if (properties) { + const allProperties = properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + const graphResource = 'https://graph.microsoft.com'; - const administrativeUnits = await odata.getAllItems(`${graphResource}/v1.0/directory/administrativeUnits?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`); + const administrativeUnits = await odata.getAllItems(`${graphResource}/v1.0/directory/administrativeUnits?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'${queryString}`); if (administrativeUnits.length === 0) { throw `The specified administrative unit '${displayName}' does not exist.`; diff --git a/src/utils/entraGroup.spec.ts b/src/utils/entraGroup.spec.ts index 97d60fc8f2f..3a84b7b6c8b 100644 --- a/src/utils/entraGroup.spec.ts +++ b/src/utils/entraGroup.spec.ts @@ -44,7 +44,7 @@ describe('utils/entraGroup', () => { ]); }); - it('correctly get a single group by id.', async () => { + it('correctly get a single group by id', async () => { sinon.stub(request, 'get').callsFake(async opts => { if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validGroupId}`) { return singleGroupResponse; @@ -57,6 +57,19 @@ describe('utils/entraGroup', () => { assert.strictEqual(actual, singleGroupResponse); }); + it('correctly get a single group by id with specified properties', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validGroupId}?$select=id,displayName`) { + return singleGroupResponse; + } + + return 'Invalid Request'; + }); + + const actual = await entraGroup.getGroupById(validGroupId, 'id,displayName'); + assert.strictEqual(actual, singleGroupResponse); + }); + it('throws error message when no group was found using getGroupByDisplayName', async () => { sinon.stub(request, 'get').callsFake(async opts => { if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validGroupName)}'`) { @@ -94,23 +107,6 @@ describe('utils/entraGroup', () => { await assert.rejects(entraGroup.getGroupByDisplayName(validGroupName), Error("Multiple groups with name 'Group name' found. Found: 00000000-0000-0000-0000-000000000000.")); }); - it('correctly get single group by name using getGroupByDisplayName', async () => { - sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validGroupName)}'`) { - return { - value: [ - singleGroupResponse - ] - }; - } - - return 'Invalid Request'; - }); - - const actual = await entraGroup.getGroupByDisplayName(validGroupName); - assert.deepStrictEqual(actual, singleGroupResponse); - }); - it('correctly get single group id by name using getGroupIdByDisplayName', async () => { sinon.stub(request, 'get').callsFake(async opts => { if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validGroupName)}'&$select=id`) { @@ -231,6 +227,26 @@ describe('utils/entraGroup', () => { assert.deepStrictEqual(actual, singleGroupResponse); }); + it('handles selecting single result when multiple groups with the specified name found and cli is set to prompt with specified properties', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validGroupName)}'?$select=id,displayName`) { + return { + value: [ + { id: validGroupId, displayName: validGroupName }, + { id: validGroupId, displayName: validGroupName } + ] + }; + } + + return 'Invalid Request'; + }); + + sinon.stub(cli, 'handleMultipleResultsFound').resolves({ id: validGroupId, displayName: validGroupName }); + + const actual = await entraGroup.getGroupByDisplayName(validGroupName, 'id,displayName'); + assert.deepStrictEqual(actual, singleGroupResponse); + }); + it('returns true if group is a valid m365group', async () => { sinon.stub(request, 'get').callsFake(async opts => { if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validGroupId}?$select=groupTypes`) { diff --git a/src/utils/entraGroup.ts b/src/utils/entraGroup.ts index fb516e48f39..fefb56465b8 100644 --- a/src/utils/entraGroup.ts +++ b/src/utils/entraGroup.ts @@ -11,10 +11,26 @@ export const entraGroup = { /** * Retrieve a single group. * @param id Group ID. + * @param properties Properties to include in the response. */ - async getGroupById(id: string): Promise { + async getGroupById(id: string, properties?: string): Promise { + const queryParameters: string[] = []; + + if (properties) { + const allProperties = properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + const requestOptions: CliRequestOptions = { - url: `${graphResource}/v1.0/groups/${id}`, + url: `${graphResource}/v1.0/groups/${id}${queryString}`, headers: { accept: 'application/json;odata.metadata=none' }, @@ -27,19 +43,36 @@ export const entraGroup = { /** * Get a list of groups by display name. * @param displayName Group display name. + * @param properties Properties to include in the response. */ - async getGroupsByDisplayName(displayName: string): Promise { - return odata.getAllItems(`${graphResource}/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`); + async getGroupsByDisplayName(displayName: string, properties?: string): Promise { + const queryParameters: string[] = []; + + if (properties) { + const allProperties = properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + + return odata.getAllItems(`${graphResource}/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'${queryString}`); }, /** * Get a single group by its display name. * @param displayName Group display name. + * @param properties Properties to include in the response. * @throws Error when group was not found. * @throws Error when multiple groups with the same name were found. */ - async getGroupByDisplayName(displayName: string): Promise { - const groups = await this.getGroupsByDisplayName(displayName); + async getGroupByDisplayName(displayName: string, properties?: string): Promise { + const groups = await this.getGroupsByDisplayName(displayName, properties); if (!groups.length) { throw Error(`The specified group '${displayName}' does not exist.`);