From ec939d9e46bd00bf862c18fdc65a4fc85ecc4948 Mon Sep 17 00:00:00 2001 From: Nanddeep Nachan Date: Fri, 27 Sep 2024 15:39:54 +0000 Subject: [PATCH 1/3] 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 | 50 ++++++++++++++++- 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 | 53 ++++++++++++++++++- 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, 483 insertions(+), 69 deletions(-) diff --git a/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx b/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx index 8b3e4d93224..66cb5990fcb 100644 --- a/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx +++ b/docs/docs/cmd/entra/administrativeunit/administrativeunit-get.mdx @@ -20,22 +20,29 @@ m365 entra 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 7c0734f3715..01d001710d6 100644 --- a/docs/docs/cmd/entra/administrativeunit/administrativeunit-list.mdx +++ b/docs/docs/cmd/entra/administrativeunit/administrativeunit-list.mdx @@ -14,16 +14,31 @@ m365 entra 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 87fc9aa69b5..4f37dff86b0 100644 --- a/docs/docs/cmd/entra/app/app-get.mdx +++ b/docs/docs/cmd/entra/app/app-get.mdx @@ -22,16 +22,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. ``` @@ -44,24 +47,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 2461a55ddac..0b0324aacf7 100644 --- a/docs/docs/cmd/entra/app/app-list.mdx +++ b/docs/docs/cmd/entra/app/app-list.mdx @@ -20,16 +20,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 490559b0b2b..28df7425c0e 100644 --- a/docs/docs/cmd/entra/group/group-get.mdx +++ b/docs/docs/cmd/entra/group/group-get.mdx @@ -20,22 +20,29 @@ m365 entra 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 da9b300b3a6..8c9b1524ea6 100644 --- a/docs/docs/cmd/entra/group/group-list.mdx +++ b/docs/docs/cmd/entra/group/group-list.mdx @@ -17,10 +17,17 @@ m365 entra 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. @@ -29,10 +36,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 2bee010e9c9..e8accf3b4cd 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-get.spec.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-get.spec.ts @@ -95,6 +95,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 9c3755317c9..3121ceebaa3 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-get.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-get.ts @@ -14,6 +14,7 @@ interface CommandArgs { export interface Options extends GlobalOptions { id?: string; displayName?: string; + properties?: string; } class EntraAdministrativeUnitGetCommand extends GraphCommand { @@ -39,7 +40,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' }); }); } @@ -51,6 +53,9 @@ class EntraAdministrativeUnitGetCommand extends GraphCommand { }, { option: '-n, --displayName [displayName]' + }, + { + option: '-p, --properties [properties]' } ); } @@ -80,7 +85,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!); @@ -93,9 +98,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 c746daa2a22..0dae14cfe7d 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-list.spec.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-list.spec.ts @@ -105,6 +105,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 83b84bc1552..e7d3bcffb00 100644 --- a/src/m365/entra/commands/administrativeunit/administrativeunit-list.ts +++ b/src/m365/entra/commands/administrativeunit/administrativeunit-list.ts @@ -4,6 +4,16 @@ import { odata } from '../../../../utils/odata.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.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 { return commands.ADMINISTRATIVEUNIT_LIST; @@ -17,9 +27,45 @@ 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 { + 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 5147ec30235..5943db2d05d 100644 --- a/src/m365/entra/commands/app/app-get.spec.ts +++ b/src/m365/entra/commands/app/app-get.spec.ts @@ -285,13 +285,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" }; } @@ -301,7 +299,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; @@ -355,7 +354,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", @@ -370,7 +369,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 d4b9be64cec..af4a9b863de 100644 --- a/src/m365/entra/commands/app/app-get.ts +++ b/src/m365/entra/commands/app/app-get.ts @@ -19,6 +19,7 @@ export interface Options extends GlobalOptions { objectId?: string; name?: string; save?: boolean; + properties?: string; } class EntraAppGetCommand extends GraphCommand { @@ -44,7 +45,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' }); }); } @@ -54,7 +56,8 @@ class EntraAppGetCommand extends GraphCommand { { option: '--appId [appId]' }, { option: '--objectId [objectId]' }, { option: '--name [name]' }, - { option: '--save' } + { option: '--save' }, + { option: '-p, --properties [properties]' } ); } @@ -81,7 +84,7 @@ class EntraAppGetCommand extends GraphCommand { public async commandAction(logger: Logger, args: CommandArgs): Promise { 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); } @@ -125,9 +128,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 e94f74a31d0..e92db919a72 100644 --- a/src/m365/entra/commands/app/app-list.spec.ts +++ b/src/m365/entra/commands/app/app-list.spec.ts @@ -113,6 +113,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 24d920ee920..f3728f4d988 100644 --- a/src/m365/entra/commands/app/app-list.ts +++ b/src/m365/entra/commands/app/app-list.ts @@ -3,6 +3,15 @@ import { Logger } from '../../../../cli/Logger.js'; import { odata } from "../../../../utils/odata.js"; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.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 { @@ -13,13 +22,53 @@ 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]; + } + public defaultProperties(): string[] | undefined { 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 { + 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 c642572d3f1..407b9eb50b9 100644 --- a/src/m365/entra/commands/group/group-get.ts +++ b/src/m365/entra/commands/group/group-get.ts @@ -13,6 +13,7 @@ interface CommandArgs { interface Options extends GlobalOptions { id?: string; displayName?: string; + properties?: string; } class EntraGroupGetCommand extends GraphCommand { @@ -40,6 +41,9 @@ class EntraGroupGetCommand extends GraphCommand { }, { option: '-n, --displayName [displayName]' + }, + { + option: '-p, --properties [properties]' } ); } @@ -66,7 +70,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' }); }); } @@ -76,10 +81,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); @@ -90,4 +95,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 9eec88ca640..485c4612cc6 100644 --- a/src/m365/entra/commands/group/group-list.spec.ts +++ b/src/m365/entra/commands/group/group-list.spec.ts @@ -342,6 +342,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 ca2f1acdad0..95e7ce0bcf1 100644 --- a/src/m365/entra/commands/group/group-list.ts +++ b/src/m365/entra/commands/group/group-list.ts @@ -13,6 +13,7 @@ interface CommandArgs { interface Options extends GlobalOptions { type?: string; + properties?: string; } interface ExtendedGroup extends Group { @@ -45,7 +46,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' }); }); } @@ -55,6 +57,9 @@ class EntraGroupListCommand extends GraphCommand { { option: '--type [type]', autocomplete: EntraGroupListCommand.groupTypes + }, + { + option: '-p, --properties [properties]' } ); } @@ -96,6 +101,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..218ffdd4558 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.`); From ae3d9534b9c46f81d748e7459b0e1d68cdbb3c39 Mon Sep 17 00:00:00 2001 From: Nanddeep Nachan Date: Mon, 25 Nov 2024 10:45:38 +0000 Subject: [PATCH 2/3] fix error --- src/m365/entra/commands/app/app-list.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/m365/entra/commands/app/app-list.ts b/src/m365/entra/commands/app/app-list.ts index f3728f4d988..9d906d481d6 100644 --- a/src/m365/entra/commands/app/app-list.ts +++ b/src/m365/entra/commands/app/app-list.ts @@ -29,10 +29,6 @@ class EntraAppListCommand extends GraphCommand { this.#initOptions(); } - public alias(): string[] | undefined { - return [aadCommands.APP_LIST, commands.APPREGISTRATION_LIST]; - } - public defaultProperties(): string[] | undefined { return ['appId', 'id', 'displayName', "signInAudience"]; } From 70a4b53662b80964c549628f0430d0f434010e15 Mon Sep 17 00:00:00 2001 From: Nanddeep Nachan Date: Mon, 25 Nov 2024 10:57:42 +0000 Subject: [PATCH 3/3] test case --- src/utils/entraGroup.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/entraGroup.spec.ts b/src/utils/entraGroup.spec.ts index 3a84b7b6c8b..67376da53e5 100644 --- a/src/utils/entraGroup.spec.ts +++ b/src/utils/entraGroup.spec.ts @@ -229,7 +229,7 @@ describe('utils/entraGroup', () => { 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`) { + 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 },