From 25307ab65b829720e6a901aad1a11edf9688f653 Mon Sep 17 00:00:00 2001 From: Martin Machacek Date: Thu, 16 Jan 2025 11:44:52 +0100 Subject: [PATCH] New command: entra rolepermission list --- .../roledefinition/roledefinition-add.mdx | 4 + .../roledefinition/roledefinition-set.mdx | 4 + .../rolepermission/rolepermission-list.mdx | 158 ++++++++++++ docs/src/config/sidebars.ts | 9 + src/m365/entra/commands.ts | 1 + .../rolepermission-list.spec.ts | 233 ++++++++++++++++++ .../rolepermission/rolepermission-list.ts | 73 ++++++ 7 files changed, 482 insertions(+) create mode 100644 docs/docs/cmd/entra/rolepermission/rolepermission-list.mdx create mode 100644 src/m365/entra/commands/rolepermission/rolepermission-list.spec.ts create mode 100644 src/m365/entra/commands/rolepermission/rolepermission-list.ts diff --git a/docs/docs/cmd/entra/roledefinition/roledefinition-add.mdx b/docs/docs/cmd/entra/roledefinition/roledefinition-add.mdx index 93b3c0d7e8..a9b771e516 100644 --- a/docs/docs/cmd/entra/roledefinition/roledefinition-add.mdx +++ b/docs/docs/cmd/entra/roledefinition/roledefinition-add.mdx @@ -33,6 +33,10 @@ m365 entra roledefinition add [options] +## Remarks + +Use the `m365 entra rolepermission list --resourceNamespace microsoft.directory` command to get a list of available resource actions. + ## Examples Create a custom Microsoft Entra ID role diff --git a/docs/docs/cmd/entra/roledefinition/roledefinition-set.mdx b/docs/docs/cmd/entra/roledefinition/roledefinition-set.mdx index a17bebc2e5..90c2a44654 100644 --- a/docs/docs/cmd/entra/roledefinition/roledefinition-set.mdx +++ b/docs/docs/cmd/entra/roledefinition/roledefinition-set.mdx @@ -37,6 +37,10 @@ m365 entra roledefinition set [options] +## Remarks + +Use the `m365 entra rolepermission list --resourceNamespace microsoft.directory` command to get a list of available resource actions. + ## Examples Update a custom Microsoft Entra ID role specified by the id diff --git a/docs/docs/cmd/entra/rolepermission/rolepermission-list.mdx b/docs/docs/cmd/entra/rolepermission/rolepermission-list.mdx new file mode 100644 index 0000000000..66fa7ae959 --- /dev/null +++ b/docs/docs/cmd/entra/rolepermission/rolepermission-list.mdx @@ -0,0 +1,158 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# entra rolepermission list + +Lists all Microsoft Entra ID role permissions. + +## Usage + +```sh +m365 entra rolepermission list [options] +``` + +## Options +```md definition-list +`-n, resourceNamespace [resourceNamespace]` +: The namespace of the resource for which to retrieve role permissions. + +`-p, --privileged` +: Retrieve only sensitive role permissions. + +`--properties [properties]` +: Comma-separated list of properties to retrieve. +``` + + + +## Examples + +### Examples + +Get a list of role permissions + +```sh +m365 entra rolepermission list --resourceNamespace 'microsoft.directory' +``` + +Get a list of sensitive role permissions + +```sh +m365 entra rolepermission list --resourceNamespace 'microsoft.directory' --privileged +``` + +## Response + + + + + ```json + [ + { + "actionVerb": null, + "description": "Create and delete access reviews, and read and update all properties of access reviews in Microsoft Entra ID", + "id": "microsoft.directory-accessReviews-allProperties-allTasks", + "isPrivileged": false, + "name": "microsoft.directory/accessReviews/allProperties/allTasks", + "resourceScopeId": null + }, + { + "actionVerb": "GET", + "description": "Read all properties of access reviews", + "id": "microsoft.directory-accessReviews-allProperties-read-get", + "isPrivileged": false, + "name": "microsoft.directory/accessReviews/allProperties/read", + "resourceScopeId": null + }, + { + "actionVerb": null, + "description": "Manage access reviews of application role assignments in Microsoft Entra ID", + "id": "microsoft.directory-accessReviews-definitions.applications-allProperties-allTasks", + "isPrivileged": false, + "name": "microsoft.directory/accessReviews/definitions.applications/allProperties/allTasks", + "resourceScopeId": null + }, + { + "actionVerb": "GET", + "description": "Read all properties of access reviews of application role assignments in Microsoft Entra ID", + "id": "microsoft.directory-accessReviews-definitions.applications-allProperties-read-get", + "isPrivileged": false, + "name": "microsoft.directory/accessReviews/definitions.applications/allProperties/read", + "resourceScopeId": null + } + ] + ``` + + + + + ```text + id name actionVerb isPrivileged + -------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------- ---------- ------------ + microsoft.directory-accessReviews-allProperties-allTasks microsoft.directory/accessReviews/allProperties/allTasks null false + microsoft.directory-accessReviews-allProperties-read-get microsoft.directory/accessReviews/allProperties/read GET false + microsoft.directory-accessReviews-definitions.applications-allProperties-allTasks microsoft.directory/accessReviews/definitions.applications/allProperties/allTasks null false + microsoft.directory-accessReviews-definitions.applications-allProperties-read-get microsoft.directory/accessReviews/definitions.applications/allProperties/read GET false + ``` + + + + + ```csv + actionVerb,description,id,isPrivileged,name,resourceScopeId + ,"Create and delete access reviews, and read and update all properties of access reviews in Microsoft Entra ID",microsoft.directory-accessReviews-allProperties-allTasks,0,microsoft.directory/accessReviews/allProperties/allTasks, + GET,Read all properties of access reviews,microsoft.directory-accessReviews-allProperties-read-get,0,microsoft.directory/accessReviews/allProperties/read, + ,Manage access reviews of application role assignments in Microsoft Entra ID,microsoft.directory-accessReviews-definitions.applications-allProperties-allTasks,0,microsoft.directory/accessReviews/definitions.applications/allProperties/allTasks, + GET,Read all properties of access reviews of application role assignments in Microsoft Entra ID,microsoft.directory-accessReviews-definitions.applications-allProperties-read-get,0,microsoft.directory/accessReviews/definitions.applications/allProperties/read, + ``` + + + + + ```md + # entra rolepermission list --resourceNamespace "microsoft.directory" + + Date: 1/16/2025 + + ## microsoft.directory/accessReviews/allProperties/allTasks (microsoft.directory-accessReviews-allProperties-allTasks) + + Property | Value + ---------|------- + description | Create and delete access reviews, and read and update all properties of access reviews in Microsoft Entra ID + id | microsoft.directory-accessReviews-allProperties-allTasks + isPrivileged | false + name | microsoft.directory/accessReviews/allProperties/allTasks + + ## microsoft.directory/accessReviews/allProperties/read (microsoft.directory-accessReviews-allProperties-read-get) + + Property | Value + ---------|------- + actionVerb | GET + description | Read all properties of access reviews + id | microsoft.directory-accessReviews-allProperties-read-get + isPrivileged | false + name | microsoft.directory/accessReviews/allProperties/read + + ## microsoft.directory/accessReviews/definitions.applications/allProperties/allTasks (microsoft.directory-accessReviews-definitions.applications-allProperties-allTasks) + + Property | Value + ---------|------- + description | Manage access reviews of application role assignments in Microsoft Entra ID + id | microsoft.directory-accessReviews-definitions.applications-allProperties-allTasks + isPrivileged | false + name | microsoft.directory/accessReviews/definitions.applications/allProperties/allTasks + + ## microsoft.directory/accessReviews/definitions.applications/allProperties/read (microsoft.directory-accessReviews-definitions.applications-allProperties-read-get) + + Property | Value + ---------|------- + actionVerb | GET + description | Read all properties of access reviews of application role assignments in Microsoft Entra ID + id | microsoft.directory-accessReviews-definitions.applications-allProperties-read-get + isPrivileged | false + name | microsoft.directory/accessReviews/definitions.applications/allProperties/read + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 8302d3b8d0..1f317326fb 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -661,6 +661,15 @@ const sidebars: SidebarsConfig = { } ] }, + { + rolepermission: [ + { + type: 'doc', + label: 'rolepermission list', + id: 'cmd/entra/rolepermission/rolepermission-list' + } + ] + }, { siteclassification: [ { diff --git a/src/m365/entra/commands.ts b/src/m365/entra/commands.ts index 15d3156488..86382e565b 100644 --- a/src/m365/entra/commands.ts +++ b/src/m365/entra/commands.ts @@ -94,6 +94,7 @@ export default { ROLEDEFINITION_GET: `${prefix} roledefinition get`, ROLEDEFINITION_REMOVE: `${prefix} roledefinition remove`, ROLEDEFINITION_SET: `${prefix} roledefinition set`, + ROLEPERMISSION_LIST: `${prefix} rolepermission list`, SITECLASSIFICATION_DISABLE: `${prefix} siteclassification disable`, SITECLASSIFICATION_ENABLE: `${prefix} siteclassification enable`, SITECLASSIFICATION_GET: `${prefix} siteclassification get`, diff --git a/src/m365/entra/commands/rolepermission/rolepermission-list.spec.ts b/src/m365/entra/commands/rolepermission/rolepermission-list.spec.ts new file mode 100644 index 0000000000..6d13677539 --- /dev/null +++ b/src/m365/entra/commands/rolepermission/rolepermission-list.spec.ts @@ -0,0 +1,233 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import commands from '../../commands.js'; +import request from '../../../../request.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import command from './rolepermission-list.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import { CommandError } from '../../../../Command.js'; +import { z } from 'zod'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { cli } from '../../../../cli/cli.js'; + +describe(commands.ROLEDEFINITION_LIST, () => { + const resourceNamespace = 'microsoft.directory'; + + const resourceActions = [ + { + "actionVerb": null, + "description": "Create and delete access reviews, and read and update all properties of access reviews in Microsoft Entra ID", + "id": "microsoft.directory-accessReviews-allProperties-allTasks", + "isPrivileged": false, + "name": "microsoft.directory/accessReviews/allProperties/allTasks", + "resourceScopeId": null + }, + { + "actionVerb": "GET", + "description": "Read all properties of access reviews", + "id": "microsoft.directory-accessReviews-allProperties-read-get", + "isPrivileged": false, + "name": "microsoft.directory/accessReviews/allProperties/read", + "resourceScopeId": null + }, + { + "actionVerb": null, + "description": "Create and delete groups, and read and update all properties", + "id": "microsoft.directory-groups-allProperties-allTasks", + "isPrivileged": true, + "name": "microsoft.directory/groups/allProperties/allTasks", + "resourceScopeId": null + }, + { + "actionVerb": null, + "description": "Create and delete OAuth 2.0 permission grants, and read and update all properties", + "id": "microsoft.directory-oAuth2PermissionGrants-allProperties-allTasks", + "isPrivileged": true, + "name": "microsoft.directory/oAuth2PermissionGrants/allProperties/allTasks", + "resourceScopeId": null + } + ]; + + const resourceActionsLimited = [ + { + "id": "microsoft.directory-accessReviews-allProperties-allTasks", + "name": "microsoft.directory/accessReviews/allProperties/allTasks" + }, + { + "id": "microsoft.directory-accessReviews-allProperties-read-get", + "name": "microsoft.directory/accessReviews/allProperties/read" + }, + { + "id": "microsoft.directory-groups-allProperties-allTasks", + "name": "microsoft.directory/groups/allProperties/allTasks" + }, + { + "id": "microsoft.directory-oAuth2PermissionGrants-allProperties-allTasks", + "name": "microsoft.directory/oAuth2PermissionGrants/allProperties/allTasks" + } + ]; + + const filteredResourceActions = [ + { + "actionVerb": null, + "description": "Create and delete groups, and read and update all properties", + "id": "microsoft.directory-groups-allProperties-allTasks", + "isPrivileged": true, + "name": "microsoft.directory/groups/allProperties/allTasks", + "resourceScopeId": null + }, + { + "actionVerb": null, + "description": "Create and delete OAuth 2.0 permission grants, and read and update all properties", + "id": "microsoft.directory-oAuth2PermissionGrants-allProperties-allTasks", + "isPrivileged": true, + "name": "microsoft.directory/oAuth2PermissionGrants/allProperties/allTasks", + "resourceScopeId": null + } + ]; + + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: z.ZodTypeAny; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').returns(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + }); + + afterEach(() => { + sinonUtil.restore([ + request.get + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.ROLEPERMISSION_LIST); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('defines correct properties for the default output', () => { + assert.deepStrictEqual(command.defaultProperties(), ['id', 'name', 'actionVerb', 'isPrivileged']); + }); + + it(`should get a list of Entra ID role permissions from a resource namespace`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces/${resourceNamespace}/resourceActions`) { + return { + value: resourceActions + }; + } + + throw 'Invalid request'; + }); + + const parsedSchema = commandOptionsSchema.safeParse({ + resourceNamespace: resourceNamespace, + verbose: true + }); + await command.action(logger, { + options: parsedSchema.data + }); + + assert(loggerLogSpy.calledWith(resourceActions)); + }); + + it(`should get a list of Entra ID role permissions with specified properties`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces/${resourceNamespace}/resourceActions?$select=id,name`) { + return { + value: resourceActionsLimited + }; + } + + throw 'Invalid request'; + }); + + const parsedSchema = commandOptionsSchema.safeParse({ + resourceNamespace: resourceNamespace, + properties: 'id,name', + verbose: true + }); + await command.action(logger, { + options: parsedSchema.data + }); + + assert(loggerLogSpy.calledWith(resourceActionsLimited)); + }); + + it(`should get a list of privileged Entra ID role permissions`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces/${resourceNamespace}/resourceActions?$filter=isPrivileged eq true`) { + return { + value: filteredResourceActions + }; + } + + throw 'Invalid request'; + }); + + const parsedSchema = commandOptionsSchema.safeParse({ + resourceNamespace: resourceNamespace, + privileged: true, + verbose: true + }); + await command.action(logger, { + options: parsedSchema.data + }); + + assert(loggerLogSpy.calledWith(filteredResourceActions)); + }); + + it('handles error when retrieving role permissions failed', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces/${resourceNamespace}/resourceActions`) { + throw { error: { message: 'An error has occurred' } }; + } + throw `Invalid request`; + }); + + const parsedSchema = commandOptionsSchema.safeParse({ + resourceNamespace: resourceNamespace, + verbose: true + }); + await assert.rejects( + command.action(logger, { options: parsedSchema.data }), + new CommandError('An error has occurred') + ); + }); +}); \ No newline at end of file diff --git a/src/m365/entra/commands/rolepermission/rolepermission-list.ts b/src/m365/entra/commands/rolepermission/rolepermission-list.ts new file mode 100644 index 0000000000..8b90e7d060 --- /dev/null +++ b/src/m365/entra/commands/rolepermission/rolepermission-list.ts @@ -0,0 +1,73 @@ +import { Logger } from '../../../../cli/Logger.js'; +import { odata } from '../../../../utils/odata.js'; +import GraphCommand from '../../../base/GraphCommand.js'; +import commands from '../../commands.js'; +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; +import { zod } from '../../../../utils/zod.js'; +import { UnifiedRbacResourceAction } from '@microsoft/microsoft-graph-types'; + +const options = globalOptionsZod + .extend({ + resourceNamespace: zod.alias('n', z.string()), + privileged: zod.alias('p', z.boolean().optional()), + properties: z.string().optional() + }) + .strict(); + +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +interface UnifiedRbacResourceActionExt extends UnifiedRbacResourceAction { + isPrivileged?: boolean; +} + +class EntraRolePermissionListCommand extends GraphCommand { + public get name(): string { + return commands.ROLEPERMISSION_LIST; + } + + public get description(): string { + return 'Lists all Microsoft Entra ID role permissions from a specifi resource namespace'; + } + + public defaultProperties(): string[] | undefined { + return ['id', 'name', 'actionVerb', 'isPrivileged']; + } + + public get schema(): z.ZodTypeAny | undefined { + return options; + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (this.verbose) { + await logger.logToStderr('Getting Microsoft Entra ID role permissions...'); + } + try { + const queryParameters: string[] = []; + + if (args.options.properties) { + queryParameters.push(`$select=${args.options.properties}`); + } + + if (args.options.privileged) { + queryParameters.push(`$filter=isPrivileged eq true`); + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + const url = `${this.resource}/beta/roleManagement/directory/resourceNamespaces/${args.options.resourceNamespace}/resourceActions${queryString}`; + const results = await odata.getAllItems(url); + await logger.log(results); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new EntraRolePermissionListCommand();