Skip to content

Commit 42524bc

Browse files
nanddeepnmilanholemans
authored andcommitted
Adds 'spo file sharinglink get' command. Closes pnp#4024
1 parent d136e29 commit 42524bc

File tree

5 files changed

+426
-0
lines changed

5 files changed

+426
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# spo file sharinglink get
2+
3+
Gets details about a specific sharing link of a file
4+
5+
## Usage
6+
7+
```sh
8+
m365 spo file sharinglink get [options]
9+
```
10+
11+
## Options
12+
13+
`-u, --webUrl <webUrl>`
14+
: The URL of the site where the file is located.
15+
16+
`--fileUrl [fileUrl]`
17+
: The server-relative (decoded) URL of the file. Specify either `fileUrl` or `fileId` but not both.
18+
19+
`--fileId [fileId]`
20+
: The UniqueId (GUID) of the file. Specify either `fileUrl` or `fileId` but not both.
21+
22+
`-i, --id <id>`
23+
: The ID of the sharing link.
24+
25+
--8<-- "docs/cmd/_global.md"
26+
27+
## Examples
28+
29+
Gets a specific sharing link of a file by id.
30+
31+
```sh
32+
m365 spo file sharinglink get --webUrl 'https://contoso.sharepoint.com/sites/demo' --fileId daebb04b-a773-4baa-b1d1-3625418e3234 --id 1ba739c5-e693-4c16-9dfa-042e4ec62972
33+
```
34+
35+
Gets a specific sharing link of a file by a specified site-relative URL.
36+
37+
```sh
38+
m365 spo file sharinglink get --webUrl 'https://contoso.sharepoint.com/sites/demo' --fileUrl 'Shared Documents/document.docx' --id 1ba739c5-e693-4c16-9dfa-042e4ec62972
39+
```
40+
41+
Gets a specific sharing link of a file by a specified server-relative URL.
42+
43+
```sh
44+
m365 spo file sharinglink get --webUrl 'https://contoso.sharepoint.com/sites/demo' --fileUrl '/sites/demo/Shared Documents/document.docx' --id 1ba739c5-e693-4c16-9dfa-042e4ec62972
45+
```
46+
47+
## Response
48+
49+
=== "JSON"
50+
51+
```json
52+
{
53+
"id": "1ba739c5-e693-4c16-9dfa-042e4ec62972",
54+
"roles": [
55+
"write"
56+
],
57+
"hasPassword": false,
58+
"grantedToIdentitiesV2": [
59+
{
60+
"user": {
61+
"displayName": "John Doe",
62+
"email": "[email protected]",
63+
"id": "04355ecd-2124-4097-bc2b-c2295a71d7a3"
64+
},
65+
"siteUser": {
66+
"displayName": "John Doe",
67+
"email": "[email protected]",
68+
"id": "11",
69+
"loginName": "i:0#.f|membership|[email protected]"
70+
}
71+
}
72+
],
73+
"grantedToIdentities": [
74+
{
75+
"user": {
76+
"displayName": "John Doe",
77+
"email": "[email protected]",
78+
"id": "04355ecd-2124-4097-bc2b-c2295a71d7a3"
79+
}
80+
}
81+
],
82+
"link": {
83+
"scope": "organization",
84+
"type": "edit",
85+
"webUrl": "https://contoso.sharepoint.com/:w:/s/demo/EecoJa3lri9Hu9NWp-W0aBQB8ZqmGqA5tdIiaab4o-6BZw",
86+
"preventsDownload": false
87+
}
88+
}
89+
```
90+
91+
=== "Text"
92+
93+
```text
94+
grantedToIdentities : [{"user":{"displayName":"John Doe","email":"[email protected]","id":"04355ecd-2124-4097-bc2b-c2295a71d7a3"}}]
95+
grantedToIdentitiesV2: [{"user":{"displayName":"John Doe","email":"[email protected]","id":"04355ecd-2124-4097-bc2b-c2295a71d7a3"},"siteUser":{"displayName":"John Doe","email":"[email protected]","id":"11","loginName":"i:0#.f|membership|[email protected]"}}]
96+
hasPassword : false
97+
id : 1ba739c5-e693-4c16-9dfa-042e4ec62972
98+
link : {"scope":"organization","type":"edit","webUrl":"https://contoso.sharepoint.com/:w:/s/demo/EecoJa3lri9Hu9NWp-W0aBQB8ZqmGqA5tdIiaab4o-6BZw","preventsDownload":false}
99+
roles : ["write"]
100+
```
101+
102+
=== "CSV"
103+
104+
```csv
105+
id,roles,hasPassword,grantedToIdentitiesV2,grantedToIdentities,link
106+
1ba739c5-e693-4c16-9dfa-042e4ec62972,"[""write""]",,"[{""user"":{""displayName"":""John Doe"",""email"":""[email protected]"",""id"":""04355ecd-2124-4097-bc2b-c2295a71d7a3""},""siteUser"":{""displayName"":""John Doe"",""email"":""[email protected]"",""id"":""11"",""loginName"":""i:0#.f|membership|[email protected]""}}]","[{""user"":{""displayName"":""John Doe"",""email"":""[email protected]"",""id"":""04355ecd-2124-4097-bc2b-c2295a71d7a3""}}]","{""scope"":""organization"",""type"":""edit"",""webUrl"":""https://contoso.sharepoint.com/:w:/s/demo/EecoJa3lri9Hu9NWp-W0aBQB8ZqmGqA5tdIiaab4o-6BZw"",""preventsDownload"":false}"
107+
```

docs/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ nav:
401401
- file roleinheritance break: cmd/spo/file/file-roleinheritance-break.md
402402
- file roleinheritance reset: cmd/spo/file/file-roleinheritance-reset.md
403403
- file sharinginfo get: cmd/spo/file/file-sharinginfo-get.md
404+
- file sharinglink get: cmd/spo/file/file-sharinglink-get.md
404405
- file sharinglink list: cmd/spo/file/file-sharinglink-list.md
405406
- file version clear: cmd/spo/file/file-version-clear.md
406407
- file version get: cmd/spo/file/file-version-get.md

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default {
6161
FILE_ROLEINHERITANCE_BREAK: `${prefix} file roleinheritance break`,
6262
FILE_ROLEINHERITANCE_RESET: `${prefix} file roleinheritance reset`,
6363
FILE_SHARINGINFO_GET: `${prefix} file sharinginfo get`,
64+
FILE_SHARINGLINK_GET: `${prefix} file sharinglink get`,
6465
FILE_SHARINGLINK_LIST: `${prefix} file sharinglink list`,
6566
FILE_VERSION_CLEAR: `${prefix} file version clear`,
6667
FILE_VERSION_GET: `${prefix} file version get`,
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import * as assert from 'assert';
2+
import * as sinon from 'sinon';
3+
import { telemetry } from '../../../../telemetry';
4+
import auth from '../../../../Auth';
5+
import { Cli } from '../../../../cli/Cli';
6+
import { CommandInfo } from '../../../../cli/CommandInfo';
7+
import { Logger } from '../../../../cli/Logger';
8+
import { formatting } from '../../../../utils/formatting';
9+
import { GraphFileDetails } from './GraphFileDetails';
10+
import { urlUtil } from '../../../../utils/urlUtil';
11+
import Command, { CommandError } from '../../../../Command';
12+
import request from '../../../../request';
13+
import { pid } from '../../../../utils/pid';
14+
import { sinonUtil } from '../../../../utils/sinonUtil';
15+
import commands from '../../commands';
16+
const command: Command = require('./file-sharinglink-get');
17+
18+
describe(commands.FILE_SHARINGLINK_GET, () => {
19+
const webUrl = 'https://contoso.sharepoint.com/sites/demo';
20+
const fileUrl = '/sites/demo/Shared Documents/document.docx';
21+
const fileId = 'daebb04b-a773-4baa-b1d1-3625418e3234';
22+
const id = 'U1BEZW1vIFZpc2l0b3Jz';
23+
24+
let log: any[];
25+
let logger: Logger;
26+
let loggerLogSpy: sinon.SinonSpy;
27+
let commandInfo: CommandInfo;
28+
29+
const fileInformationResponse: GraphFileDetails = {
30+
SiteId: '9798e615-a586-455e-8486-84913f492c49',
31+
VroomDriveID: 'b!FeaYl4alXkWEhoSRP0ksSSOaj9osSfFPqj5bQNdluvlwfL79GNVISZZCf6nfB3vY',
32+
VroomItemID: '01A5WCPNXHFAS23ZNOF5D3XU2WU7S3I2AU'
33+
};
34+
35+
const fileSharingLinkResponse = {
36+
"id": id,
37+
"roles": [
38+
"read"
39+
],
40+
"grantedToV2": {
41+
"siteGroup": {
42+
"displayName": "Demo Visitors",
43+
"id": "5",
44+
"loginName": "Demo Visitors"
45+
}
46+
},
47+
"grantedTo": {
48+
"user": {
49+
"displayName": "Demo Visitors"
50+
}
51+
},
52+
"inheritedFrom": {}
53+
};
54+
55+
before(() => {
56+
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve());
57+
sinon.stub(telemetry, 'trackEvent').callsFake(() => { });
58+
sinon.stub(pid, 'getProcessName').callsFake(() => '');
59+
auth.service.connected = true;
60+
commandInfo = Cli.getCommandInfo(command);
61+
});
62+
63+
beforeEach(() => {
64+
log = [];
65+
logger = {
66+
log: (msg: string) => {
67+
log.push(msg);
68+
},
69+
logRaw: (msg: string) => {
70+
log.push(msg);
71+
},
72+
logToStderr: (msg: string) => {
73+
log.push(msg);
74+
}
75+
};
76+
loggerLogSpy = sinon.spy(logger, 'log');
77+
});
78+
79+
afterEach(() => {
80+
sinonUtil.restore([
81+
request.get
82+
]);
83+
});
84+
85+
after(() => {
86+
sinonUtil.restore([
87+
auth.restoreAuth,
88+
telemetry.trackEvent,
89+
pid.getProcessName
90+
]);
91+
auth.service.connected = false;
92+
});
93+
94+
it('has correct name', () => {
95+
assert.strictEqual(command.name, commands.FILE_SHARINGLINK_GET);
96+
});
97+
98+
it('has a description', () => {
99+
assert.notStrictEqual(command.description, null);
100+
});
101+
102+
it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => {
103+
const actual = await command.validate({ options: { webUrl: 'foo', fileId: fileId, id: id } }, commandInfo);
104+
assert.notStrictEqual(actual, true);
105+
});
106+
107+
it('passes validation if the webUrl option is a valid SharePoint site URL', async () => {
108+
const actual = await command.validate({ options: { webUrl: webUrl, fileId: fileId, id: id } }, commandInfo);
109+
assert.strictEqual(actual, true);
110+
});
111+
112+
it('fails validation if the fileId option is not a valid GUID', async () => {
113+
const actual = await command.validate({ options: { webUrl: webUrl, fileId: '12345', id: id } }, commandInfo);
114+
assert.notStrictEqual(actual, true);
115+
});
116+
117+
it('gets a specific sharing link of a file by id', async () => {
118+
sinon.stub(request, 'get').callsFake(async (opts) => {
119+
if (opts.url === `${webUrl}/_api/web/GetFileById('${fileId}')?$select=SiteId,VroomItemId,VroomDriveId`) {
120+
return fileInformationResponse;
121+
}
122+
123+
if (opts.url === `https://graph.microsoft.com/v1.0/sites/${fileInformationResponse.SiteId}/drives/${fileInformationResponse.VroomDriveID}/items/${fileInformationResponse.VroomItemID}/permissions/${id}`) {
124+
return fileSharingLinkResponse;
125+
}
126+
127+
throw 'Invalid request';
128+
});
129+
130+
await command.action(logger, {
131+
options: {
132+
verbose: true,
133+
webUrl: webUrl,
134+
fileId: fileId,
135+
id: id
136+
}
137+
});
138+
139+
assert(loggerLogSpy.calledWith(fileSharingLinkResponse));
140+
});
141+
142+
it('gets a specific sharing link of a file by URL', async () => {
143+
const fileServerRelativeUrl: string = urlUtil.getServerRelativePath(webUrl, fileUrl);
144+
sinon.stub(request, 'get').callsFake(async (opts) => {
145+
if (opts.url === `${webUrl}/_api/web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(fileServerRelativeUrl)}')?$select=SiteId,VroomItemId,VroomDriveId`) {
146+
return fileInformationResponse;
147+
}
148+
149+
if (opts.url === `https://graph.microsoft.com/v1.0/sites/${fileInformationResponse.SiteId}/drives/${fileInformationResponse.VroomDriveID}/items/${fileInformationResponse.VroomItemID}/permissions/${id}`) {
150+
return fileSharingLinkResponse;
151+
}
152+
153+
throw 'Invalid request';
154+
});
155+
156+
await command.action(logger, {
157+
options: {
158+
verbose: true,
159+
webUrl: webUrl,
160+
fileUrl: fileUrl,
161+
id: id
162+
}
163+
});
164+
165+
assert(loggerLogSpy.calledWith(fileSharingLinkResponse));
166+
});
167+
168+
it('throws error when file not found by id', async () => {
169+
sinon.stub(request, 'get').callsFake(async (opts) => {
170+
if (opts.url === `${webUrl}/_api/web/GetFileById('${fileId}')?$select=SiteId,VroomItemId,VroomDriveId`) {
171+
throw { error: { 'odata.error': { message: { value: 'File Not Found.' } } } };
172+
}
173+
174+
throw 'Invalid request';
175+
});
176+
177+
await assert.rejects(command.action(logger, { options: { webUrl: webUrl, fileId: fileId, verbose: true } } as any),
178+
new CommandError(`File Not Found.`));
179+
});
180+
});

0 commit comments

Comments
 (0)