Skip to content

Commit d136e29

Browse files
nicodecleyremilanholemans
authored andcommitted
Enhances 'spo file sharinglink list' with scope option. Closes pnp#4314
1 parent 725ebd5 commit d136e29

File tree

3 files changed

+103
-24
lines changed

3 files changed

+103
-24
lines changed

docs/docs/cmd/spo/file/file-sharinglink-list.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ m365 spo file sharinglink list [options]
1919
`-i, --fileId [fileId]`
2020
: The UniqueId (GUID) of the file. Specify either `fileUrl` or `fileId` but not both.
2121

22+
`--scope [scope]`
23+
: Filter the results to only sharing links of a given scope: `anonymous`, `users` or `organization`. By default all sharing links are listed.
24+
2225
--8<-- "docs/cmd/_global.md"
2326

2427
## Examples
@@ -35,6 +38,12 @@ List sharing links of a file by url
3538
m365 spo file sharinglink list --webUrl https://contoso.sharepoint.com/sites/demo --fileUrl "/sites/demo/shared documents/document.docx"
3639
```
3740

41+
List anonymous sharing links of a file by url
42+
43+
```sh
44+
m365 spo file sharinglink list --webUrl https://contoso.sharepoint.com/sites/demo --fileUrl "/sites/demo/shared documents/document.docx" --scope anonymous
45+
```
46+
3847
## Response
3948

4049
=== "JSON"
@@ -84,14 +93,14 @@ m365 spo file sharinglink list --webUrl https://contoso.sharepoint.com/sites/dem
8493
=== "Text"
8594

8695
```text
87-
id roles link
88-
------------------------------------ ----- -----------------------------------------------------------------------------------------------------------
89-
2a021f54-90a2-4016-b3b3-5f34d2e7d932 read https://contoso.sharepoint.com/:b:/s/demo/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A
96+
id scope roles link
97+
------------------------------------ ------------ ----- ----------------------------------------------------------------------------------------
98+
2a021f54-90a2-4016-b3b3-5f34d2e7d932 organization read https://contoso.sharepoint.com/:w:/s/demo/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A
9099
```
91100

92101
=== "CSV"
93102

94103
```csv
95-
id,roles,link
96-
2a021f54-90a2-4016-b3b3-5f34d2e7d932,read,https://contoso.sharepoint.com/:b:/s/demo/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A
104+
id,scope,roles,link
105+
2a021f54-90a2-4016-b3b3-5f34d2e7d932,organization,read,https://contoso.sharepoint.com/:w:/s/demo/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A
97106
```

src/m365/spo/commands/file/file-sharinglink-list.spec.ts

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
5555
"grantedToIdentitiesV2": [],
5656
"grantedToIdentities": [],
5757
"link": {
58-
"scope": "anonymous",
58+
"scope": "users",
5959
"type": "view",
6060
"webUrl": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBsS_o5pIcCyNIL3D_vEyG5Q",
6161
"preventsDownload": true
@@ -70,7 +70,7 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
7070
"grantedToIdentitiesV2": [],
7171
"grantedToIdentities": [],
7272
"link": {
73-
"scope": "anonymous",
73+
"scope": "organization",
7474
"type": "edit",
7575
"webUrl": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBDyAMq6f9C2eqWwFsbei6nA",
7676
"preventsDownload": false
@@ -83,17 +83,8 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
8383
{
8484
"id": "2a021f54-90a2-4016-b3b3-5f34d2e7d932",
8585
"roles": "read",
86-
"link": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A"
87-
},
88-
{
89-
"id": "a47e5387-8868-497c-bb00-115c66c60390",
90-
"roles": "read",
91-
"link": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBsS_o5pIcCyNIL3D_vEyG5Q"
92-
},
93-
{
94-
"id": "8bf1ca81-a63f-4796-9af5-d86ded8ce5a7",
95-
"roles": "write",
96-
"link": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBDyAMq6f9C2eqWwFsbei6nA"
86+
"link": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A",
87+
"scope": "anonymous"
9788
}
9889
];
9990

@@ -106,6 +97,15 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
10697
});
10798
};
10899

100+
const stubOdataScopeResponse: any = (scope: any = null, graphResponse: any = null) => {
101+
return sinon.stub(odata, 'getAllItems').callsFake(async (url: string) => {
102+
if (url === `https://graph.microsoft.com/v1.0/sites/${fileDetailsResponse.SiteId}/drives/${fileDetailsResponse.VroomDriveID}/items/${fileDetailsResponse.VroomItemID}/permissions?$filter=Link ne null and Link/Scope eq '${scope}'`) {
103+
return graphResponse.value.filter((x: any) => x.link.scope === scope);
104+
}
105+
throw 'Invalid request';
106+
});
107+
};
108+
109109
before(() => {
110110
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve());
111111
sinon.stub(telemetry, 'trackEvent').callsFake(() => { });
@@ -182,8 +182,54 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
182182
assert(loggerLogSpy.calledWith(graphResponse.value));
183183
});
184184

185+
it('retrieves sharing links from file specified by url and scope anonymous', async () => {
186+
const scope = 'anonymous';
187+
stubOdataScopeResponse(scope, graphResponse);
188+
sinon.stub(request, 'get').callsFake(async (opts) => {
189+
if (opts.url === `${webUrl}/_api/web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(fileUrl)}')?$select=SiteId,VroomItemId,VroomDriveId`) {
190+
return fileDetailsResponse;
191+
}
192+
193+
throw 'Invalid request';
194+
});
195+
196+
await command.action(logger, { options: { webUrl: webUrl, fileUrl: fileUrl, scope: scope, output: 'json', verbose: true } } as any);
197+
assert(loggerLogSpy.calledWith(graphResponse.value.filter(x => x.link.scope === scope)));
198+
});
199+
200+
it('retrieves sharing links from file specified by url and scope users', async () => {
201+
const scope = 'users';
202+
stubOdataScopeResponse(scope, graphResponse);
203+
sinon.stub(request, 'get').callsFake(async (opts) => {
204+
if (opts.url === `${webUrl}/_api/web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(fileUrl)}')?$select=SiteId,VroomItemId,VroomDriveId`) {
205+
return fileDetailsResponse;
206+
}
207+
208+
throw 'Invalid request';
209+
});
210+
211+
await command.action(logger, { options: { webUrl: webUrl, fileUrl: fileUrl, scope: scope, output: 'json', verbose: true } } as any);
212+
assert(loggerLogSpy.calledWith(graphResponse.value.filter(x => x.link.scope === scope)));
213+
});
214+
215+
it('retrieves sharing links from file specified by url and scope organization', async () => {
216+
const scope = 'organization';
217+
stubOdataScopeResponse(scope, graphResponse);
218+
sinon.stub(request, 'get').callsFake(async (opts) => {
219+
if (opts.url === `${webUrl}/_api/web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(fileUrl)}')?$select=SiteId,VroomItemId,VroomDriveId`) {
220+
return fileDetailsResponse;
221+
}
222+
223+
throw 'Invalid request';
224+
});
225+
226+
await command.action(logger, { options: { webUrl: webUrl, fileUrl: fileUrl, scope: scope, output: 'json', verbose: true } } as any);
227+
assert(loggerLogSpy.calledWith(graphResponse.value.filter(x => x.link.scope === scope)));
228+
});
229+
185230
it('retrieves sharing links from file specified by url with output text', async () => {
186-
stubOdataResponse(graphResponse);
231+
const scope = 'anonymous';
232+
stubOdataScopeResponse(scope, graphResponse);
187233
sinon.stub(request, 'get').callsFake(async (opts) => {
188234
if (opts.url === `${webUrl}/_api/web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(fileUrl)}')?$select=SiteId,VroomItemId,VroomDriveId`) {
189235
return fileDetailsResponse;
@@ -192,7 +238,7 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
192238
throw 'Invalid request';
193239
});
194240

195-
await command.action(logger, { options: { webUrl: webUrl, fileUrl: fileUrl, output: 'text', verbose: true } } as any);
241+
await command.action(logger, { options: { webUrl: webUrl, fileUrl: fileUrl, scope: scope, output: 'text', verbose: true } } as any);
196242
assert(loggerLogSpy.calledWith(graphResponseText));
197243
});
198244

@@ -219,6 +265,11 @@ describe(commands.FILE_SHARINGLINK_LIST, () => {
219265
assert.notStrictEqual(actual, true);
220266
});
221267

268+
it('fails validation if invalid scope specified', async () => {
269+
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', fileId: fileId, scope: 'invalid scope' } }, commandInfo);
270+
assert.notStrictEqual(actual, true);
271+
});
272+
222273
it('passes validation if options are valid', async () => {
223274
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', fileId: fileId } }, commandInfo);
224275
assert.strictEqual(actual, true);

src/m365/spo/commands/file/file-sharinglink-list.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ interface Options extends GlobalOptions {
1616
webUrl: string;
1717
fileId?: string;
1818
fileUrl?: string;
19+
scope?: string;
1920
}
2021

2122
class SpoFileSharingLinkListCommand extends SpoCommand {
23+
private static readonly scope: string[] = ['anonymous', 'users', 'organization'];
24+
2225
public get name(): string {
2326
return commands.FILE_SHARINGLINK_LIST;
2427
}
@@ -28,7 +31,7 @@ class SpoFileSharingLinkListCommand extends SpoCommand {
2831
}
2932

3033
public defaultProperties(): string[] | undefined {
31-
return ['id', 'roles', 'link'];
34+
return ['id', 'scope', 'roles', 'link'];
3235
}
3336

3437
constructor() {
@@ -44,7 +47,8 @@ class SpoFileSharingLinkListCommand extends SpoCommand {
4447
this.telemetry.push((args: CommandArgs) => {
4548
Object.assign(this.telemetryProperties, {
4649
fileId: typeof args.options.fileId !== 'undefined',
47-
fileUrl: typeof args.options.fileUrl !== 'undefined'
50+
fileUrl: typeof args.options.fileUrl !== 'undefined',
51+
scope: typeof args.options.scope !== 'undefined'
4852
});
4953
});
5054
}
@@ -59,6 +63,10 @@ class SpoFileSharingLinkListCommand extends SpoCommand {
5963
},
6064
{
6165
option: '-f, --fileUrl [fileUrl]'
66+
},
67+
{
68+
option: '--scope [scope]',
69+
autocomplete: SpoFileSharingLinkListCommand.scope
6270
}
6371
);
6472
}
@@ -75,6 +83,10 @@ class SpoFileSharingLinkListCommand extends SpoCommand {
7583
return `${args.options.fileId} is not a valid GUID`;
7684
}
7785

86+
if (args.options.scope && SpoFileSharingLinkListCommand.scope.indexOf(args.options.scope) === -1) {
87+
return `'${args.options.scope}' is not a valid scope. Allowed values are: ${SpoFileSharingLinkListCommand.scope.join(',')}`;
88+
}
89+
7890
return true;
7991
}
8092
);
@@ -91,7 +103,13 @@ class SpoFileSharingLinkListCommand extends SpoCommand {
91103

92104
try {
93105
const fileDetails = await this.getFileDetails(args.options.webUrl, args.options.fileId, args.options.fileUrl);
94-
const sharingLinks = await odata.getAllItems<any>(`https://graph.microsoft.com/v1.0/sites/${fileDetails.SiteId}/drives/${fileDetails.VroomDriveID}/items/${fileDetails.VroomItemID}/permissions?$filter=Link ne null`);
106+
107+
let url = `https://graph.microsoft.com/v1.0/sites/${fileDetails.SiteId}/drives/${fileDetails.VroomDriveID}/items/${fileDetails.VroomItemID}/permissions?$filter=Link ne null`;
108+
if (args.options.scope) {
109+
url += ` and Link/Scope eq '${args.options.scope}'`;
110+
}
111+
112+
const sharingLinks = await odata.getAllItems<any>(url);
95113

96114
if (!args.options.output || args.options.output === 'json' || args.options.output === 'md') {
97115
logger.log(sharingLinks);
@@ -102,7 +120,8 @@ class SpoFileSharingLinkListCommand extends SpoCommand {
102120
return {
103121
id: i.id,
104122
roles: i.roles.join(','),
105-
link: i.link.webUrl
123+
link: i.link.webUrl,
124+
scope: i.link.scope
106125
};
107126
}));
108127
}

0 commit comments

Comments
 (0)