UNPKG

@pnp/cli-microsoft365

Version:

Manage Microsoft 365 and SharePoint Framework projects on any platform

170 lines • 7.01 kB
import { z } from 'zod'; import { globalOptionsZod } from '../../../../Command.js'; import request from '../../../../request.js'; import { entraUser } from '../../../../utils/entraUser.js'; import { validation } from '../../../../utils/validation.js'; import { zod } from '../../../../utils/zod.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; const GroupTypeEnum = { microsoft365: 'microsoft365', security: 'security' }; const VisibilityEnum = { Public: 'Public', Private: 'Private', HiddenMembership: 'HiddenMembership' }; export const options = z.looseObject({ ...globalOptionsZod.shape, displayName: z.string().max(256, `The maximum amount of characters for 'displayName' is 256.`).alias('n'), description: z.string().optional().alias('d'), type: zod.coercedEnum(GroupTypeEnum).alias('t'), mailNickname: z.string() .refine(val => validation.isValidMailNickname(val), { error: `Value for option 'mailNickname' must contain only characters in the ASCII character set 0-127 except the following: @ () \\ [] " ; : <> , SPACE.` }) .refine(val => val.length <= 64, { error: `The maximum amount of characters for 'mailNickname' is 64.` }) .optional().alias('m'), ownerIds: z.string() .refine(ids => validation.isValidGuidArray(ids) === true, { error: e => `The following GUIDs are invalid for the option 'ownerIds': ${validation.isValidGuidArray(e.input)}.` }).optional(), ownerUserNames: z.string() .refine(names => validation.isValidUserPrincipalNameArray(names) === true, { error: e => `The following user principal names are invalid for the option 'ownerUserNames': ${validation.isValidUserPrincipalNameArray(e.input)}.` }).optional(), memberIds: z.string() .refine(ids => validation.isValidGuidArray(ids) === true, { error: e => `The following GUIDs are invalid for the option 'memberIds': ${validation.isValidGuidArray(e.input)}.` }).optional(), memberUserNames: z.string() .refine(names => validation.isValidUserPrincipalNameArray(names) === true, { error: e => `The following user principal names are invalid for the option 'memberUserNames': ${validation.isValidUserPrincipalNameArray(e.input)}.` }).optional(), visibility: zod.coercedEnum(VisibilityEnum).optional() }); class EntraGroupAddCommand extends GraphCommand { get name() { return commands.GROUP_ADD; } get description() { return 'Creates a Microsoft Entra group'; } allowUnknownOptions() { return true; } get schema() { return options; } getRefinedSchema(schema) { return schema .refine(options => options.type !== 'microsoft365' || options.visibility !== undefined, { error: `Option 'visibility' must be specified if the option 'type' is set to microsoft365` }); } async commandAction(logger, args) { let group; try { const manifest = this.createRequestBody(args.options); const requestOptions = { url: `${this.resource}/v1.0/groups`, headers: { accept: 'application/json;odata.metadata=none' }, responseType: 'json', data: manifest }; const ownerIds = await this.getUserIds(logger, args.options.ownerIds, args.options.ownerUserNames); const memberIds = await this.getUserIds(logger, args.options.memberIds, args.options.memberUserNames); group = await request.post(requestOptions); if (ownerIds.length !== 0) { await this.addUsers(group.id, 'owners', ownerIds); } if (memberIds.length !== 0) { await this.addUsers(group.id, 'members', memberIds); } await logger.log(group); } catch (err) { this.handleRejectedODataJsonPromise(err); } } ; createRequestBody(options) { const requestBody = { displayName: options.displayName, description: options.description, mailNickName: options.mailNickname ?? this.generateMailNickname(), visibility: options.visibility ?? 'Public', groupTypes: options.type === 'microsoft365' ? ['Unified'] : [], mailEnabled: options.type === 'security' ? false : true, securityEnabled: true }; this.addUnknownOptionsToPayloadZod(requestBody, options); return requestBody; } generateMailNickname() { return `Group${Math.floor(Math.random() * 1000000)}`; } async getUserIds(logger, userIds, userNames) { if (userIds) { return userIds.split(',').map(o => o.trim()); } if (!userNames) { if (this.verbose) { await logger.logToStderr('No users to validate, skipping.'); } return []; } if (this.verbose) { await logger.logToStderr('Retrieving user information.'); } const userArr = userNames.split(',').map(o => o.trim()); if (this.verbose) { await logger.logToStderr('Retrieving ID(s) of user(s)...'); } return entraUser.getUserIdsByUpns(userArr); } async addUsers(groupId, role, userIds) { for (let i = 0; i < userIds.length; i += 400) { const userIdsBatch = userIds.slice(i, i + 400); const requestOptions = { url: `${this.resource}/v1.0/$batch`, headers: { 'content-type': 'application/json;odata.metadata=none' }, responseType: 'json', data: { requests: [] } }; // only 20 requests per one batch are allowed for (let j = 0; j < userIdsBatch.length; j += 20) { // only 20 users can be added in one request const userIdsChunk = userIdsBatch.slice(j, j + 20); requestOptions.data.requests.push({ id: j + 1, method: 'PATCH', url: `/groups/${groupId}`, headers: { 'content-type': 'application/json;odata.metadata=none' }, body: { [`${role}@odata.bind`]: userIdsChunk.map(u => `${this.resource}/v1.0/directoryObjects/${u}`) } }); } const res = await request.post(requestOptions); for (const response of res.responses) { if (response.status !== 204) { throw response.body; } } } } } export default new EntraGroupAddCommand(); //# sourceMappingURL=group-add.js.map