Skip to content

Commit ef552f6

Browse files
committed
Enforces delegated access token for onenote commands. Closes #7049
1 parent a01f353 commit ef552f6

File tree

9 files changed

+112
-44
lines changed

9 files changed

+112
-44
lines changed

docs/docs/cmd/onenote/notebook/notebook-add.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ m365 onenote notebook add [options]
3636

3737
<Global />
3838

39+
## Permissions
40+
41+
<Tabs>
42+
<TabItem value="Delegated">
43+
44+
| Resource | Permissions |
45+
|-----------------|------------------------------------------------------------------|
46+
| Microsoft Graph | Notes.Create, Sites.Read.All, User.ReadBasic.All, Group.Read.All |
47+
48+
</TabItem>
49+
<TabItem value="Application">
50+
51+
This command does not support application permissions.
52+
53+
</TabItem>
54+
</Tabs>
55+
3956
## Examples
4057

4158
Create a Microsoft OneNote notebook for the currently logged in user

docs/docs/cmd/onenote/notebook/notebook-list.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ m365 onenote notebook list [options]
3333

3434
<Global />
3535

36+
## Permissions
37+
38+
<Tabs>
39+
<TabItem value="Delegated">
40+
41+
| Resource | Permissions |
42+
|-----------------|--------------------------------------------------------------------|
43+
| Microsoft Graph | Notes.Read.All, Sites.Read.All, User.ReadBasic.All, Group.Read.All |
44+
45+
</TabItem>
46+
<TabItem value="Application">
47+
48+
This command does not support application permissions.
49+
50+
</TabItem>
51+
</Tabs>
52+
3653
## Examples
3754

3855
List Microsoft OneNote notebooks for the currently logged in user

docs/docs/cmd/onenote/page/page-list.mdx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,24 @@ m365 onenote page list [options]
3535

3636
## Remarks
3737

38-
When we don't specify either `userId`, `userName`, `groupId`, `groupName` or `webUrl`, the OneNote pages will be retrieved of the currently logged in user.
38+
When you don't specify either `userId`, `userName`, `groupId`, `groupName` or `webUrl`, the OneNote pages will be retrieved of the currently logged in user.
39+
40+
## Permissions
41+
42+
<Tabs>
43+
<TabItem value="Delegated">
44+
45+
| Resource | Permissions |
46+
|-----------------|--------------------------------------------------------------------|
47+
| Microsoft Graph | Notes.Read.All, Sites.Read.All, User.ReadBasic.All, Group.Read.All |
48+
49+
</TabItem>
50+
<TabItem value="Application">
51+
52+
This command does not support application permissions.
53+
54+
</TabItem>
55+
</Tabs>
3956

4057
## Examples
4158

src/m365/onenote/commands/notebook/notebook-add.spec.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import commands from '../../commands.js';
1414
import command from './notebook-add.js';
1515
import { entraGroup } from '../../../../utils/entraGroup.js';
1616
import { spo } from '../../../../utils/spo.js';
17+
import { accessToken } from '../../../../utils/accessToken.js';
18+
import { formatting } from '../../../../utils/formatting.js';
1719

1820
describe(commands.NOTEBOOK_ADD, () => {
1921
const name = 'My Notebook';
@@ -54,6 +56,7 @@ describe(commands.NOTEBOOK_ADD, () => {
5456
let logger: Logger;
5557
let loggerLogSpy: sinon.SinonSpy;
5658
let commandInfo: CommandInfo;
59+
let accessTokenStub: sinon.SinonStub;
5760

5861
before(() => {
5962
sinon.stub(auth, 'restoreAuth').resolves();
@@ -78,11 +81,13 @@ describe(commands.NOTEBOOK_ADD, () => {
7881
}
7982
};
8083
loggerLogSpy = sinon.spy(logger, 'log');
84+
accessTokenStub = sinon.stub(accessToken, 'assertAccessTokenType').resolves();
8185
});
8286

8387
afterEach(() => {
8488
sinonUtil.restore([
85-
request.post
89+
request.post,
90+
accessToken.assertAccessTokenType
8691
]);
8792
});
8893

@@ -130,6 +135,13 @@ describe(commands.NOTEBOOK_ADD, () => {
130135
assert.strictEqual(actual, true);
131136
});
132137

138+
it('enforces the user to use delegated permissions', async () => {
139+
sinon.stub(request, 'post').resolves();
140+
141+
await command.action(logger, { options: {} });
142+
assert(accessTokenStub.calledOnceWithExactly('delegated'));
143+
});
144+
133145
it('adds notebook for the currently logged in user', async () => {
134146
sinon.stub(request, 'post').callsFake(async (opts) => {
135147
if (opts.url === `https://graph.microsoft.com/v1.0/me/onenote/notebooks`) {
@@ -209,7 +221,7 @@ describe(commands.NOTEBOOK_ADD, () => {
209221
const userName = '[email protected]';
210222

211223
sinon.stub(request, 'post').callsFake(async (opts) => {
212-
if (opts.url === `https://graph.microsoft.com/v1.0/users/${userName}/onenote/notebooks`) {
224+
if (opts.url === `https://graph.microsoft.com/v1.0/users/${formatting.encodeQueryParameter(userName)}/onenote/notebooks`) {
213225
return addResponse;
214226
}
215227

src/m365/onenote/commands/notebook/notebook-add.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import GlobalOptions from '../../../../GlobalOptions.js';
33
import request, { CliRequestOptions } from '../../../../request.js';
44
import { entraGroup } from '../../../../utils/entraGroup.js';
55
import { validation } from '../../../../utils/validation.js';
6-
import GraphCommand from '../../../base/GraphCommand.js';
76
import commands from '../../commands.js';
87
import { spo } from '../../../../utils/spo.js';
8+
import GraphDelegatedCommand from '../../../base/GraphDelegatedCommand.js';
9+
import { formatting } from '../../../../utils/formatting.js';
910

1011
interface CommandArgs {
1112
options: Options;
@@ -20,7 +21,7 @@ interface Options extends GlobalOptions {
2021
webUrl?: string;
2122
}
2223

23-
class OneNoteNotebookAddCommand extends GraphCommand {
24+
class OneNoteNotebookAddCommand extends GraphDelegatedCommand {
2425
public get name(): string {
2526
return commands.NOTEBOOK_ADD;
2627
}
@@ -146,7 +147,7 @@ class OneNoteNotebookAddCommand extends GraphCommand {
146147
endpoint += `users/${args.options.userId}`;
147148
}
148149
else if (args.options.userName) {
149-
endpoint += `users/${args.options.userName}`;
150+
endpoint += `users/${formatting.encodeQueryParameter(args.options.userName)}`;
150151
}
151152
else if (args.options.groupId) {
152153
endpoint += `groups/${args.options.groupId}`;

src/m365/onenote/commands/notebook/notebook-list.spec.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import { session } from '../../../../utils/session.js';
1212
import { sinonUtil } from '../../../../utils/sinonUtil.js';
1313
import commands from '../../commands.js';
1414
import command from './notebook-list.js';
15+
import { accessToken } from '../../../../utils/accessToken.js';
16+
import { formatting } from '../../../../utils/formatting.js';
1517

1618
describe(commands.NOTEBOOK_LIST, () => {
1719
let log: string[];
1820
let logger: Logger;
1921
let loggerLogSpy: sinon.SinonSpy;
2022
let commandInfo: CommandInfo;
23+
let accessTokenStub: sinon.SinonStub;
2124

2225
before(() => {
2326
sinon.stub(auth, 'restoreAuth').resolves();
@@ -42,12 +45,13 @@ describe(commands.NOTEBOOK_LIST, () => {
4245
}
4346
};
4447
loggerLogSpy = sinon.spy(logger, 'log');
45-
(command as any).items = [];
48+
accessTokenStub = sinon.stub(accessToken, 'assertAccessTokenType').resolves();
4649
});
4750

4851
afterEach(() => {
4952
sinonUtil.restore([
50-
request.get
53+
request.get,
54+
accessToken.assertAccessTokenType
5155
]);
5256
});
5357

@@ -93,6 +97,13 @@ describe(commands.NOTEBOOK_LIST, () => {
9397
assert.strictEqual(actual, true);
9498
});
9599

100+
it('enforces the user to use delegated permissions', async () => {
101+
sinon.stub(request, 'get').resolves([]);
102+
103+
await command.action(logger, { options: {} });
104+
assert(accessTokenStub.calledOnceWithExactly('delegated'));
105+
});
106+
96107
it('lists Microsoft OneNote notebooks for the currently logged in user (debug)', async () => {
97108
sinon.stub(request, 'get').callsFake(async (opts) => {
98109
if (opts.url === `https://graph.microsoft.com/v1.0/me/onenote/notebooks`) {
@@ -351,7 +362,7 @@ describe(commands.NOTEBOOK_LIST, () => {
351362

352363
it('lists Microsoft OneNote notebooks for user by name', async () => {
353364
sinon.stub(request, 'get').callsFake(async (opts) => {
354-
if (opts.url === `https://graph.microsoft.com/v1.0/users/[email protected]/onenote/notebooks`) {
365+
if (opts.url === `https://graph.microsoft.com/v1.0/users/${formatting.encodeQueryParameter('[email protected]')}/onenote/notebooks`) {
355366
return {
356367
"value": [
357368
{

src/m365/onenote/commands/notebook/notebook-list.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import request, { CliRequestOptions } from '../../../../request.js';
55
import { entraGroup } from '../../../../utils/entraGroup.js';
66
import { odata } from '../../../../utils/odata.js';
77
import { validation } from '../../../../utils/validation.js';
8-
import GraphCommand from '../../../base/GraphCommand.js';
98
import commands from '../../commands.js';
9+
import { formatting } from '../../../../utils/formatting.js';
10+
import GraphDelegatedCommand from '../../../base/GraphDelegatedCommand.js';
1011

1112
interface CommandArgs {
1213
options: Options;
@@ -20,7 +21,7 @@ interface Options extends GlobalOptions {
2021
webUrl?: string;
2122
}
2223

23-
class OneNoteNotebookListCommand extends GraphCommand {
24+
class OneNoteNotebookListCommand extends GraphDelegatedCommand {
2425
public get name(): string {
2526
return commands.NOTEBOOK_LIST;
2627
}
@@ -101,7 +102,7 @@ class OneNoteNotebookListCommand extends GraphCommand {
101102
endpoint += `users/${args.options.userId}`;
102103
}
103104
else if (args.options.userName) {
104-
endpoint += `users/${args.options.userName}`;
105+
endpoint += `users/${formatting.encodeQueryParameter(args.options.userName)}`;
105106
}
106107
else if (args.options.groupId) {
107108
endpoint += `groups/${args.options.groupId}`;
@@ -129,7 +130,7 @@ class OneNoteNotebookListCommand extends GraphCommand {
129130
private async getSpoSiteId(webUrl: string): Promise<string> {
130131
const url = new URL(webUrl);
131132
const requestOptions: CliRequestOptions = {
132-
url: `${this.resource}/v1.0/sites/${url.hostname}:${url.pathname}`,
133+
url: `${this.resource}/v1.0/sites/${url.hostname}:${url.pathname}?$select=id`,
133134
headers: {
134135
accept: 'application/json;odata.metadata=none'
135136
},

src/m365/onenote/commands/page/page-list.spec.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { session } from '../../../../utils/session.js';
1414
import { sinonUtil } from '../../../../utils/sinonUtil.js';
1515
import commands from '../../commands.js';
1616
import command from './page-list.js';
17+
import { accessToken } from '../../../../utils/accessToken.js';
1718
import { settingsNames } from '../../../../settingsNames.js';
1819

1920
describe(commands.PAGE_LIST, () => {
@@ -76,6 +77,7 @@ describe(commands.PAGE_LIST, () => {
7677
let logger: Logger;
7778
let loggerLogSpy: sinon.SinonSpy;
7879
let commandInfo: CommandInfo;
80+
let accessTokenStub: sinon.SinonStub;
7981

8082
before(() => {
8183
sinon.stub(auth, 'restoreAuth').resolves();
@@ -84,13 +86,7 @@ describe(commands.PAGE_LIST, () => {
8486
sinon.stub(session, 'getId').returns('');
8587
auth.connection.active = true;
8688
commandInfo = cli.getCommandInfo(command);
87-
sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName: string, defaultValue: any) => {
88-
if (settingName === 'prompt') {
89-
return false;
90-
}
91-
92-
return defaultValue;
93-
});
89+
sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName: string, defaultValue: any) => settingName === settingsNames.prompt ? false : defaultValue);
9490
});
9591

9692
beforeEach(() => {
@@ -107,14 +103,14 @@ describe(commands.PAGE_LIST, () => {
107103
}
108104
};
109105
loggerLogSpy = sinon.spy(logger, 'log');
110-
(command as any).items = [];
106+
accessTokenStub = sinon.stub(accessToken, 'assertAccessTokenType').resolves();
111107
});
112108

113109
afterEach(() => {
114110
sinonUtil.restore([
115111
request.get,
116112
odata.getAllItems,
117-
cli.getSettingWithDefaultValue
113+
accessToken.assertAccessTokenType
118114
]);
119115
});
120116

@@ -165,6 +161,13 @@ describe(commands.PAGE_LIST, () => {
165161
assert.strictEqual(actual, true);
166162
});
167163

164+
it('enforces the user to use delegated permissions', async () => {
165+
sinon.stub(odata, 'getAllItems').resolves([]);
166+
167+
await command.action(logger, { options: {} });
168+
assert(accessTokenStub.calledOnceWithExactly('delegated'));
169+
});
170+
168171
it('lists Microsoft OneNote pages for the currently logged in user', async () => {
169172
sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => {
170173
if (url === `https://graph.microsoft.com/v1.0/me/onenote/pages`) {
@@ -191,7 +194,7 @@ describe(commands.PAGE_LIST, () => {
191194

192195
it('lists Microsoft OneNote pages for user by name', async () => {
193196
sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => {
194-
if (url === `https://graph.microsoft.com/v1.0/users/${userName}/onenote/pages`) {
197+
if (url === `https://graph.microsoft.com/v1.0/users/${formatting.encodeQueryParameter(userName)}/onenote/pages`) {
195198
return pageResponse.value;
196199
}
197200
throw 'Invalid request';
@@ -215,7 +218,7 @@ describe(commands.PAGE_LIST, () => {
215218

216219
it('lists Microsoft OneNote pages in group by name', async () => {
217220
sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => {
218-
if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'`) {
221+
if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'&$select=id`) {
219222
return [{
220223
"id": groupId,
221224
"description": groupName,
@@ -277,7 +280,7 @@ describe(commands.PAGE_LIST, () => {
277280

278281
it('throws error if group by displayName returns no results', async () => {
279282
sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => {
280-
if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'`) {
283+
if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'&$select=id`) {
281284
return [];
282285
}
283286
throw 'Invalid request';
@@ -287,17 +290,9 @@ describe(commands.PAGE_LIST, () => {
287290
});
288291

289292
it('throws an error if group by displayName returns multiple results', async () => {
290-
sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => {
291-
if (settingName === settingsNames.prompt) {
292-
return false;
293-
}
294-
295-
return defaultValue;
296-
});
297-
298293
const duplicateGroupId = '9f3c2c36-1682-4922-9ae1-f57d2caf0de1';
299294
sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => {
300-
if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'`) {
295+
if (url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(groupName)}'&$select=id`) {
301296
return [{
302297
"id": groupId,
303298
"description": groupName,
@@ -311,6 +306,7 @@ describe(commands.PAGE_LIST, () => {
311306
throw 'Invalid request';
312307
});
313308

314-
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."));
309+
await assert.rejects(command.action(logger, { options: { groupName: groupName } }),
310+
new CommandError("Multiple groups with name 'Dummy Group A' found. Found: bba4c915-0ac8-47a1-bd05-087a44c92d3b, 9f3c2c36-1682-4922-9ae1-f57d2caf0de1."));
315311
});
316312
});

0 commit comments

Comments
 (0)