Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhances spo web roleassignment commands with Microsoft Entra groups, Closes #6193 #6463

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
First commit
  • Loading branch information
nicodecleyre committed Nov 3, 2024
commit e1ec7eada4484cb7c0e7928cda4566d59b7357d2
18 changes: 15 additions & 3 deletions docs/docs/cmd/spo/web/web-roleassignment-add.mdx
Original file line number Diff line number Diff line change
@@ -17,13 +17,19 @@ m365 spo web roleassignment add [options]
: URL of the site.

`--principalId [principalId]`
: SharePoint ID of principal it may be either user id or group id we want to add permissions to. Specify either `principalId`, `upn`, or `groupName` but not multiple.
: SharePoint ID of principal it may be either user id or group id we want to add permissions to. Specify either `principalId`, `upn`, `groupName`, `entraGroupId` or `entraGroupName` but not multiple.

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

`--groupName [groupName]`
: The group name of the SharePoint group Specify either `principalId`, `upn`, or `groupName` but not multiple.
: The group name of the SharePoint group. Use this option exclusively for SharePoint Online groups. Specify either `principalId`, `upn`, `groupName`, `entraGroupId` or `entraGroupName` but not multiple.

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

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

`--roleDefinitionId [roleDefinitionId]`
: ID of role definition. Specify either `roleDefinitionId` or `roleDefinitionName` but not both.
@@ -60,6 +66,12 @@ add role assignment to a site for principal id _11_ and role definition name _Fu
m365 spo web roleassignment add --webUrl "https://contoso.sharepoint.com/sites/project-x" --principalId 11 --roleDefinitionName "Full Control"
```

add role assignment using a Entra Group ID and a role definition

```sh
m365 spo web roleassignment add --webUrl "https://contoso.sharepoint.com/sites/project-x" --entraGroupId '27ae47f1-48f1-46f3-980b-d3c1470e398d' --roleDefinitionId 1073741829
```

## Response

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

`--principalId [principalId]`
: SharePoint ID of principal it may be either user id or group id we want to add permissions to. Specify either `principalId`, `upn`, or `groupName` but not multiple.
: SharePoint ID of principal it may be either user id or group id we want to add permissions to. Specify either `principalId`, `upn`, `groupName`, `entraGroupId` or `entraGroupName` but not multiple.

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

`--groupName [groupName]`
: The group name of the SharePoint group Specify either `principalId`, `upn`, or `groupName` but not multiple.
: The group name of the SharePoint group. Use this option exclusively for SharePoint Online groups. Specify either `principalId`, `upn`, `groupName`, `entraGroupId` or `entraGroupName` but not multiple.

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

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

`-f, --force`
: Don't prompt for confirming removing the roleassignment.
@@ -57,6 +63,12 @@ Remove roleassignment from web based on principal Id without prompting for confi
m365 spo web roleassignment remove --webUrl "https://contoso.sharepoint.com/sites/contoso-sales" --principalId 2 --force
```

Remove roleassignment from web based on Entra Group Id

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

## Response

The command won't return a response on success.
121 changes: 120 additions & 1 deletion src/m365/spo/commands/web/web-roleassignment-add.spec.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,65 @@ import spoGroupGetCommand from '../group/group-get.js';
import spoRoleDefinitionListCommand from '../roledefinition/roledefinition-list.js';
import spoUserGetCommand from '../user/user-get.js';
import command from './web-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.WEB_ROLEASSIGNMENT_ADD, () => {
let log: any[];
@@ -48,7 +107,10 @@ describe(commands.WEB_ROLEASSIGNMENT_ADD, () => {
afterEach(() => {
sinonUtil.restore([
request.post,
cli.executeCommandWithOutput
cli.executeCommandWithOutput,
entraGroup.getGroupById,
entraGroup.getGroupByDisplayName,
spo.ensureEntraGroup
]);
});

@@ -95,6 +157,17 @@ describe(commands.WEB_ROLEASSIGNMENT_ADD, () => {
assert.strictEqual(actual, true);
});


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

it('passes validation if the entaGroupId is a valid guid', async () => {
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', entraGroupId: '27ae47f1-48f1-46f3-980b-d3c1470e398d', roleDefinitionId: 1073741827 } }, commandInfo);
assert.strictEqual(actual, true);
});

it('add role assignment on web by role definition id', async () => {
sinon.stub(request, 'post').callsFake(async (opts) => {
if ((opts.url as string).indexOf('_api/web/roleassignments/addroleassignment(principalid=\'11\',roledefid=\'1073741827\')') > -1) {
@@ -257,6 +330,52 @@ describe(commands.WEB_ROLEASSIGNMENT_ADD, () => {
});
});

it('adds role assignment on web by role definition id and Entra group 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 as string).indexOf('_api/web/roleassignments/addroleassignment(principalid=\'11\',roledefid=\'1073741827\')') > -1) {
return;
}

throw 'Invalid request';
});

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

it('adds role assignment on web by role definition id and Entra group name', 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 as string).indexOf('_api/web/roleassignments/addroleassignment(principalid=\'11\',roledefid=\'1073741827\')') > -1) {
return;
}

throw 'Invalid request';
});

await command.action(logger, {
options: {
debug: true,
webUrl: 'https://contoso.sharepoint.com',
entraGroupName: 'Marketing',
principalId: 11,
roleDefinitionId: 1073741827
}
});
});

it('correctly handles error when role definition does not exist', async () => {
sinon.stub(request, 'post').callsFake(async (opts) => {
if ((opts.url as string).indexOf('/_api/web/roleassignments/addroleassignment(principalid=\'11\',roledefid=\'1073741827\')') > -1) {
41 changes: 36 additions & 5 deletions src/m365/spo/commands/web/web-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, CommandOutput } from '../../../../cli/cli.js';
import { Logger } from '../../../../cli/Logger.js';
import Command from '../../../../Command.js';
@@ -10,6 +11,8 @@ import spoGroupGetCommand, { Options as SpoGroupGetCommandOptions } from '../gro
import spoRoleDefinitionListCommand, { Options as SpoRoleDefinitionListCommandOptions } 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;
@@ -20,6 +23,8 @@ interface Options extends GlobalOptions {
principalId?: number;
upn?: string;
groupName?: string;
entraGroupId?: string;
entraGroupName?: string;
roleDefinitionId?: number;
roleDefinitionName?: string;
}
@@ -48,6 +53,8 @@ class SpoWebRoleAssignmentAddCommand 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'
});
@@ -68,6 +75,12 @@ class SpoWebRoleAssignmentAddCommand extends SpoCommand {
{
option: '--groupName [groupName]'
},
{
option: '--entraGroupId [entraGroupId]'
},
{
option: '--entraGroupName [entraGroupName]'
},
{
option: '--roleDefinitionId [roleDefinitionId]'
},
@@ -93,14 +106,18 @@ class SpoWebRoleAssignmentAddCommand extends SpoCommand {
return `Specified roleDefinitionId ${args.options.roleDefinitionId} 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.`;
}

return true;
}
);
}

#initOptionSets(): void {
this.optionSets.push(
{ options: ['principalId', 'upn', 'groupName'] },
{ options: ['principalId', 'upn', 'groupName', 'entraGroupId', 'entraGroupName'] },
{ options: ['roleDefinitionId', 'roleDefinitionName'] }
);
}
@@ -115,15 +132,29 @@ class SpoWebRoleAssignmentAddCommand extends SpoCommand {

if (args.options.upn) {
args.options.principalId = await this.getUserPrincipalId(args.options);
await this.addRoleAssignment(logger, args.options);
}
else if (args.options.groupName) {
args.options.principalId = await this.getGroupPrincipalId(args.options);
await this.addRoleAssignment(logger, args.options);
}
else {
await this.addRoleAssignment(logger, args.options);
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);
args.options.principalId = siteUser.Id;
}

await this.addRoleAssignment(logger, args.options);

}
catch (err: any) {
this.handleRejectedODataJsonPromise(err);
Loading