UNPKG

mcdev

Version:

Accenture Salesforce Marketing Cloud DevTools

358 lines (338 loc) 14.2 kB
'use strict'; import MetadataType from './MetadataType.js'; import { Util } from '../util/util.js'; import File from '../util/file.js'; import cache from '../util/cache.js'; /** * @typedef {import('../../types/mcdev.d.js').BuObject} BuObject * @typedef {import('../../types/mcdev.d.js').CodeExtract} CodeExtract * @typedef {import('../../types/mcdev.d.js').CodeExtractItem} CodeExtractItem * @typedef {import('../../types/mcdev.d.js').MetadataTypeItem} MetadataTypeItem * @typedef {import('../../types/mcdev.d.js').MetadataTypeItemDiff} MetadataTypeItemDiff * @typedef {import('../../types/mcdev.d.js').MetadataTypeItemObj} MetadataTypeItemObj * @typedef {import('../../types/mcdev.d.js').MetadataTypeMap} MetadataTypeMap * @typedef {import('../../types/mcdev.d.js').MetadataTypeMapObj} MetadataTypeMapObj * @typedef {import('../../types/mcdev.d.js').SoapRequestParams} SoapRequestParams * @typedef {import('../../types/mcdev.d.js').SoapSDKFilterSimple} SoapSDKFilterSimple * @typedef {import('../../types/mcdev.d.js').TemplateMap} TemplateMap */ /** * ImportFile MetadataType * * @augments MetadataType */ class Role extends MetadataType { /** * Gets metadata from Marketing Cloud * * @param {string} retrieveDir Directory where retrieved metadata directory will be saved * @param {void | string[]} [_] Returns specified fields even if their retrieve definition is not set to true * @param {void | string[]} [___] unused parameter * @param {string} [key] customer key of single item to retrieve * @returns {Promise.<MetadataTypeMapObj>} Metadata store object */ static async retrieve(retrieveDir, _, ___, key) { if (retrieveDir && this.buObject.eid !== this.buObject.mid) { // don't run for BUs other than Parent BU // this check does not work during caching Util.logger.info(' - Skipping Role retrieval on non-parent BU'); return; } const fields = super.getFieldNamesToRetrieve(null, !retrieveDir); /** @type {SoapRequestParams} */ let requestParams = { // filter individual roles filter: { leftOperand: 'IsPrivate', operator: 'equals', rightOperand: false, }, }; if (key) { // move original filter down one level into rightOperand and add key filter into leftOperand /** @type {SoapSDKFilterSimple} */ const keyFilter = { leftOperand: 'CustomerKey', operator: 'equals', rightOperand: key, }; requestParams = { filter: { leftOperand: keyFilter, operator: 'AND', rightOperand: requestParams.filter, }, }; } const results = await this.client.soap.retrieve('Role', fields, requestParams); const parsed = this.parseResponseBody(results); if (retrieveDir) { const savedMetadata = await super.saveResults(parsed, retrieveDir, null); Util.logger.info( `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})` + Util.getKeysString(key) ); await this.runDocumentOnRetrieve(key, savedMetadata); } await this.cacheDefaultRolesAndTimezones(parsed); return { metadata: parsed, type: this.definition.type }; } /** * adds default roles to the list of retrieved roles for proper caching (but not storing) * also caches available timezones for retrieve-user * * @param {MetadataTypeMap} parsed list or previously retrieved items as reference */ static async cacheDefaultRolesAndTimezones(parsed) { // retrieve "Marketing Cloud%" roles not returned by SOAP API for cache (required by retrieve-user) // also cache available timezones for retrieve-user Util.logger.info(Util.getGrayMsg(' - Caching default roles and timezones')); const { roles, timeZones } = await this.client.rest.get( '/platform/v1/setup/quickflow/data' ); // this endpoint does not provide keys const roleNameKeyMap = { 'Marketing Cloud Administrator': 'SYS_DEF_IMHADMIN', 'Marketing Cloud Channel Manager': 'SYS_DEF_CHANNELMANAGER', 'Marketing Cloud Content Editor/Publisher': 'SYS_DEF_CONTENTEDIT', 'Marketing Cloud Security Administrator': 'SYS_DEF_SECURITYADMIN', 'Marketing Cloud Viewer': 'SYS_DEF_VIEWER', }; for (const role of roles) { if (roleNameKeyMap[role.roleName]) { parsed[roleNameKeyMap[role.roleName]] = { CustomerKey: roleNameKeyMap[role.roleName], Name: role.roleName, ObjectID: role.roleID, Desscription: role.description, CreatedDate: '2012-02-21T02:09:19.983', IsSystemDefined: true, c__notAssignable: true, }; } } // the languages object is incomplete. the actual list is much longer --> ignoring it here // convert timeZones to object const timeZonesObj = {}; for (const timeZone of timeZones) { timeZonesObj[timeZone.id] = timeZone; } // cache timeZones to share it with other type-classes cache.setMetadata('_timezone', timeZonesObj); } /** * Gets executed before deploying metadata * * @param {MetadataTypeItem} metadata a single metadata item * @returns {MetadataTypeItem} Promise of a single metadata item */ static preDeployTasks(metadata) { if (this.definition.deployBlacklist.includes(metadata.CustomerKey)) { throw new Error( `Skipped ${metadata.Name} (${metadata.CustomerKey}) because its CustomerKey is reserved for a default system role.` ); } return metadata; } /** * Create a single Role. * * @param {MetadataTypeItem} metadata single metadata entry * @returns {Promise} Promise */ static create(metadata) { return super.createSOAP(metadata); } /** * Updates a single Role. * * @param {MetadataTypeItem} metadata single metadata entry * @returns {Promise} Promise */ static update(metadata) { return super.updateSOAP(metadata); } /** * Creates markdown documentation of all roles * * @param {MetadataTypeMap} [metadata] role definitions * @returns {Promise.<void>} - */ static async document(metadata) { if (this.buObject.eid !== this.buObject.mid) { Util.logger.error( `Roles can only be retrieved & documented for the ${Util.parentBuName}` ); return; } if (!metadata) { try { metadata = ( await this.readBUMetadataForType( File.normalizePath([ this.properties.directories.retrieve, this.buObject.credential, Util.parentBuName, ]), true ) ).role; } catch (ex) { Util.logger.error(ex.message); return; } } const directory = File.normalizePath([this.properties.directories.docs, 'role']); // initialize permission object this.allPermissions = {}; // traverse all permissions recursively and write them into allPermissions object once it has reached the end for (const role in metadata) { // filter standard rules if ( this.properties.options?.documentStandardRoles === false && 'string' === typeof metadata[role]?.CustomerKey && // key could be numeric metadata[role].CustomerKey.startsWith('SYS_DEF') ) { delete metadata[role]; } else { for (const element of metadata[role].PermissionSets.PermissionSet) { this._traverseRoles(role, element); } } } // Create output markdown let output = `# Permission Overview - ${this.buObject.credential}\n\n`; output += `> **Legend** > > <hr> > > **[Role Name]** = System Default Role > > **Role Name** = Custom Role > > **+** = _Allow_: User has access to the application or functionality > > ** •** = _Not Set_: User permission for this app or functionality is not explicitely granted nor denied, but defaults to Deny > > **-** = _Deny_: User does not have access to the app or functionality > > <hr>\n\n`; // Loop through all permissions for (const permGroup in this.allPermissions) { output += '## ' + permGroup + '\n\n'; // create table header output += '| Permission |'; let separator = '| --- |'; for (const role in metadata) { output += metadata[role].IsSystemDefined === true ? ` [${metadata[role].Name}] |` : ` ${metadata[role].Name} |`; separator += ' --- |'; } output += '\n' + separator + '\n'; // Write all permissions of a major permission group // output += '| '; for (const permission in this.allPermissions[permGroup]) { output += '| ' + permission + ' |'; for (const role in this.allPermissions[permGroup][permission]) { if (this.allPermissions[permGroup][permission][role] === true) { output += ' + |'; } else if (this.allPermissions[permGroup][permission][role] === false) { output += ' - |'; } else if ( 'undefined' === typeof this.allPermissions[permGroup][permission][role] ) { output += ' • |'; } else { output += ' ' + this.allPermissions[permGroup][permission][role] + ' |'; } } output += '\n'; } output += '\n'; } try { const filename = this.buObject.credential; // write to disk await File.writeToFile(directory, filename + '.roles', 'md', output); Util.logger.info(`Created ${File.normalizePath([directory, filename])}.roles.md`); if (['html', 'both'].includes(this.properties.options.documentType)) { Util.logger.warn(' - HTML-based documentation of roles currently not supported.'); } } catch (ex) { Util.logger.error(`Roles.document():: error | `, ex.message); } } /** * iterates through permissions to output proper row-names for nested permissionss * * @static * @param {string} role name of the user role * @param {object} element data of the permission * @param {string} [permission] name of the permission * @param {string} [isAllowed] "true" / "false" from the parent * @memberof Role * @returns {void} */ static _traverseRoles(role, element, permission, isAllowed) { const _permission = permission ? permission + ' > ' + element.Name : element.Name; // Reached end: write permission into this.allPermissions if (element.Operation) { const permSplit = _permission.split(' > '); const permOperation = permSplit.pop(); let basePermission = permSplit.shift(); if (basePermission === 'Interactive Marketing Hub') { basePermission = 'Salesforce Marketing Cloud'; } const permissionName = `<nobr><b>${permSplit.join( ' > ' )}</b> > ${permOperation}</nobr>`; if (!this.allPermissions[basePermission]) { this.allPermissions[basePermission] = {}; } if (!this.allPermissions[basePermission][permissionName]) { this.allPermissions[basePermission][permissionName] = {}; } this.allPermissions[basePermission][permissionName][role] = element.IsAllowed || isAllowed; // Not at end: Traverse more } else if (element.PermissionSets) { if (Array.isArray(element.PermissionSets.PermissionSet)) { for (const nextElement of element.PermissionSets.PermissionSet) { this._traverseRoles(role, nextElement, _permission); } } else { this._traverseRoles( role, element.PermissionSets.PermissionSet, _permission, element.IsAllowed || isAllowed ); } // Not at end: Traverse more } else if (element.Permissions) { if (Array.isArray(element.Permissions.Permission)) { for (const nextElement of element.Permissions.Permission) { this._traverseRoles( role, nextElement, _permission, element.IsAllowed || isAllowed ); } } else { this._traverseRoles( role, element.Permissions.Permission, _permission, element.IsAllowed || isAllowed ); } } } } // Assign definition to static attributes import MetadataTypeDefinitions from '../MetadataTypeDefinitions.js'; Role.definition = MetadataTypeDefinitions.role; export default Role;