diff --git a/src/m365/spo/commands/list/list-roleassignment-add.spec.ts b/src/m365/spo/commands/list/list-roleassignment-add.spec.ts index 54f2de16376..ebf1c4f8ddf 100644 --- a/src/m365/spo/commands/list/list-roleassignment-add.spec.ts +++ b/src/m365/spo/commands/list/list-roleassignment-add.spec.ts @@ -11,15 +11,73 @@ import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; import commands from '../../commands'; -import * as SpoUserGetCommand from '../user/user-get'; -import * as SpoGroupGetCommand from '../group/group-get'; -import * as SpoRoleDefinitionListCommand from '../roledefinition/roledefinition-list'; +import { spo } from '../../../../utils/spo'; +import { RoleDefinition } from '../roledefinition/RoleDefinition'; const command: Command = require('./list-roleassignment-add'); describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { let log: any[]; let logger: Logger; let commandInfo: CommandInfo; + const userResponse = { + Id: 11, + IsHiddenInUI: false, + LoginName: 'i:0#.f|membership|john.doe@contoso.com', + Title: 'John Doe', + PrincipalType: 1, + Email: 'john.doe@contoso.com', + Expiration: '', + IsEmailAuthenticationGuestUser: false, + IsShareByEmailGuestUser: false, + IsSiteAdmin: false, + UserId: { + NameId: '10032002473c5ae3', + NameIdIssuer: 'urn:federation:microsoftonline' + }, + UserPrincipalName: 'john.doe@contoso.com' + }; + + const groupResponse = { + Id: 11, + IsHiddenInUI: false, + LoginName: "groupname", + Title: "groupname", + PrincipalType: 8, + AllowMembersEditMembership: false, + AllowRequestToJoinLeave: false, + AutoAcceptRequestToJoinLeave: false, + Description: "", + OnlyAllowMembersViewMembership: true, + OwnerTitle: "John Doe", + RequestToJoinLeaveEmailSetting: null + }; + + const roledefinitionResponse: RoleDefinition = { + BasePermissions: { + High: 176, + Low: 138612833 + }, + Description: "Can view pages and list items and download documents.", + Hidden: false, + Id: 1073741827, + Name: "Read", + Order: 128, + RoleTypeKind: 2, + BasePermissionsValue: [ + "ViewListItems", + "OpenItems", + "ViewVersions", + "ViewFormPages", + "Open", + "ViewPages", + "CreateSSCSite", + "BrowseUserInfo", + "UseClientIntegration", + "UseRemoteAPIs", + "CreateAlerts" + ], + RoleTypeKindValue: "Reader" + }; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -48,7 +106,9 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { afterEach(() => { sinonUtil.restore([ request.post, - Cli.executeCommandWithOutput + spo.getGroupByName, + spo.getUserByEmail, + spo.getRoleDefinitionByName ]); }); @@ -174,15 +234,7 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { throw 'Invalid request'; }); - sinon.stub(Cli, 'executeCommandWithOutput').callsFake(async (command): Promise => { - if (command === SpoUserGetCommand) { - return { - stdout: '{"Id": 11,"IsHiddenInUI": false,"LoginName": "i:0#.f|membership|someaccount@tenant.onmicrosoft.com","Title": "Some Account","PrincipalType": 1,"Email": "someaccount@tenant.onmicrosoft.com","Expiration": "","IsEmailAuthenticationGuestUser": false,"IsShareByEmailGuestUser": false,"IsSiteAdmin": true,"UserId": {"NameId": "1003200097d06dd6","NameIdIssuer": "urn:federation:microsoftonline"},"UserPrincipalName": "someaccount@tenant.onmicrosoft.com"}' - }; - } - - throw 'Unknown case'; - }); + sinon.stub(spo, 'getUserByEmail').resolves(userResponse); await command.action(logger, { options: { @@ -204,14 +256,8 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { throw 'Invalid request'; }); - const error = 'no user found'; - sinon.stub(Cli, 'executeCommandWithOutput').callsFake(async (command): Promise => { - if (command === SpoUserGetCommand) { - throw error; - } - - throw 'Unknown case'; - }); + const error = 'User cannot be found.'; + sinon.stub(spo, 'getUserByEmail').rejects(new Error(error)); await assert.rejects(command.action(logger, { options: { @@ -233,15 +279,7 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { throw 'Invalid request'; }); - sinon.stub(Cli, 'executeCommandWithOutput').callsFake(async (command): Promise => { - if (command === SpoGroupGetCommand) { - return { - stdout: '{"Id": 11,"IsHiddenInUI": false,"LoginName": "otherGroup","Title": "otherGroup","PrincipalType": 8,"AllowMembersEditMembership": false,"AllowRequestToJoinLeave": false,"AutoAcceptRequestToJoinLeave": false,"Description": "","OnlyAllowMembersViewMembership": true,"OwnerTitle": "Some Account","RequestToJoinLeaveEmailSetting": null}' - }; - } - - throw new CommandError('Unknown case'); - }); + sinon.stub(spo, 'getGroupByName').resolves(groupResponse); await command.action(logger, { options: { @@ -263,14 +301,8 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { throw 'Invalid request'; }); - const error = 'no group found'; - sinon.stub(Cli, 'executeCommandWithOutput').callsFake(async (command): Promise => { - if (command === SpoGroupGetCommand) { - throw error; - } - - throw 'Unknown case'; - }); + const error = 'Group cannot be found'; + sinon.stub(spo, 'getGroupByName').rejects(new Error(error)); await assert.rejects(command.action(logger, { options: { @@ -292,15 +324,7 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { throw 'Invalid request'; }); - sinon.stub(Cli, 'executeCommandWithOutput').callsFake(async (command): Promise => { - if (command === SpoRoleDefinitionListCommand) { - return { - stdout: '[{"BasePermissions": {"High": "2147483647","Low": "4294967295"},"Description": "Has full control.","Hidden": false,"Id": 1073741827,"Name": "Full Control","Order": 1,"RoleTypeKind": 5}]' - }; - } - - throw 'Unknown case'; - }); + sinon.stub(spo, 'getRoleDefinitionByName').resolves(roledefinitionResponse); await command.action(logger, { options: { @@ -322,14 +346,8 @@ describe(commands.LIST_ROLEASSIGNMENT_ADD, () => { throw 'Invalid request'; }); - const error = 'no role definition found'; - sinon.stub(Cli, 'executeCommandWithOutput').callsFake(async (command): Promise => { - if (command === SpoRoleDefinitionListCommand) { - throw error; - } - - throw 'Unknown case'; - }); + const error = 'No roledefinition is found for Read'; + sinon.stub(spo, 'getRoleDefinitionByName').rejects(new Error(error)); await assert.rejects(command.action(logger, { options: { diff --git a/src/m365/spo/commands/list/list-roleassignment-add.ts b/src/m365/spo/commands/list/list-roleassignment-add.ts index 34d93e8137d..99a97ed7c7b 100644 --- a/src/m365/spo/commands/list/list-roleassignment-add.ts +++ b/src/m365/spo/commands/list/list-roleassignment-add.ts @@ -1,6 +1,4 @@ -import { Cli } from '../../../../cli/Cli'; import { Logger } from '../../../../cli/Logger'; -import Command from '../../../../Command'; import GlobalOptions from '../../../../GlobalOptions'; import request, { CliRequestOptions } from '../../../../request'; import { formatting } from '../../../../utils/formatting'; @@ -8,13 +6,7 @@ import { urlUtil } from '../../../../utils/urlUtil'; import { validation } from '../../../../utils/validation'; import SpoCommand from '../../../base/SpoCommand'; import commands from '../../commands'; -import * as SpoUserGetCommand from '../user/user-get'; -import { Options as SpoUserGetCommandOptions } from '../user/user-get'; -import * as SpoGroupGetCommand from '../group/group-get'; -import { Options as SpoGroupGetCommandOptions } from '../group/group-get'; -import * as SpoRoleDefinitionListCommand from '../roledefinition/roledefinition-list'; -import { Options as SpoRoleDefinitionListCommandOptions } from '../roledefinition/roledefinition-list'; -import { RoleDefinition } from '../roledefinition/RoleDefinition'; +import { spo } from '../../../../utils/spo'; interface CommandArgs { options: Options; @@ -148,13 +140,15 @@ class SpoListRoleAssignmentAddCommand extends SpoCommand { requestUrl += `GetList('${formatting.encodeQueryParameter(listServerRelativeUrl)}')/`; } - args.options.roleDefinitionId = await this.getRoleDefinitionId(args.options); + args.options.roleDefinitionId = await this.getRoleDefinitionId(args.options, logger); if (args.options.upn) { - args.options.principalId = await this.getUserPrincipalId(args.options); + const user = await spo.getUserByEmail(args.options.webUrl, args.options.upn, logger, this.verbose); + args.options.principalId = user.Id; await this.addRoleAssignment(requestUrl, logger, args.options); } else if (args.options.groupName) { - args.options.principalId = await this.getGroupPrincipalId(args.options); + const group = await spo.getGroupByName(args.options.webUrl, args.options.groupName, logger, this.verbose); + args.options.principalId = group.Id; await this.addRoleAssignment(requestUrl, logger, args.options); } else { @@ -180,51 +174,13 @@ class SpoListRoleAssignmentAddCommand extends SpoCommand { return request.post(requestOptions); } - private async getRoleDefinitionId(options: Options): Promise { + private async getRoleDefinitionId(options: Options, logger: Logger): Promise { if (!options.roleDefinitionName) { return options.roleDefinitionId as number; } - const roleDefinitionListCommandOptions: SpoRoleDefinitionListCommandOptions = { - webUrl: options.webUrl, - output: 'json', - debug: this.debug, - verbose: this.verbose - }; - - const output = await Cli.executeCommandWithOutput(SpoRoleDefinitionListCommand as Command, { options: { ...roleDefinitionListCommandOptions, _: [] } }); - const getRoleDefinitionListOutput = JSON.parse(output.stdout); - const roleDefinitionId: number = getRoleDefinitionListOutput.find((role: RoleDefinition) => role.Name === options.roleDefinitionName).Id; - return roleDefinitionId; - } - - private async getGroupPrincipalId(options: Options): Promise { - const groupGetCommandOptions: SpoGroupGetCommandOptions = { - webUrl: options.webUrl, - name: options.groupName, - output: 'json', - debug: this.debug, - verbose: this.verbose - }; - - const output = await Cli.executeCommandWithOutput(SpoGroupGetCommand as Command, { options: { ...groupGetCommandOptions, _: [] } }); - const getGroupOutput = JSON.parse(output.stdout); - return getGroupOutput.Id; - } - - private async getUserPrincipalId(options: Options): Promise { - const userGetCommandOptions: SpoUserGetCommandOptions = { - webUrl: options.webUrl, - email: options.upn, - id: undefined, - output: 'json', - debug: this.debug, - verbose: this.verbose - }; - - const output = await Cli.executeCommandWithOutput(SpoUserGetCommand as Command, { options: { ...userGetCommandOptions, _: [] } }); - const getUserOutput = JSON.parse(output.stdout); - return getUserOutput.Id; + const roleDefinition = await spo.getRoleDefinitionByName(options.webUrl, options.roleDefinitionName, logger, this.verbose); + return roleDefinition.Id; } } diff --git a/src/utils/spo.spec.ts b/src/utils/spo.spec.ts index a91eaefee6c..0929d8b5aed 100644 --- a/src/utils/spo.spec.ts +++ b/src/utils/spo.spec.ts @@ -5,6 +5,8 @@ import { Logger } from '../cli/Logger'; import request from '../request'; import { sinonUtil } from '../utils/sinonUtil'; import { FormDigestInfo, spo } from '../utils/spo'; +import { formatting } from './formatting'; +import { RoleDefinition } from '../m365/spo/commands/roledefinition/RoleDefinition'; const stubPostResponses: any = ( folderAddResp: any = null @@ -925,6 +927,37 @@ describe('utils/spo', () => { assert.deepEqual(customAction, '6cc1797e-5463-45ec-bb1a-b93ec198bab6'); }); + it(`retrieves spo user by email sucessfully`, async () => { + const userResponse = { + Id: 11, + IsHiddenInUI: false, + LoginName: 'i:0#.f|membership|john.doe@contoso.com', + Title: 'John Doe', + PrincipalType: 1, + Email: 'john.doe@contoso.com', + Expiration: '', + IsEmailAuthenticationGuestUser: false, + IsShareByEmailGuestUser: false, + IsSiteAdmin: false, + UserId: { + NameId: '10032002473c5ae3', + NameIdIssuer: 'urn:federation:microsoftonline' + }, + UserPrincipalName: 'john.doe@contoso.com' + }; + + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/siteusers/GetByEmail('${formatting.encodeQueryParameter('john.doe@contoso.com')}')`) { + return userResponse; + } + + throw 'Invalid request'; + }); + + const user = await spo.getUserByEmail('https://contoso.sharepoint.com/sites/sales', 'john.doe@contoso.com', logger, true); + assert.deepEqual(user, userResponse); + }); + it(`throws error retrieving a custom action by id with a wrong scope value`, async () => { try { await spo.getCustomActionById('https://contoso.sharepoint.com/sites/sales', 'd1e5e0d6-109d-40c4-a53e-924073fe9bbd', 'Invalid'); @@ -994,4 +1027,84 @@ describe('utils/spo', () => { await spo.saveMenuState(webUrl, topNavigation); assert.deepStrictEqual(postStub.lastCall.args[0].data, { menuState: topNavigation }); }); + + it(`retrieves spo group by name sucessfully`, async () => { + const groupResponse = { + Id: 11, + IsHiddenInUI: false, + LoginName: "groupname", + Title: "groupname", + PrincipalType: 8, + AllowMembersEditMembership: false, + AllowRequestToJoinLeave: false, + AutoAcceptRequestToJoinLeave: false, + Description: "", + OnlyAllowMembersViewMembership: true, + OwnerTitle: "John Doe", + RequestToJoinLeaveEmailSetting: null + }; + + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/sitegroups/GetByName('${formatting.encodeQueryParameter('groupname')}')`) { + return groupResponse; + } + + throw 'Invalid request'; + }); + + const group = await spo.getGroupByName('https://contoso.sharepoint.com/sites/sales', 'groupname', logger, true); + assert.deepEqual(group, groupResponse); + }); + + it(`retrieves roledefinition by name sucessfully`, async () => { + const roledefinitionResponse: RoleDefinition = { + BasePermissions: { + High: 176, + Low: 138612833 + }, + Description: "Can view pages and list items and download documents.", + Hidden: false, + Id: 1073741827, + Name: "Read", + Order: 128, + RoleTypeKind: 2, + BasePermissionsValue: [ + "ViewListItems", + "OpenItems", + "ViewVersions", + "ViewFormPages", + "Open", + "ViewPages", + "CreateSSCSite", + "BrowseUserInfo", + "UseClientIntegration", + "UseRemoteAPIs", + "CreateAlerts" + ], + RoleTypeKindValue: "Reader" + }; + + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/roledefinitions`) { + return { value: [roledefinitionResponse] }; + } + + throw 'Invalid request'; + }); + + const roledefintion = await spo.getRoleDefinitionByName('https://contoso.sharepoint.com/sites/sales', 'Read', logger, true); + assert.deepEqual(roledefintion, roledefinitionResponse); + }); + + it(`handles error when no roledefinition by name is found`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/roledefinitions`) { + return { value: [] }; + } + + throw 'Invalid request'; + }); + + await assert.rejects(spo.getRoleDefinitionByName('https://contoso.sharepoint.com/sites/sales', 'Read', logger, true), 'An error occured'); + }); }); \ No newline at end of file diff --git a/src/utils/spo.ts b/src/utils/spo.ts index 366b102a70f..d743432ff45 100644 --- a/src/utils/spo.ts +++ b/src/utils/spo.ts @@ -10,6 +10,8 @@ import { formatting } from './formatting'; import { CustomAction } from '../m365/spo/commands/customaction/customaction'; import { odata } from './odata'; import { MenuState } from '../m365/spo/commands/navigation/NavigationNode'; +import { RoleDefinition } from '../m365/spo/commands/roledefinition/RoleDefinition'; +import { RoleType } from '../m365/spo/commands/roledefinition/RoleType'; export interface ContextInfo { FormDigestTimeoutSeconds: number; @@ -697,6 +699,32 @@ export const spo = { return res.AadObjectId.NameId; }, + /** + * Retrieves the spo user by email. + * @param webUrl Web url + * @param email The email of the user + * @param logger the Logger object + * @param debug set if debug logging should be logged + */ + async getUserByEmail(webUrl: string, email: string, logger: Logger, debug?: boolean): Promise { + if (debug) { + logger.logToStderr(`Retrieving the spo user by email ${email}`); + } + const requestUrl = `${webUrl}/_api/web/siteusers/GetByEmail('${formatting.encodeQueryParameter(email)}')`; + + const requestOptions: any = { + url: requestUrl, + headers: { + 'accept': 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + const userInstance: any = await request.get(requestOptions); + + return userInstance; + }, + /** * Retrieves the menu state for the quick launch. * @param webUrl Web url @@ -753,5 +781,59 @@ export const spo = { }; return request.post(requestOptions); + }, + + /** +* Retrieves the spo group by name. +* @param webUrl Web url +* @param name The name of the group +* @param logger the Logger object +* @param debug set if debug logging should be logged +*/ + async getGroupByName(webUrl: string, name: string, logger: Logger, debug?: boolean): Promise { + if (debug) { + logger.logToStderr(`Retrieving the group by name ${name}`); + } + const requestUrl = `${webUrl}/_api/web/sitegroups/GetByName('${formatting.encodeQueryParameter(name)}')`; + + const requestOptions: any = { + url: requestUrl, + headers: { + 'accept': 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + const groupInstance: any = await request.get(requestOptions); + + return groupInstance; + }, + + /** +* Retrieves the role definition by name. +* @param webUrl Web url +* @param name the name of the role definition +* @param logger the Logger object +* @param debug set if debug logging should be logged +*/ + async getRoleDefinitionByName(webUrl: string, name: string, logger: Logger, debug?: boolean): Promise { + if (debug) { + logger.logToStderr(`Retrieving the role definitions for ${name}`); + } + + const roledefinitions = await odata.getAllItems(`${webUrl}/_api/web/roledefinitions`); + const roledefinition = roledefinitions.find((role: RoleDefinition) => role.Name === name); + + if (!roledefinition) { + throw `No roledefinition is found for ${name}`; + } + + const permissions: BasePermissions = new BasePermissions(); + permissions.high = roledefinition.BasePermissions.High as number; + permissions.low = roledefinition.BasePermissions.Low as number; + roledefinition.BasePermissionsValue = permissions.parse(); + roledefinition.RoleTypeKindValue = RoleType[roledefinition.RoleTypeKind]; + + return roledefinition; } };