diff --git a/docs/docs/cmd/entra/resourcenamespace/resourcenamespace-list.mdx b/docs/docs/cmd/entra/resourcenamespace/resourcenamespace-list.mdx new file mode 100644 index 00000000000..cda37a32a79 --- /dev/null +++ b/docs/docs/cmd/entra/resourcenamespace/resourcenamespace-list.mdx @@ -0,0 +1,96 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# entra resourcenamespace list + +Get a list of the RBAC resource namespaces and their properties + +## Usage + +```sh +m365 entra resourcenamespace list [options] +``` + +## Options + + + +## Remarks + +:::warning + +The command is based on an API that is currently in preview and is subject to change once the API reached general availability. + +::: + +## Examples + +Retrieve all resource namespaces. + +```sh +m365 entra resourcenamespace list +``` + +## Response + + + + + ```json + [ + { + "id": "microsoft.directory", + "name": "microsoft.directory" + }, + { + "id": "microsoft.aad.b2c", + "name": "microsoft.aad.b2c" + } + ] + ``` + + + + + ```text + id name + -------------------- -------------------- + microsoft.directory microsoft.directory + microsoft.aad.b2c microsoft.aad.b2c + ``` + + + + + ```csv + id,name + microsoft.directory,microsoft.directory + microsoft.aad.b2c,microsoft.aad.b2c + ``` + + + + + ```md + # entra resourcenamespace list + + Date: 1/31/2025 + + ## microsoft.directory (microsoft.directory) + + Property | Value + ---------|------- + id | microsoft.directory + name | microsoft.directory + + ## microsoft.aad.b2c (microsoft.aad.b2c) + + Property | Value + ---------|------- + id | microsoft.aad.b2c + name | microsoft.aad.b2c + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 3e109021e95..928aabbaf0e 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -632,6 +632,15 @@ const sidebars: SidebarsConfig = { } ] }, + { + resourcenamespace: [ + { + type: 'doc', + label: 'resourcenamespace list', + id: 'cmd/entra/resourcenamespace/resourcenamespace-list' + } + ] + }, { roledefinition: [ { diff --git a/src/config.ts b/src/config.ts index b9240f96c83..016ee7da291 100644 --- a/src/config.ts +++ b/src/config.ts @@ -38,6 +38,7 @@ export default { 'https://graph.microsoft.com/Reports.ReadWrite.All', 'https://graph.microsoft.com/RoleAssignmentSchedule.ReadWrite.Directory', 'https://graph.microsoft.com/RoleEligibilitySchedule.Read.Directory', + 'https://graph.microsoft.com/RoleManagement.Read.Directory', 'https://graph.microsoft.com/SecurityEvents.Read.All', 'https://graph.microsoft.com/ServiceHealth.Read.All', 'https://graph.microsoft.com/ServiceMessage.Read.All', diff --git a/src/m365/entra/commands.ts b/src/m365/entra/commands.ts index 86382e565b6..572747cddd5 100644 --- a/src/m365/entra/commands.ts +++ b/src/m365/entra/commands.ts @@ -89,6 +89,7 @@ export default { PIM_ROLE_ASSIGNMENT_ELIGIBILITY_LIST: `${prefix} pim role assignment eligibility list`, PIM_ROLE_REQUEST_LIST: `${prefix} pim role request list`, POLICY_LIST: `${prefix} policy list`, + RESOURCENAMESPACE_LIST: `${prefix} resourcenamespace list`, ROLEDEFINITION_ADD: `${prefix} roledefinition add`, ROLEDEFINITION_LIST: `${prefix} roledefinition list`, ROLEDEFINITION_GET: `${prefix} roledefinition get`, diff --git a/src/m365/entra/commands/resourcenamespace/resourcenamespace-list.spec.ts b/src/m365/entra/commands/resourcenamespace/resourcenamespace-list.spec.ts new file mode 100644 index 00000000000..698803500f4 --- /dev/null +++ b/src/m365/entra/commands/resourcenamespace/resourcenamespace-list.spec.ts @@ -0,0 +1,118 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command from './resourcenamespace-list.js'; + + +describe(commands.RESOURCENAMESPACE_LIST, () => { + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + + 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; + }); + + 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.RESOURCENAMESPACE_LIST); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('defines correct properties for the default output', () => { + assert.deepStrictEqual(command.defaultProperties(), ['id', 'name']); + }); + + it(`should get a list of resource namespaces`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces`) { + return { + value: [ + { + "id": "microsoft.directory", + "name": "microsoft.directory" + }, + { + "id": "microsoft.aad.b2c", + "name": "microsoft.aad.b2c" + } + ] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { verbose: true } + }); + + assert( + loggerLogSpy.calledWith([ + { + "id": "microsoft.directory", + "name": "microsoft.directory" + }, + { + "id": "microsoft.aad.b2c", + "name": "microsoft.aad.b2c" + } + ]) + ); + }); + + it('handles error when retrieving a list of resource namespaces failed', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces`) { + throw { error: { message: 'An error has occurred' } }; + } + throw `Invalid request`; + }); + + await assert.rejects( + command.action(logger, { options: {} } as any), + new CommandError('An error has occurred') + ); + }); +}); \ No newline at end of file diff --git a/src/m365/entra/commands/resourcenamespace/resourcenamespace-list.ts b/src/m365/entra/commands/resourcenamespace/resourcenamespace-list.ts new file mode 100644 index 00000000000..c9dbda151f8 --- /dev/null +++ b/src/m365/entra/commands/resourcenamespace/resourcenamespace-list.ts @@ -0,0 +1,34 @@ +import { Logger } from '../../../../cli/Logger.js'; +import { odata } from '../../../../utils/odata.js'; +import GraphCommand from '../../../base/GraphCommand.js'; +import commands from '../../commands.js'; + +class ResourceNamespaceListCommand extends GraphCommand { + public get name(): string { + return commands.RESOURCENAMESPACE_LIST; + } + + public get description(): string { + return 'Get a list of the RBAC resource namespaces and their properties'; + } + + public defaultProperties(): string[] | undefined { + return ['id', 'name']; + } + + public async commandAction(logger: Logger): Promise { + if (this.verbose) { + await logger.logToStderr('Getting a list of the RBAC resource namespaces and their properties...'); + } + + try { + const results = await odata.getAllItems<{ id: string, name: string }>(`${this.resource}/beta/roleManagement/directory/resourceNamespaces`); + await logger.log(results); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new ResourceNamespaceListCommand(); \ No newline at end of file