Skip to content

Commit

Permalink
➕ Add entraGroupId and entraGroupName parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
nicodecleyre committed Nov 20, 2024
1 parent 6c6be23 commit 8290426
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 27 deletions.
18 changes: 15 additions & 3 deletions docs/docs/cmd/spo/folder/folder-roleassignment-add.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ m365 spo folder roleassignment add [options]
: The server- or site-relative decoded URL of the folder.

`--principalId [principalId]`
: The SharePoint principal id. It may be either an user id or group id for which the role assignment will be addd. Specify either `upn`, `groupName` or `principalId` but not multiple.
: The SharePoint principal id. It may be either an user id or group id for which the role assignment will be addd. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--upn [upn]`
: The upn/email of the user. Specify either `upn`, `groupName` or `principalId` but not multiple.
: The upn/email of the user. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--groupName [groupName]`
: The Microsoft Entra or SharePoint group name. Specify either `upn`, `groupName` or `principalId` but not multiple.
: The Microsoft Entra or SharePoint group name. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--entraGroupId [entraGroupId]`
: ID of the Microsoft Entra group to add. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--entraGroupName [entraGroupName]`
: Display name of the Microsoft Entra group to add. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--roleDefinitionId [roleDefinitionId]`
: ID of the role definition. Specify either `roleDefinitionId` or `roleDefinitionName` but not both.
Expand Down Expand Up @@ -63,6 +69,12 @@ Add the role assignment to the root folder based on the upn and role definition
m365 spo folder roleassignment add --webUrl "https://contoso.sharepoint.com/sites/contoso-sales" --folderUrl "/Shared Documents" --upn "[email protected]" --roleDefinitionName "Edit"
```

Add the role assignment to the specified folder based on the Entra Group Id and role definition id.

```sh
m365 spo folder roleassignment add --webUrl "https://contoso.sharepoint.com/sites/contoso-sales" --folderUrl "/Shared Documents/FolderPermission" --entraGroupId '27ae47f1-48f1-46f3-980b-d3c1470e398d' --roleDefinitionId 1073741827
```

## Response

The command won't return a response on success.
18 changes: 15 additions & 3 deletions docs/docs/cmd/spo/folder/folder-roleassignment-remove.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ m365 spo folder roleassignment remove [options]
: The server- or site-relative decoded URL of the folder.

`--principalId [principalId]`
: The SharePoint principal id. It may be either an user id or group id for which the role assignment will be removed. Specify either `upn`, `groupName` or `principalId` but not multiple.
: The SharePoint principal id. It may be either an user id or group id for which the role assignment will be removed. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--upn [upn]`
: The upn/email of the user. Specify either `upn`, `groupName` or `principalId` but not multiple.
: The upn/email of the user. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--groupName [groupName]`
: The Microsoft Entra or SharePoint group name. Specify either `upn`, `groupName` or `principalId` but not multiple.
: The Microsoft Entra or SharePoint group name. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--entraGroupId [entraGroupId]`
: ID of the Microsoft Entra group to remove. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`--entraGroupName [entraGroupName]`
: Display name of the Microsoft Entra group to remove. Specify either `upn`, `groupName`, `principalId`, `entraGroupId` or `entraGroupName` but not multiple.

`-f, --force`
: Don't prompt for confirmation when removing the role assignment.
Expand Down Expand Up @@ -60,6 +66,12 @@ Remove the role assignment from the specified folder based on the upn.
m365 spo folder roleassignment remove --webUrl "https://contoso.sharepoint.com/sites/contoso-sales" --folderUrl "/Shared Documents/FolderPermission" --upn "[email protected]"
```

Remove the role assignment from the specified folder based on the Entra group id.

```sh
m365 spo folder roleassignment remove --webUrl "https://contoso.sharepoint.com/sites/contoso-sales" --folderUrl "/Shared Documents/FolderPermission" --entraGroupId '27ae47f1-48f1-46f3-980b-d3c1470e398d'
```

## Response

The command won't return a response on success.
142 changes: 141 additions & 1 deletion src/m365/spo/commands/folder/folder-roleassignment-add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,65 @@ import spoRoleDefinitionFolderCommand from '../roledefinition/roledefinition-lis
import spoUserGetCommand from '../user/user-get.js';
import spoFolderGetCommand from './folder-get.js';
import command from './folder-roleassignment-add.js';
import { entraGroup } from '../../../../utils/entraGroup.js';
import { spo } from '../../../../utils/spo.js';

const graphGroup = {
id: '27ae47f1-48f1-46f3-980b-d3c1470e398d',
deletedDateTime: null,
classification: null,
createdDateTime: '2024-03-22T20:18:37Z',
creationOptions: [],
description: null,
displayName: 'Marketing',
expirationDateTime: null,
groupTypes: [
'Unified'
],
isAssignableToRole: null,
mail: '[email protected]',
mailEnabled: true,
mailNickname: 'Marketing',
membershipRule: null,
membershipRuleProcessingState: null,
onPremisesDomainName: null,
onPremisesLastSyncDateTime: null,
onPremisesNetBiosName: null,
onPremisesSamAccountName: null,
onPremisesSecurityIdentifier: null,
onPremisesSyncEnabled: null,
preferredDataLocation: null,
preferredLanguage: null,
proxyAddresses: [
'SPO:SPO_de7704ba-415d-4dd0-9bbd-fa565007a87e@SPO_18c58817-3bc9-489d-ac63-f7264fb357e5',
'SMTP:[email protected]'
],
renewedDateTime: '2024-03-22T20:18:37Z',
resourceBehaviorOptions: [],
resourceProvisioningOptions: [],
securityEnabled: true,
securityIdentifier: 'S-1-12-1-665733105-1190349041-3268610968-2369326662',
theme: null,
uniqueName: null,
visibility: 'Private',
onPremisesProvisioningErrors: [],
serviceProvisioningErrors: []
};

const entraGroupResponse = {
Id: 11,
IsHiddenInUI: false,
LoginName: 'c:0o.c|federateddirectoryclaimprovider|27ae47f1-48f1-46f3-980b-d3c1470e398d',
Title: 'Marketing members',
PrincipalType: 1,
Email: '',
Expiration: '',
IsEmailAuthenticationGuestUser: false,
IsShareByEmailGuestUser: false,
IsSiteAdmin: false,
UserId: null,
UserPrincipalName: null
};

describe(commands.FOLDER_ROLEASSIGNMENT_ADD, () => {
let log: any[];
Expand Down Expand Up @@ -49,7 +108,10 @@ describe(commands.FOLDER_ROLEASSIGNMENT_ADD, () => {
afterEach(() => {
sinonUtil.restore([
request.post,
cli.executeCommandWithOutput
cli.executeCommandWithOutput,
entraGroup.getGroupById,
entraGroup.getGroupByDisplayName,
spo.ensureEntraGroup
]);
});

Expand Down Expand Up @@ -126,6 +188,16 @@ describe(commands.FOLDER_ROLEASSIGNMENT_ADD, () => {
assert.notStrictEqual(actual, true);
});

it('fails validation if the entraGroupId option is not a valid GUID', async () => {
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderUrl: '/Shared Documents/FolderPermission', entraGroupId: 'invalid', roleDefinitionId: 1073741827 } }, commandInfo);
assert.notStrictEqual(actual, true);
});

it('passes validation if the entraGroupId option is a valid GUID', async () => {
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderUrl: '/Shared Documents/FolderPermission', entraGroupId: '37455d5c-e466-4e49-8eba-808b5acec21b', roleDefinitionId: 1073741827 } }, commandInfo);
assert.strictEqual(actual, true);
});

it('add the role assignment to the specified folder based on the upn and role definition id', async () => {
sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === 'https://contoso.sharepoint.com/_api/web/GetFolderByServerRelativePath(DecodedUrl=\'%2FShared%20Documents%2FFolderPermission\')/ListItemAllFields/breakroleinheritance(true)') {
Expand Down Expand Up @@ -384,4 +456,72 @@ describe(commands.FOLDER_ROLEASSIGNMENT_ADD, () => {
}
} as any), new CommandError(error));
});

it('adds the role assignment to the specified root folder based on the Entra group id and role definition id', async () => {
sinon.stub(entraGroup, 'getGroupById').withArgs(graphGroup.id).resolves(graphGroup);
sinon.stub(spo, 'ensureEntraGroup').withArgs('https://contoso.sharepoint.com', graphGroup).resolves(entraGroupResponse);

sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === 'https://contoso.sharepoint.com/_api/web/GetList(\'%2FShared%20Documents\')/breakroleinheritance(true)') {
return;
}

if (opts.url === 'https://contoso.sharepoint.com/_api/web/GetList(\'%2FShared%20Documents\')/roleassignments/addroleassignment(principalid=\'11\',roledefid=\'1073741827\')') {
return;
}

throw 'Invalid request';
});

sinon.stub(cli, 'executeCommandWithOutput').callsFake(async (command): Promise<any> => {
if (command === spoFolderGetCommand) {
return { "Exists": true, "IsWOPIEnabled": false, "ItemCount": 0, "Name": "test1", "ProgID": null, "ServerRelativeUrl": "/Shared Documents/FolderPermission", "TimeCreated": "2018-05-02T23:21:45Z", "TimeLastModified": "2018-05-02T23:21:45Z", "UniqueId": "0ac3da45-cacf-4c31-9b38-9ef3697d5a66", "WelcomePage": "" };
}
throw new CommandError('Unknown case');
});

await command.action(logger, {
options: {
debug: true,
webUrl: 'https://contoso.sharepoint.com',
folderUrl: '/Shared Documents',
entraGroupId: '27ae47f1-48f1-46f3-980b-d3c1470e398d',
roleDefinitionId: 1073741827
}
});
});

it('adds the role assignment to the specified root folder based on the Entra group name and role definition id', async () => {
sinon.stub(entraGroup, 'getGroupByDisplayName').withArgs(graphGroup.displayName).resolves(graphGroup);
sinon.stub(spo, 'ensureEntraGroup').withArgs('https://contoso.sharepoint.com', graphGroup).resolves(entraGroupResponse);

sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === 'https://contoso.sharepoint.com/_api/web/GetList(\'%2FShared%20Documents\')/breakroleinheritance(true)') {
return;
}

if (opts.url === 'https://contoso.sharepoint.com/_api/web/GetList(\'%2FShared%20Documents\')/roleassignments/addroleassignment(principalid=\'11\',roledefid=\'1073741827\')') {
return;
}

throw 'Invalid request';
});

sinon.stub(cli, 'executeCommandWithOutput').callsFake(async (command): Promise<any> => {
if (command === spoFolderGetCommand) {
return { "Exists": true, "IsWOPIEnabled": false, "ItemCount": 0, "Name": "test1", "ProgID": null, "ServerRelativeUrl": "/Shared Documents/FolderPermission", "TimeCreated": "2018-05-02T23:21:45Z", "TimeLastModified": "2018-05-02T23:21:45Z", "UniqueId": "0ac3da45-cacf-4c31-9b38-9ef3697d5a66", "WelcomePage": "" };
}
throw new CommandError('Unknown case');
});

await command.action(logger, {
options: {
debug: true,
webUrl: 'https://contoso.sharepoint.com',
folderUrl: '/Shared Documents',
entraGroupName: 'Marketing',
roleDefinitionId: 1073741827
}
});
});
});
53 changes: 41 additions & 12 deletions src/m365/spo/commands/folder/folder-roleassignment-add.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Group } from '@microsoft/microsoft-graph-types';
import { cli } from '../../../../cli/cli.js';
import { Logger } from '../../../../cli/Logger.js';
import Command from '../../../../Command.js';
Expand All @@ -12,6 +13,8 @@ import spoGroupGetCommand, { Options as SpoGroupGetCommandOptions } from '../gro
import spoRoleDefinitionFolderCommand, { Options as SpoRoleDefinitionFolderCommandOptions } from '../roledefinition/roledefinition-list.js';
import { RoleDefinition } from '../roledefinition/RoleDefinition.js';
import spoUserGetCommand, { Options as SpoUserGetCommandOptions } from '../user/user-get.js';
import { entraGroup } from '../../../../utils/entraGroup.js';
import { spo } from '../../../../utils/spo.js';

interface CommandArgs {
options: Options;
Expand All @@ -23,6 +26,8 @@ interface Options extends GlobalOptions {
principalId?: number;
upn?: string;
groupName?: string;
entraGroupId?: string;
entraGroupName?: string;
roleDefinitionId?: number;
roleDefinitionName?: string;
}
Expand Down Expand Up @@ -51,6 +56,8 @@ class SpoFolderRoleAssignmentAddCommand extends SpoCommand {
principalId: typeof args.options.principalId !== 'undefined',
upn: typeof args.options.upn !== 'undefined',
groupName: typeof args.options.groupName !== 'undefined',
entraGroupId: typeof args.options.entraGroupId !== 'undefined',
entraGroupName: typeof args.options.entraGroupName !== 'undefined',
roleDefinitionId: typeof args.options.roleDefinitionId !== 'undefined',
roleDefinitionName: typeof args.options.roleDefinitionName !== 'undefined'
});
Expand All @@ -74,6 +81,12 @@ class SpoFolderRoleAssignmentAddCommand extends SpoCommand {
{
option: '--groupName [groupName]'
},
{
option: '--entraGroupId [entraGroupId]'
},
{
option: '--entraGroupName [entraGroupName]'
},
{
option: '--roleDefinitionId [roleDefinitionId]'
},
Expand All @@ -95,17 +108,21 @@ class SpoFolderRoleAssignmentAddCommand extends SpoCommand {
return `Specified principalId ${args.options.principalId} is not a number`;
}

if (args.options.entraGroupId && !validation.isValidGuid(args.options.entraGroupId)) {
return `'${args.options.entraGroupId}' is not a valid GUID for option entraGroupId.`;
}

if (args.options.roleDefinitionId && isNaN(args.options.roleDefinitionId)) {
return `Specified roleDefinitionId ${args.options.roleDefinitionId} is not a number`;
}

const principalOptions: any[] = [args.options.principalId, args.options.upn, args.options.groupName];
const principalOptions: any[] = [args.options.principalId, args.options.upn, args.options.groupName, args.options.entraGroupId, args.options.entraGroupName];
if (!principalOptions.some(item => item !== undefined)) {
return `Specify either principalId, upn or groupName`;
return `Specify either principalId, upn, groupName, entraGroupId or entraGroupName`;
}

if (principalOptions.filter(item => item !== undefined).length > 1) {
return `Specify either principalId, upn or groupName but not multiple`;
return `Specify either principalId, upn, groupName, entraGroupId or entraGroupName but not multiple`;
}

const roleDefinitionOptions: any[] = [args.options.roleDefinitionId, args.options.roleDefinitionName];
Expand Down Expand Up @@ -145,20 +162,32 @@ class SpoFolderRoleAssignmentAddCommand extends SpoCommand {
}

const roleDefinitionId = await this.getRoleDefinitionId(args.options);
let principalId: number | undefined = args.options.principalId;
if (args.options.upn) {
const upnPrincipalId = await this.getUserPrincipalId(args.options);
await this.breakRoleAssignment(requestUrl);
await this.addRoleAssignment(requestUrl, upnPrincipalId, roleDefinitionId);
principalId = await this.getUserPrincipalId(args.options);
}
else if (args.options.groupName) {
const groupPrincipalId = await this.getGroupPrincipalId(args.options);
await this.breakRoleAssignment(requestUrl);
await this.addRoleAssignment(requestUrl, groupPrincipalId, roleDefinitionId);
principalId = await this.getGroupPrincipalId(args.options);
}
else {
await this.breakRoleAssignment(requestUrl);
await this.addRoleAssignment(requestUrl, args.options.principalId!, roleDefinitionId);
else if (args.options.entraGroupId || args.options.entraGroupName) {
if (this.verbose) {
await logger.logToStderr('Retrieving group information...');
}

let group: Group;
if (args.options.entraGroupId) {
group = await entraGroup.getGroupById(args.options.entraGroupId);
}
else {
group = await entraGroup.getGroupByDisplayName(args.options.entraGroupName!);
}

const siteUser = await spo.ensureEntraGroup(args.options.webUrl, group);
principalId = siteUser.Id;
}

await this.breakRoleAssignment(requestUrl);
await this.addRoleAssignment(requestUrl, principalId!, roleDefinitionId);
}
catch (err: any) {
this.handleRejectedODataJsonPromise(err);
Expand Down
Loading

0 comments on commit 8290426

Please sign in to comment.