Skip to content

Commit

Permalink
Adds command 'spo site sharingpermission set'. Closes pnp#6266
Browse files Browse the repository at this point in the history
  • Loading branch information
milanholemans authored and MathijsVerbeeck committed Sep 24, 2024
1 parent 77f6320 commit 6f78d79
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 0 deletions.
58 changes: 58 additions & 0 deletions docs/docs/cmd/spo/site/site-sharingpermission-set.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Global from '/docs/cmd/_global.mdx';

# spo site sharingpermission set

Controls how a site and its components can be shared

## Usage

```sh
m365 spo site sharingpermission set [options]
```

## Options

```md definition-list
`-u, --url <url>`
: URL of the site.

`--capability <capability>`
: Define how the site is shared. Possible values: `full`, `limited`, `ownersOnly`.
```

<Global />

## Remarks

When specifying `capability`, consider the following:
- `full`: Site owners and members can share files, folders, and the site. People with Edit permissions can share files and folders.
- `limited`: Site owners and members, and people with Edit permissions can share files and folders, but only site owners can share the site.
- `ownersOnly`: Only site owners can share files, folders, and the site.

## Examples

Update the sharing permissions for a site so only owners can share files and the site

```sh
m365 spo site sharingpermission set --siteUrl https://siteaddress.com/sites/sitename --capability ownersOnly
```

Update the sharing permissions for a site where so both owners and members can share files and the site

```sh
m365 spo site sharingpermission set --siteUrl https://siteaddress.com/sites/sitename --capability full
```

Update the sharing permissions for a site where so owners can share the site, but members can only share files

```sh
m365 spo site sharingpermission set --siteUrl https://siteaddress.com/sites/sitename --capability full
```

## Response

The command won't return a response on success.

## More information

- Sharing a SharePoint site: [https://support.microsoft.com/office/share-a-site-958771a8-d041-4eb8-b51c-afea2eae3658](https://support.microsoft.com/office/share-a-site-958771a8-d041-4eb8-b51c-afea2eae3658)
5 changes: 5 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3525,6 +3525,11 @@ const sidebars: SidebarsConfig = {
type: 'doc',
label: 'site recyclebinitem restore',
id: 'cmd/spo/site/site-recyclebinitem-restore'
},
{
type: 'doc',
label: 'site sharingpermission set',
id: 'cmd/spo/site/site-sharingpermission-set'
}
]
},
Expand Down
1 change: 1 addition & 0 deletions src/m365/spo/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ export default {
SITE_REMOVE: `${prefix} site remove`,
SITE_RENAME: `${prefix} site rename`,
SITE_SET: `${prefix} site set`,
SITE_SHARINGPERMISSION_SET: `${prefix} site sharingpermission set`,
SITE_CHROME_SET: `${prefix} site chrome set`,
SITEDESIGN_ADD: `${prefix} sitedesign add`,
SITEDESIGN_APPLY: `${prefix} sitedesign apply`,
Expand Down
168 changes: 168 additions & 0 deletions src/m365/spo/commands/site/site-sharingpermission-set.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import assert from 'assert';
import sinon from 'sinon';
import auth from '../../../../Auth.js';
import { CommandError } from '../../../../Command.js';
import { Logger } from '../../../../cli/Logger.js';
import request from '../../../../request.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { session } from '../../../../utils/session.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
import command from './site-sharingpermission-set.js';
import { z } from 'zod';
import { cli } from '../../../../cli/cli.js';

describe(commands.SITE_SHARINGPERMISSION_SET, () => {
const siteUrl = 'https://contoso.sharepoint.com/sites/marketing';

let log: string[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let patchStub: sinon.SinonStub;
let commandInfo: CommandInfo;
let commandOptionsSchema: z.ZodTypeAny;

before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
sinon.stub(telemetry, 'trackEvent').returns();
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');

commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
auth.connection.active = true;
});

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');

patchStub = sinon.stub(request, 'patch').callsFake(async opts => {
if (opts.url === `${siteUrl}/_api/Web`) {
return;
}
if (opts.url === `${siteUrl}/_api/Web/AssociatedMemberGroup`) {
return;
}

throw 'Invalid request :' + opts.url;
});
});

afterEach(() => {
sinonUtil.restore([
request.patch
]);
});

after(() => {
sinon.restore();
auth.connection.active = false;
});

it('has correct name', () => {
assert.strictEqual(command.name, commands.SITE_SHARINGPERMISSION_SET);
});

it('has a description', () => {
assert.notStrictEqual(command.description, null);
});

it('fails validation if siteUrl is not a valid URL', async () => {
const actual = commandOptionsSchema.safeParse({ siteUrl: 'invalid', capability: 'full' });
assert.strictEqual(actual.success, false);
});

it('passes validation when capability is not a valid value', async () => {
const actual = commandOptionsSchema.safeParse({ siteUrl: siteUrl, capability: 'invalid' });
assert.strictEqual(actual.success, false);
});

it('passes validation when the input is correct', async () => {
const actual = commandOptionsSchema.safeParse({ siteUrl: siteUrl, capability: 'limited' });
assert.strictEqual(actual.success, true);
});

it('outputs no command output', async () => {
patchStub.restore();
sinon.stub(request, 'patch').resolves();

await command.action(logger, {
options: {
siteUrl: siteUrl,
capability: 'full',
verbose: true
}
});

assert(loggerLogSpy.notCalled);
});

it('correctly sets sharing permissions to full', async () => {
await command.action(logger, {
options: {
siteUrl: siteUrl,
capability: 'full'
}
});

assert.deepStrictEqual(patchStub.firstCall.args[0].data, { MembersCanShare: true });
assert.deepStrictEqual(patchStub.secondCall.args[0].data, { AllowMembersEditMembership: true });
});

it('correctly sets sharing permissions to limited', async () => {
await command.action(logger, {
options: {
siteUrl: siteUrl,
capability: 'limited'
}
});

assert.deepStrictEqual(patchStub.firstCall.args[0].data, { MembersCanShare: true });
assert.deepStrictEqual(patchStub.secondCall.args[0].data, { AllowMembersEditMembership: false });
});

it('correctly sets sharing permissions to ownersOnly', async () => {
await command.action(logger, {
options: {
siteUrl: siteUrl,
capability: 'ownersOnly'
}
});

assert.deepStrictEqual(patchStub.firstCall.args[0].data, { MembersCanShare: false });
assert.deepStrictEqual(patchStub.secondCall.args[0].data, { AllowMembersEditMembership: false });
});

it('correctly handles error when updating sharing permissions', async () => {
patchStub.restore();
const errorMessage = 'Access is denied.';

sinon.stub(request, 'patch').rejects({
error: {
'odata.error': {
message: {
lang: 'en-US',
value: errorMessage
}
}
}
});

await assert.rejects(command.action(logger, { options: { siteUrl: siteUrl, capability: 'limited' } }),
new CommandError(errorMessage));
});
});
85 changes: 85 additions & 0 deletions src/m365/spo/commands/site/site-sharingpermission-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import SpoCommand from '../../../base/SpoCommand.js';
import { globalOptionsZod } from '../../../../Command.js';
import { z } from 'zod';
import { zod } from '../../../../utils/zod.js';
import { Logger } from '../../../../cli/Logger.js';
import commands from '../../commands.js';
import { validation } from '../../../../utils/validation.js';
import request, { CliRequestOptions } from '../../../../request.js';

const options = globalOptionsZod
.extend({
siteUrl: zod.alias('u', z.string()
.refine(url => validation.isValidSharePointUrl(url) === true, url => ({
message: `'${url}' is not a valid SharePoint Online site URL.`
}))
),
capability: z.enum(['full', 'limited', 'ownersOnly'])
})
.strict();
declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

class SpoSiteSharingPermissionSetCommand extends SpoCommand {
public get name(): string {
return commands.SITE_SHARINGPERMISSION_SET;
}

public get description(): string {
return 'Controls how a site and its components can be shared';
}

public get schema(): z.ZodTypeAny {
return options;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
try {
if (this.verbose) {
await logger.logToStderr(`Updating sharing permissions for site '${args.options.siteUrl}'...`);
}

const { capability } = args.options;

if (this.verbose) {
await logger.logToStderr(`Updating site sharing permissions...`);
}
const requestOptionsWeb: CliRequestOptions = {
url: `${args.options.siteUrl}/_api/Web`,
headers: {
accept: 'application/json;odata=nometadata'
},
responseType: 'json',
data: {
MembersCanShare: capability === 'full' || capability === 'limited'
}
};
await request.patch(requestOptionsWeb);

if (this.verbose) {
await logger.logToStderr(`Updating associated member group sharing permissions...`);
}

const requestOptionsMemberGroup: CliRequestOptions = {
url: `${args.options.siteUrl}/_api/Web/AssociatedMemberGroup`,
headers: {
accept: 'application/json;odata=nometadata'
},
responseType: 'json',
data: {
AllowMembersEditMembership: capability === 'full'
}
};

await request.patch(requestOptionsMemberGroup);
}
catch (err: any) {
this.handleRejectedODataJsonPromise(err);
}
}
}

export default new SpoSiteSharingPermissionSetCommand();

0 comments on commit 6f78d79

Please sign in to comment.