-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New command: m365 spo folder sharinglink add. Closes #5963
- Loading branch information
1 parent
3a80e6f
commit 1be1c43
Showing
5 changed files
with
518 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import Global from '/docs/cmd/_global.mdx'; | ||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
|
||
# spo folder sharinglink add | ||
|
||
Creates a new sharing link to a folder | ||
|
||
## Usage | ||
|
||
```sh | ||
m365 spo folder sharinglink add [options] | ||
``` | ||
|
||
## Options | ||
|
||
```md definition-list | ||
`-u, --webUrl <webUrl>` | ||
: The URL of the site where the folder is located. | ||
|
||
`--folderUrl [folderUrl]` | ||
: The server- or site-relative decoded URL of the folder. Specify either `folderUrl` or `folderId` but not both. | ||
|
||
`--folderId [folderId]` | ||
: The UniqueId (GUID) of the folder. Specify either `folderUrl` or `folderId` but not both. | ||
|
||
`--type <type>` | ||
: The type of sharing link to create. Either `view` or `edit`. | ||
|
||
`--expirationDateTime [expirationDateTime]` | ||
: The date and time to set the expiration. This should be defined as a valid ISO 8601 string. | ||
|
||
`--scope [scope]` | ||
: The scope of link to create. Either `anonymous`, `organization` or `users`. If not specified, the default of the organization will be used. | ||
|
||
`--retainInheritedPermissions [retainInheritedPermissions]` | ||
: If `true`, any existing inherited permissions are retained on the shared item when sharing this item for the first time. If `false`, all existing permissions are removed when sharing for the first time. | ||
|
||
`--recipients [recipients]` | ||
: Comma separated list of users with whom we wish to share the item with. Required when using scope `users`. | ||
``` | ||
|
||
<Global /> | ||
|
||
## Examples | ||
|
||
Creates a view-only anonymous sharing link of a folder by id. | ||
|
||
```sh | ||
m365 spo folder sharinglink add --webUrl https://contoso.sharepoint.com/sites/demo --folderId daebb04b-a773-4baa-b1d1-3625418e3234 --type view --scope anonymous | ||
``` | ||
|
||
Creates an edit organization sharing link of a folder by url with a specific expiration date. | ||
|
||
```sh | ||
m365 spo folder sharinglink add --webUrl https://contoso.sharepoint.com/sites/demo --folderUrl /sites/demo/shared%20documents/Folder --type edit --scope organization --expirationDateTime '2022-11-30T00:00:00Z' | ||
``` | ||
|
||
Creates a user sharing link of a folder by id. | ||
|
||
```sh | ||
m365 spo folder sharinglink add --webUrl https://contoso.sharepoint.com/sites/demo --folderId daebb04b-a773-4baa-b1d1-3625418e3234 --type view --scope users --recipients john@contoso.com,doe@contoso.com | ||
``` | ||
|
||
## Response | ||
|
||
<Tabs> | ||
<TabItem value="JSON"> | ||
|
||
```json | ||
{ | ||
"id": "4fe11ccb-6c83-4927-8072-95642422b8ae", | ||
"roles": [ | ||
"read" | ||
], | ||
"shareId": "u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0V2QVFpdnpLV2ZoT3ZJOHJiNm1UVEhjQjNHWkRRNlVhQ3VwNEhWeFpiR25mRkE", | ||
"hasPassword": false, | ||
"link": { | ||
"scope": "anonymous", | ||
"type": "view", | ||
"webUrl": "https://contoso.sharepoint.com/:f:/g/EvAQivzKWfhOvI8rb6mTTHcB3GZDQ6UaCup4HVxZbGnfFA", | ||
"preventsDownload": false | ||
} | ||
} | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="Text"> | ||
|
||
```text | ||
hasPassword: false | ||
id : 4fe11ccb-6c83-4927-8072-95642422b8ae | ||
link : {"scope":"anonymous","type":"view","webUrl":"https://contoso.sharepoint.com/:f:/g/EvAQivzKWfhOvI8rb6mTTHcB3GZDQ6UaCup4HVxZbGnfFA","preventsDownload":false} | ||
roles : ["read"] | ||
shareId : u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0V2QVFpdnpLV2ZoT3ZJOHJiNm1UVEhjQjNHWkRRNlVhQ3VwNEhWeFpiR25mRkE | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="CSV"> | ||
|
||
```csv | ||
id,shareId,hasPassword | ||
4fe11ccb-6c83-4927-8072-95642422b8ae,u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0V2QVFpdnpLV2ZoT3ZJOHJiNm1UVEhjQjNHWkRRNlVhQ3VwNEhWeFpiR25mRkE, | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="Markdown"> | ||
|
||
```md | ||
# spo folder sharinglink add --webUrl "https://contoso.sharepoint.com" --folderUrl "/shared documents/folder1" --type "view" --scope "anonymous" | ||
|
||
Date: 29/04/2024 | ||
|
||
## 4fe11ccb-6c83-4927-8072-95642422b8ae | ||
|
||
Property | Value | ||
---------|------- | ||
id | 4fe11ccb-6c83-4927-8072-95642422b8ae | ||
shareId | u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0V2QVFpdnpLV2ZoT3ZJOHJiNm1UVEhjQjNHWkRRNlVhQ3VwNEhWeFpiR25mRkE | ||
hasPassword | false | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
src/m365/spo/commands/folder/folder-sharinglink-add.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import assert from 'assert'; | ||
import sinon from 'sinon'; | ||
import auth from '../../../../Auth.js'; | ||
import { cli } from '../../../../cli/cli.js'; | ||
import { CommandError } from '../../../../Command.js'; | ||
import { telemetry } from '../../../../telemetry.js'; | ||
import { Logger } from '../../../../cli/Logger.js'; | ||
import request from '../../../../request.js'; | ||
import { pid } from '../../../../utils/pid.js'; | ||
import { session } from '../../../../utils/session.js'; | ||
import { sinonUtil } from '../../../../utils/sinonUtil.js'; | ||
import { CommandInfo } from '../../../../cli/CommandInfo.js'; | ||
import { spo } from '../../../../utils/spo.js'; | ||
import { drive } from '../../../../utils/drive.js'; | ||
import { Drive } from '@microsoft/microsoft-graph-types'; | ||
import commands from '../../commands.js'; | ||
import command from './folder-sharinglink-add.js'; | ||
|
||
describe(commands.FOLDER_SHARINGLINK_ADD, () => { | ||
let log: any[]; | ||
let logger: Logger; | ||
let loggerLogSpy: sinon.SinonSpy; | ||
let commandInfo: CommandInfo; | ||
|
||
const webUrl = 'https://contoso.sharepoint.com/sites/project-x'; | ||
const folderId = 'f09c4efe-b8c0-4e89-a166-03418661b89b'; | ||
const folderUrl = '/sites/project-x/shared documents/folder1'; | ||
const siteId = '0f9b8f4f-0e8e-4630-bb0a-501442db9b64'; | ||
const driveId = '013TMHP6UOOSLON57HT5GLKEU7R5UGWZVK'; | ||
const itemId = 'b!T4-bD44OMEa7ClAUQtubZID9tc40pGJKpguycvELod_Gx-lo4ZQiRJ7vylonTufG'; | ||
|
||
const driveDetails: Drive = { | ||
id: driveId, | ||
webUrl: `${webUrl}/Shared%20Documents` | ||
}; | ||
|
||
const graphResponse = { | ||
"id": "2a021f54-90a2-4016-b3b3-5f34d2e7d932", | ||
"roles": [ | ||
"read" | ||
], | ||
"hasPassword": false, | ||
"grantedToIdentitiesV2": [], | ||
"grantedToIdentities": [], | ||
"link": { | ||
"scope": "anonymous", | ||
"type": "view", | ||
"webUrl": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A", | ||
"preventsDownload": false | ||
} | ||
}; | ||
|
||
const getStubs: any = (options: any) => { | ||
sinon.stub(spo, 'getFolderServerRelativeUrl').resolves(options.folderUrl); | ||
sinon.stub(spo, 'getSiteId').resolves(options.siteId); | ||
sinon.stub(drive, 'getDriveByUrl').resolves(options.drive); | ||
sinon.stub(drive, 'getDriveItemId').resolves(options.itemId); | ||
}; | ||
|
||
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; | ||
commandInfo = cli.getCommandInfo(command); | ||
}); | ||
|
||
beforeEach(() => { | ||
log = []; | ||
logger = { | ||
log: async (msg: string) => { | ||
log.push(msg); | ||
}, | ||
logRaw: async (msg: string) => { | ||
log.push(msg); | ||
}, | ||
logToStderr: async (msg: string) => { | ||
log.push(msg); | ||
} | ||
}; | ||
loggerLogSpy = sinon.spy(logger, 'log'); | ||
}); | ||
|
||
afterEach(() => { | ||
sinonUtil.restore([ | ||
request.post, | ||
spo.getSiteId, | ||
spo.getFolderServerRelativeUrl, | ||
drive.getDriveByUrl, | ||
drive.getDriveItemId | ||
]); | ||
}); | ||
|
||
after(() => { | ||
sinon.restore(); | ||
auth.connection.active = false; | ||
}); | ||
|
||
it('has correct name', () => { | ||
assert.strictEqual(command.name, commands.FOLDER_SHARINGLINK_ADD); | ||
}); | ||
|
||
it('has a description', () => { | ||
assert.notStrictEqual(command.description, null); | ||
}); | ||
|
||
it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'foo', folderId: folderId, type: "view" } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation if the folderId option is not a valid GUID', async () => { | ||
const actual = await command.validate({ options: { webUrl: webUrl, folderId: 'invalid', type: "view" } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation if the expirationDateTime option is not a valid date', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, expirationDateTime: 'invalid date', type: 'view' } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation if invalid scope specified', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, scope: 'invalid scope', type: 'view' } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation if invalid type specified', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, type: 'invalid type' } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation if scope is users but recipients are not specified', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, type: 'view', scope: 'users' } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation if recipients option is not a valid UPN', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, type: 'view', scope: 'users', recipients: "invalid upn" } }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('passes validation if options are valid', async () => { | ||
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, type: 'view' } }, commandInfo); | ||
assert.strictEqual(actual, true); | ||
}); | ||
|
||
it('creates a sharing link to a folder specified by the id', async () => { | ||
getStubs({ folderUrl: folderUrl, siteId: siteId, drive: driveDetails, itemId: itemId }); | ||
|
||
sinon.stub(request, 'post').callsFake(async (opts) => { | ||
if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/createLink`) { | ||
return graphResponse; | ||
} | ||
|
||
throw 'Invalid request'; | ||
}); | ||
|
||
await command.action(logger, { options: { webUrl: webUrl, folderId: folderId, type: 'view', scope: 'organization', verbose: true } } as any); | ||
assert(loggerLogSpy.calledWith(graphResponse)); | ||
}); | ||
|
||
it('creates a sharing link to a folder specified by the URL', async () => { | ||
getStubs({ folderUrl: folderUrl, siteId: siteId, drive: driveDetails, itemId: itemId }); | ||
|
||
sinon.stub(request, 'post').callsFake(async (opts) => { | ||
if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/createLink`) { | ||
return graphResponse; | ||
} | ||
|
||
throw 'Invalid request'; | ||
}); | ||
|
||
await command.action(logger, { options: { webUrl: webUrl, folderUrl: folderUrl, type: 'view', scope: 'users', recipients: 'john@contoso.com', verbose: true } } as any); | ||
assert(loggerLogSpy.calledWith(graphResponse)); | ||
}); | ||
|
||
it('throws error when drive not found by url', async () => { | ||
sinon.stub(spo, 'getFolderServerRelativeUrl').resolves(folderUrl); | ||
sinon.stub(spo, 'getSiteId').resolves(siteId); | ||
sinon.stub(request, 'get').callsFake(async opts => { | ||
if (opts.url === `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=webUrl,id`) { | ||
return { | ||
value: [] | ||
}; | ||
} | ||
|
||
throw 'Invalid request'; | ||
}); | ||
|
||
await assert.rejects(command.action(logger, { options: { webUrl: webUrl, folderUrl: folderUrl, type: 'view' } } as any), | ||
new CommandError(`Drive 'https://contoso.sharepoint.com/sites/project-x/shared%20documents/folder1' not found`)); | ||
}); | ||
}); |
Oops, something went wrong.