Skip to content

Commit

Permalink
New command: m365 entra multitenant remove
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinM85 committed May 7, 2024
1 parent 5f64790 commit 7d350ec
Show file tree
Hide file tree
Showing 6 changed files with 445 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const dictionary = [
'member',
'messaging',
'model',
'multitenant',
'm365',
'news',
'oauth2',
Expand Down
50 changes: 50 additions & 0 deletions docs/docs/cmd/entra/multitenant/multitenant-remove.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Global from '/docs/cmd/_global.mdx';

# entra multitenant remove

Removes a multitenant organization

## Usage

```sh
m365 entra multitenant remove [options]
```

## options

```md definition-list
`-f, --force`
: Don't prompt for confirmation.
```

<Global />

## Remarks

:::info

To use this command you must be at least **Security Administrator**.

:::

## Examples

Remove the multitenant organization

```sh
m365 entra multitenant remove
```

Remove the multitenant organization without prompting for confirmation

```sh
m365 entra multitenant remove --force
```

## Response

The command won't return a response on success

## More information

- Multitenant organization: https://learn.microsoft.com/entra/identity/multi-tenant-organizations/overview
9 changes: 9 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,15 @@ const sidebars: SidebarsConfig = {
}
]
},
{
multitenant: [
{
type: 'doc',
label: 'multitenant remove',
id: 'cmd/entra/multitenant/multitenant-remove'
}
]
},
{
m365group: [
{
Expand Down
1 change: 1 addition & 0 deletions src/m365/entra/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export default {
M365GROUP_USER_LIST: `${prefix} m365group user list`,
M365GROUP_USER_REMOVE: `${prefix} m365group user remove`,
M365GROUP_USER_SET: `${prefix} m365group user set`,
MULTITENANT_REMOVE: `${prefix} multitenant remove`,
OAUTH2GRANT_ADD: `${prefix} oauth2grant add`,
OAUTH2GRANT_LIST: `${prefix} oauth2grant list`,
OAUTH2GRANT_REMOVE: `${prefix} oauth2grant remove`,
Expand Down
229 changes: 229 additions & 0 deletions src/m365/entra/commands/multitenant/multitenant-remove.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import assert from 'assert';
import sinon from 'sinon';
import auth from '../../../../Auth.js';
import { Logger } from '../../../../cli/Logger.js';
import commands from '../../commands.js';
import { cli } from '../../../../cli/cli.js';
import request from '../../../../request.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import command from './multitenant-remove.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { session } from '../../../../utils/session.js';
import { CommandError } from '../../../../Command.js';

describe(commands.MULTITENANT_REMOVE, () => {
const tenantId = "526dcbd1-4f42-469e-be90-ba4a7c0b7802";
const organization = {
"id": "526dcbd1-4f42-469e-be90-ba4a7c0b7802"
};
const multitenantOrganizationMembers = [
{
"tenantId": "526dcbd1-4f42-469e-be90-ba4a7c0b7802"
},
{
"tenantId": "6babcaad-604b-40ac-a9d7-9fd97c0b779f"
}
];
let log: string[];
let logger: Logger;
let promptIssued: boolean;

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);
}
};
sinon.stub(cli, 'promptForConfirmation').callsFake(() => {
promptIssued = true;
return Promise.resolve(false);
});

promptIssued = false;
});

afterEach(() => {
sinonUtil.restore([
request.delete,
request.get,
cli.handleMultipleResultsFound,
cli.promptForConfirmation,
global.setTimeout
]);
});

after(() => {
sinon.restore();
auth.connection.active = false;
});

it('has correct name', () => {
assert.strictEqual(command.name, commands.MULTITENANT_REMOVE);
});

it('has a description', () => {
assert.notStrictEqual(command.description, null);
});

it('removes the multitenant organization without prompting for confirmation', async () => {
let i = 0;
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/organization?$select=id`) {
return {
value: [
organization
]
};
}

if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants?$select=tenantId`) {
if (i++ < 2) {
return {
value: multitenantOrganizationMembers
};
}
return {
value: [
multitenantOrganizationMembers[0]
]
};
}

throw 'Invalid request';
});
const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants/${multitenantOrganizationMembers[0].tenantId}`
|| opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants/${multitenantOrganizationMembers[1].tenantId}`) {
return;
}

throw 'Invalid request';
});

sinon.stub(global, 'setTimeout').callsFake((fn) => {
fn();
return {} as any;
});

await command.action(logger, { options: { force: true, verbose: true } });
assert(deleteRequestStub.calledTwice);
});

it('removes the multitenant organization while prompting for confirmation', async () => {
let i = 0;
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/organization?$select=id`) {
return {
value: [
organization
]
};
}

if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants?$select=tenantId`) {
if (i++ < 2) {
return {
value: multitenantOrganizationMembers
};
}
return {
value: [
multitenantOrganizationMembers[0]
]
};
}

throw 'Invalid request';
});
const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants/${multitenantOrganizationMembers[0].tenantId}`
|| opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants/${multitenantOrganizationMembers[1].tenantId}`) {
return;
}

throw 'Invalid request';
});

sinon.stub(global, 'setTimeout').callsFake((fn) => {
fn();
return {} as any;
});
sinonUtil.restore(cli.promptForConfirmation);
sinon.stub(cli, 'promptForConfirmation').resolves(true);

await command.action(logger, { options: { } });
assert(deleteRequestStub.calledTwice);
});

it('prompts before removing the multitenant organization when prompt option not passed', async () => {
await command.action(logger, { options: { } });

assert(promptIssued);
});

it('aborts removing the multitenant organization when prompt not confirmed', async () => {
const deleteSpy = sinon.stub(request, 'delete').resolves();

await command.action(logger, { options: { } });
assert(deleteSpy.notCalled);
});

it('throws an error when one of the tenant cannot be found', async () => {
const error = {
error: {
code: 'Request_ResourceNotFound',
message: `Resource '${tenantId}' does not exist or one of its queried reference-property objects are not present.`,
innerError: {
date: '2024-05-07T06:59:51',
'request-id': 'b7dee9ee-d85b-4e7a-8686-74852cbfd85b',
'client-request-id': 'b7dee9ee-d85b-4e7a-8686-74852cbfd85b'
}
}
};

sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/organization?$select=id`) {
return {
value: [
organization
]
};
}

if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants?$select=tenantId`) {
return {
value: multitenantOrganizationMembers
};
}

throw 'Invalid request';
});

sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants/${multitenantOrganizationMembers[1].tenantId}`) {
throw error;
}

throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { force: true } }),
new CommandError(error.error.message));
});
});

0 comments on commit 7d350ec

Please sign in to comment.