diff --git a/docs/docs/cmd/onenote/notebook/notebook-add.mdx b/docs/docs/cmd/onenote/notebook/notebook-add.mdx index 84a9daafed1..cb44e691869 100644 --- a/docs/docs/cmd/onenote/notebook/notebook-add.mdx +++ b/docs/docs/cmd/onenote/notebook/notebook-add.mdx @@ -36,6 +36,23 @@ m365 onenote notebook add [options] +## Permissions + + + + + | Resource | Permissions | + |-----------------|------------------------------------------------------------------| + | Microsoft Graph | Notes.Create, Sites.Read.All, User.ReadBasic.All, Group.Read.All | + + + + + This command does not support application permissions. + + + + ## Examples Create a Microsoft OneNote notebook for the currently logged in user diff --git a/docs/docs/cmd/onenote/notebook/notebook-list.mdx b/docs/docs/cmd/onenote/notebook/notebook-list.mdx index 0fc22b22d8d..a44ef3447e2 100644 --- a/docs/docs/cmd/onenote/notebook/notebook-list.mdx +++ b/docs/docs/cmd/onenote/notebook/notebook-list.mdx @@ -33,6 +33,23 @@ m365 onenote notebook list [options] +## Permissions + + + + + | Resource | Permissions | + |-----------------|--------------------------------------------------------------------| + | Microsoft Graph | Notes.Read.All, Sites.Read.All, User.ReadBasic.All, Group.Read.All | + + + + + This command does not support application permissions. + + + + ## Examples List Microsoft OneNote notebooks for the currently logged in user diff --git a/docs/docs/cmd/onenote/page/page-list.mdx b/docs/docs/cmd/onenote/page/page-list.mdx index 287e4b16961..25243633c6b 100644 --- a/docs/docs/cmd/onenote/page/page-list.mdx +++ b/docs/docs/cmd/onenote/page/page-list.mdx @@ -35,7 +35,24 @@ m365 onenote page list [options] ## Remarks -When we don't specify either `userId`, `userName`, `groupId`, `groupName` or `webUrl`, the OneNote pages will be retrieved of the currently logged in user. +When you don't specify either `userId`, `userName`, `groupId`, `groupName` or `webUrl`, the OneNote pages will be retrieved of the currently logged in user. + +## Permissions + + + + + | Resource | Permissions | + |-----------------|--------------------------------------------------------------------| + | Microsoft Graph | Notes.Read.All, Sites.Read.All, User.ReadBasic.All, Group.Read.All | + + + + + This command does not support application permissions. + + + ## Examples diff --git a/src/m365/onenote/commands/notebook/notebook-add.spec.ts b/src/m365/onenote/commands/notebook/notebook-add.spec.ts index f98f2c80cfc..efb265ce294 100644 --- a/src/m365/onenote/commands/notebook/notebook-add.spec.ts +++ b/src/m365/onenote/commands/notebook/notebook-add.spec.ts @@ -14,6 +14,8 @@ import commands from '../../commands.js'; import command from './notebook-add.js'; import { entraGroup } from '../../../../utils/entraGroup.js'; import { spo } from '../../../../utils/spo.js'; +import { accessToken } from '../../../../utils/accessToken.js'; +import { formatting } from '../../../../utils/formatting.js'; describe(commands.NOTEBOOK_ADD, () => { const name = 'My Notebook'; @@ -54,6 +56,7 @@ describe(commands.NOTEBOOK_ADD, () => { let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let commandInfo: CommandInfo; + let accessTokenStub: sinon.SinonStub; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -78,11 +81,13 @@ describe(commands.NOTEBOOK_ADD, () => { } }; loggerLogSpy = sinon.spy(logger, 'log'); + accessTokenStub = sinon.stub(accessToken, 'assertAccessTokenType').resolves(); }); afterEach(() => { sinonUtil.restore([ - request.post + request.post, + accessToken.assertAccessTokenType ]); }); @@ -130,6 +135,13 @@ describe(commands.NOTEBOOK_ADD, () => { assert.strictEqual(actual, true); }); + it('enforces the user to use delegated permissions', async () => { + sinon.stub(request, 'post').resolves(); + + await command.action(logger, { options: {} }); + assert(accessTokenStub.calledOnceWithExactly('delegated')); + }); + it('adds notebook for the currently logged in user', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/me/onenote/notebooks`) { @@ -209,7 +221,7 @@ describe(commands.NOTEBOOK_ADD, () => { const userName = 'john@contoso.com'; sinon.stub(request, 'post').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/users/${userName}/onenote/notebooks`) { + if (opts.url === `https://graph.microsoft.com/v1.0/users/${formatting.encodeQueryParameter(userName)}/onenote/notebooks`) { return addResponse; } diff --git a/src/m365/onenote/commands/notebook/notebook-add.ts b/src/m365/onenote/commands/notebook/notebook-add.ts index 75366bf3620..ae4157edc96 100644 --- a/src/m365/onenote/commands/notebook/notebook-add.ts +++ b/src/m365/onenote/commands/notebook/notebook-add.ts @@ -3,9 +3,10 @@ import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { entraGroup } from '../../../../utils/entraGroup.js'; import { validation } from '../../../../utils/validation.js'; -import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; import { spo } from '../../../../utils/spo.js'; +import GraphDelegatedCommand from '../../../base/GraphDelegatedCommand.js'; +import { formatting } from '../../../../utils/formatting.js'; interface CommandArgs { options: Options; @@ -20,7 +21,7 @@ interface Options extends GlobalOptions { webUrl?: string; } -class OneNoteNotebookAddCommand extends GraphCommand { +class OneNoteNotebookAddCommand extends GraphDelegatedCommand { public get name(): string { return commands.NOTEBOOK_ADD; } @@ -146,7 +147,7 @@ class OneNoteNotebookAddCommand extends GraphCommand { endpoint += `users/${args.options.userId}`; } else if (args.options.userName) { - endpoint += `users/${args.options.userName}`; + endpoint += `users/${formatting.encodeQueryParameter(args.options.userName)}`; } else if (args.options.groupId) { endpoint += `groups/${args.options.groupId}`; diff --git a/src/m365/onenote/commands/notebook/notebook-list.spec.ts b/src/m365/onenote/commands/notebook/notebook-list.spec.ts index 85f2c8d210b..226e1cfea20 100644 --- a/src/m365/onenote/commands/notebook/notebook-list.spec.ts +++ b/src/m365/onenote/commands/notebook/notebook-list.spec.ts @@ -12,12 +12,15 @@ import { session } from '../../../../utils/session.js'; import { sinonUtil } from '../../../../utils/sinonUtil.js'; import commands from '../../commands.js'; import command from './notebook-list.js'; +import { accessToken } from '../../../../utils/accessToken.js'; +import { formatting } from '../../../../utils/formatting.js'; describe(commands.NOTEBOOK_LIST, () => { let log: string[]; let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let commandInfo: CommandInfo; + let accessTokenStub: sinon.SinonStub; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -42,12 +45,13 @@ describe(commands.NOTEBOOK_LIST, () => { } }; loggerLogSpy = sinon.spy(logger, 'log'); - (command as any).items = []; + accessTokenStub = sinon.stub(accessToken, 'assertAccessTokenType').resolves(); }); afterEach(() => { sinonUtil.restore([ - request.get + request.get, + accessToken.assertAccessTokenType ]); }); @@ -93,6 +97,13 @@ describe(commands.NOTEBOOK_LIST, () => { assert.strictEqual(actual, true); }); + it('enforces the user to use delegated permissions', async () => { + sinon.stub(request, 'get').resolves([]); + + await command.action(logger, { options: {} }); + assert(accessTokenStub.calledOnceWithExactly('delegated')); + }); + it('lists Microsoft OneNote notebooks for the currently logged in user (debug)', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/me/onenote/notebooks`) { @@ -351,7 +362,7 @@ describe(commands.NOTEBOOK_LIST, () => { it('lists Microsoft OneNote notebooks for user by name', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/users/user1@contoso.onmicrosoft.com/onenote/notebooks`) { + if (opts.url === `https://graph.microsoft.com/v1.0/users/${formatting.encodeQueryParameter('user1@contoso.onmicrosoft.com')}/onenote/notebooks`) { return { "value": [ { diff --git a/src/m365/onenote/commands/notebook/notebook-list.ts b/src/m365/onenote/commands/notebook/notebook-list.ts index 918cf33c3f2..57d1b85af53 100644 --- a/src/m365/onenote/commands/notebook/notebook-list.ts +++ b/src/m365/onenote/commands/notebook/notebook-list.ts @@ -1,12 +1,13 @@ import { Notebook } from '@microsoft/microsoft-graph-types'; import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; -import request, { CliRequestOptions } from '../../../../request.js'; import { entraGroup } from '../../../../utils/entraGroup.js'; import { odata } from '../../../../utils/odata.js'; import { validation } from '../../../../utils/validation.js'; -import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; +import { formatting } from '../../../../utils/formatting.js'; +import GraphDelegatedCommand from '../../../base/GraphDelegatedCommand.js'; +import { spo } from '../../../../utils/spo.js'; interface CommandArgs { options: Options; @@ -20,7 +21,7 @@ interface Options extends GlobalOptions { webUrl?: string; } -class OneNoteNotebookListCommand extends GraphCommand { +class OneNoteNotebookListCommand extends GraphDelegatedCommand { public get name(): string { return commands.NOTEBOOK_LIST; } @@ -101,7 +102,7 @@ class OneNoteNotebookListCommand extends GraphCommand { endpoint += `users/${args.options.userId}`; } else if (args.options.userName) { - endpoint += `users/${args.options.userName}`; + endpoint += `users/${formatting.encodeQueryParameter(args.options.userName)}`; } else if (args.options.groupId) { endpoint += `groups/${args.options.groupId}`; @@ -111,7 +112,7 @@ class OneNoteNotebookListCommand extends GraphCommand { endpoint += `groups/${groupId}`; } else if (args.options.webUrl) { - const siteId = await this.getSpoSiteId(args.options.webUrl); + const siteId = await spo.getSpoGraphSiteId(args.options.webUrl); endpoint += `sites/${siteId}`; } else { @@ -126,20 +127,6 @@ class OneNoteNotebookListCommand extends GraphCommand { return group.id!; } - private async getSpoSiteId(webUrl: string): Promise { - const url = new URL(webUrl); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/sites/${url.hostname}:${url.pathname}`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const site = await request.get<{ id: string }>(requestOptions); - return site.id; - } - public async commandAction(logger: Logger, args: CommandArgs): Promise { try { const endpoint = await this.getEndpointUrl(args); diff --git a/src/m365/onenote/commands/page/page-list.spec.ts b/src/m365/onenote/commands/page/page-list.spec.ts index da76ba0b376..133f4a7445b 100644 --- a/src/m365/onenote/commands/page/page-list.spec.ts +++ b/src/m365/onenote/commands/page/page-list.spec.ts @@ -14,6 +14,7 @@ import { session } from '../../../../utils/session.js'; import { sinonUtil } from '../../../../utils/sinonUtil.js'; import commands from '../../commands.js'; import command from './page-list.js'; +import { accessToken } from '../../../../utils/accessToken.js'; import { settingsNames } from '../../../../settingsNames.js'; describe(commands.PAGE_LIST, () => { @@ -76,6 +77,7 @@ describe(commands.PAGE_LIST, () => { let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let commandInfo: CommandInfo; + let accessTokenStub: sinon.SinonStub; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -84,13 +86,7 @@ describe(commands.PAGE_LIST, () => { sinon.stub(session, 'getId').returns(''); auth.connection.active = true; commandInfo = cli.getCommandInfo(command); - sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName: string, defaultValue: any) => { - if (settingName === 'prompt') { - return false; - } - - return defaultValue; - }); + sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName: string, defaultValue: any) => settingName === settingsNames.prompt ? false : defaultValue); }); beforeEach(() => { @@ -107,14 +103,14 @@ describe(commands.PAGE_LIST, () => { } }; loggerLogSpy = sinon.spy(logger, 'log'); - (command as any).items = []; + accessTokenStub = sinon.stub(accessToken, 'assertAccessTokenType').resolves(); }); afterEach(() => { sinonUtil.restore([ request.get, odata.getAllItems, - cli.getSettingWithDefaultValue + accessToken.assertAccessTokenType ]); }); @@ -165,6 +161,13 @@ describe(commands.PAGE_LIST, () => { assert.strictEqual(actual, true); }); + it('enforces the user to use delegated permissions', async () => { + sinon.stub(odata, 'getAllItems').resolves([]); + + await command.action(logger, { options: {} }); + assert(accessTokenStub.calledOnceWithExactly('delegated')); + }); + it('lists Microsoft OneNote pages for the currently logged in user', async () => { sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => { if (url === `https://graph.microsoft.com/v1.0/me/onenote/pages`) { @@ -191,7 +194,7 @@ describe(commands.PAGE_LIST, () => { it('lists Microsoft OneNote pages for user by name', async () => { sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => { - if (url === `https://graph.microsoft.com/v1.0/users/${userName}/onenote/pages`) { + if (url === `https://graph.microsoft.com/v1.0/users/${formatting.encodeQueryParameter(userName)}/onenote/pages`) { return pageResponse.value; } throw 'Invalid request'; @@ -215,7 +218,7 @@ describe(commands.PAGE_LIST, () => { it('lists Microsoft OneNote pages in group by name', async () => { sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => { - if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'`) { + if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'&$select=id`) { return [{ "id": groupId, "description": groupName, @@ -277,7 +280,7 @@ describe(commands.PAGE_LIST, () => { it('throws error if group by displayName returns no results', async () => { sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => { - if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'`) { + if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'&$select=id`) { return []; } throw 'Invalid request'; @@ -287,17 +290,9 @@ describe(commands.PAGE_LIST, () => { }); it('throws an error if group by displayName returns multiple results', async () => { - sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { - if (settingName === settingsNames.prompt) { - return false; - } - - return defaultValue; - }); - const duplicateGroupId = '9f3c2c36-1682-4922-9ae1-f57d2caf0de1'; sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => { - if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'`) { + if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'&$select=id`) { return [{ "id": groupId, "description": groupName, @@ -311,6 +306,7 @@ describe(commands.PAGE_LIST, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { groupName: groupName } } as any), new CommandError("Multiple groups with name 'Dummy Group A' found. Found: bba4c915-0ac8-47a1-bd05-087a44c92d3b, 9f3c2c36-1682-4922-9ae1-f57d2caf0de1.")); + await assert.rejects(command.action(logger, { options: { groupName: groupName } }), + new CommandError("Multiple groups with name 'Dummy Group A' found. Found: bba4c915-0ac8-47a1-bd05-087a44c92d3b, 9f3c2c36-1682-4922-9ae1-f57d2caf0de1.")); }); }); diff --git a/src/m365/onenote/commands/page/page-list.ts b/src/m365/onenote/commands/page/page-list.ts index 46852b8d74d..9594626b3ae 100644 --- a/src/m365/onenote/commands/page/page-list.ts +++ b/src/m365/onenote/commands/page/page-list.ts @@ -5,8 +5,9 @@ import { entraGroup } from '../../../../utils/entraGroup.js'; import { odata } from '../../../../utils/odata.js'; import { spo } from '../../../../utils/spo.js'; import { validation } from '../../../../utils/validation.js'; -import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; +import GraphDelegatedCommand from '../../../base/GraphDelegatedCommand.js'; +import { formatting } from '../../../../utils/formatting.js'; interface CommandArgs { options: Options; @@ -20,7 +21,7 @@ interface Options extends GlobalOptions { webUrl?: string; } -class OneNotePageListCommand extends GraphCommand { +class OneNotePageListCommand extends GraphDelegatedCommand { public get name(): string { return commands.PAGE_LIST; } @@ -101,13 +102,13 @@ class OneNotePageListCommand extends GraphCommand { endpoint += `users/${args.options.userId}`; } else if (args.options.userName) { - endpoint += `users/${args.options.userName}`; + endpoint += `users/${formatting.encodeQueryParameter(args.options.userName)}`; } else if (args.options.groupId) { endpoint += `groups/${args.options.groupId}`; } else if (args.options.groupName) { - const groupId = await this.getGroupId(args.options.groupName); + const groupId = await entraGroup.getGroupIdByDisplayName(args.options.groupName); endpoint += `groups/${groupId}`; } else if (args.options.webUrl) { @@ -121,11 +122,6 @@ class OneNotePageListCommand extends GraphCommand { return endpoint; } - private async getGroupId(groupName: string): Promise { - const group = await entraGroup.getGroupByDisplayName(groupName); - return group.id!; - } - public async commandAction(logger: Logger, args: CommandArgs): Promise { try { const endpoint = await this.getEndpointUrl(args);