UNPKG

28.2 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2018, Kinvey, Inc. All rights reserved.
3 *
4 * This software is licensed to you under the Kinvey terms of service located at
5 * http://www.kinvey.com/terms-of-use. By downloading, accessing and/or using this
6 * software, you hereby accept such terms of service (and any agreement referenced
7 * therein) and agree that you have read, understand and agree to be bound by such
8 * terms of service and are of legal age to agree to such terms with Kinvey.
9 *
10 * This software contains valuable confidential and proprietary information of
11 * KINVEY, INC and is subject to applicable licensing agreements.
12 * Unauthorized reproduction, transmission or distribution of this file and its
13 * contents is a violation of applicable laws.
14 */
15
16const async = require('async');
17
18const { AllowedPermissionsOperations, AppOptionsName, BackendCollectionPermission, CollectionHook, DomainTypes,
19 EntityType, EnvOptionsName, LogLevel, OperationType } = require('./Constants');
20const FileProcessorHelper = require('./FileProcessorHelper');
21const { Endpoints, getCustomNotFoundError, getObjectByOmitting, isEmpty, isNullOrUndefined } = require('./Utils');
22
23class EnvFileProcessor {
24 constructor(options) {
25 this.cliManager = options.cliManager;
26 this.applicationsService = options.applicationsService;
27 this.environmentsService = options.environmentsService;
28 this.collectionsService = options.collectionsService;
29 this.blService = options.businessLogicService;
30 this.rolesService = options.rolesService;
31 this.groupsService = options.groupsService;
32 this.pushService = options.pushService;
33 this.servicesService = options.servicesService;
34 }
35
36 process(options, done) {
37 const operationType = options.operation;
38 const source = options.parsedData;
39 if (operationType === OperationType.CREATE) {
40 this._createEnv(source, options, done);
41 } else if (operationType === OperationType.UPDATE) {
42 this._updateEnv(source, options, done);
43 } else {
44 return setImmediate(() => { done(new Error(`Operation type not supported: ${operationType}`)); });
45 }
46 }
47
48 _createEnv(source, options, done) {
49 let envId;
50 let env;
51
52 async.series([
53 (next) => {
54 let envData = { name: options.name };
55 if (source.settings) {
56 envData = Object.assign(envData, source.settings);
57 }
58
59 this.cliManager.log(LogLevel.INFO, `Creating environment: ${envData.name}`);
60
61 this.environmentsService.create(envData, options[AppOptionsName.APP], (err, data) => {
62 if (err) {
63 return next(err);
64 }
65
66 envId = data.id;
67 next();
68 });
69 },
70 (next) => {
71 this.environmentsService.getById(envId, (err, data) => {
72 if (err) {
73 return next(err);
74 }
75
76 env = data;
77 next();
78 });
79 },
80 (next) => {
81 this._createCommonCode(envId, source.commonCode, next);
82 },
83 (next) => {
84 this._createRoles(env, source.roles, next);
85 },
86 (next) => {
87 this._createGroups(env, source.groups, next);
88 },
89 (next) => {
90 this._modifyCollections(source.collections, env, next);
91 },
92 (next) => {
93 // hooks must be created when collections creation has completed
94 this._createOrUpdateCollectionHooks(env, source.collectionHooks, next);
95 },
96 (next) => {
97 this._createEndpoints(env, source.customEndpoints, next);
98 },
99 (next) => {
100 this._modifyPushSettings(source.push, envId, next);
101 }
102 ], (err) => {
103 if (err) {
104 return done(err);
105 }
106
107 done(null, { id: envId });
108 });
109 }
110
111 _createRoles(env, sourceRoles, done) {
112 if (isNullOrUndefined(sourceRoles) || isEmpty(sourceRoles)) {
113 return setImmediate(done);
114 }
115
116 const roles = Object.keys(sourceRoles);
117 async.each(
118 roles,
119 (roleName, next) => {
120 const currentRole = sourceRoles[roleName];
121 const data = Object.assign({ name: roleName }, currentRole);
122 this.cliManager.log(LogLevel.INFO, `Creating role: ${data.name}`);
123 this.rolesService.create(data, env, next);
124 },
125 done
126 );
127 }
128
129 _createGroups(env, sourceGroups, done) {
130 if (isNullOrUndefined(sourceGroups) || isEmpty(sourceGroups)) {
131 return setImmediate(done);
132 }
133
134 const groups = Object.keys(sourceGroups);
135 async.each(
136 groups,
137 (groupId, next) => {
138 const currentGroup = sourceGroups[groupId];
139 const data = Object.assign({ _id: groupId }, currentGroup);
140 if (Array.isArray(currentGroup.groups)) {
141 data.groups = EnvFileProcessor._buildChildGroups(currentGroup.groups);
142 }
143
144 this.cliManager.log(LogLevel.INFO, `Creating group: ${data._id}`);
145 this.groupsService.create(data, env, next);
146 },
147 done
148 );
149 }
150
151 static _buildChildGroups(childGroups) {
152 const result = childGroups.map((x) => {
153 return {
154 _type: 'KinveyRef',
155 _collection: 'group',
156 _id: x
157 };
158 });
159
160 return result;
161 }
162
163 _createCommonCode(envId, sourceCommonCode, done) {
164 if (isNullOrUndefined(sourceCommonCode) || isEmpty(sourceCommonCode)) {
165 return setImmediate(done);
166 }
167
168 const commonCodeNames = Object.keys(sourceCommonCode);
169 async.each(
170 commonCodeNames,
171 (codeName, next) => {
172 const currentCommonCode = sourceCommonCode[codeName];
173 const data = Object.assign({ name: codeName }, currentCommonCode);
174 this.cliManager.log(LogLevel.INFO, `Creating common code: ${data.name}`);
175 this.blService.createCommonCode(data, envId, next);
176 },
177 done
178 );
179 }
180
181 _createOrUpdateCollectionHooks(env, sourceHooks, done) {
182 if (isNullOrUndefined(sourceHooks) || isEmpty(sourceHooks)) {
183 return setImmediate(done);
184 }
185
186 const collNames = Object.keys(sourceHooks);
187 async.each(
188 collNames,
189 (collName, next) => {
190 const hooksPerCollection = Object.assign({}, sourceHooks[collName]);
191 const hookNames = Object.keys(hooksPerCollection);
192
193 async.each(
194 hookNames,
195 (hookName, cb) => {
196 const currentHook = hooksPerCollection[hookName];
197 this._setServiceOnHookOrEndpoint(currentHook, hookName, env, (err) => {
198 if (err) {
199 return cb(err);
200 }
201
202 const mappedHookName = CollectionHook[hookName];
203 this.cliManager.log(LogLevel.INFO, `Creating/updating hook for ${collName} collection: ${hookName}`);
204 this.blService.createOrUpdateHook(currentHook, env.id, collName, mappedHookName, cb);
205 });
206 },
207 next
208 );
209 },
210 done
211 );
212 }
213
214 _createEndpoints(env, sourceEndpoints, done) {
215 if (isNullOrUndefined(sourceEndpoints) || isEmpty(sourceEndpoints)) {
216 return setImmediate(done);
217 }
218
219 const endpointNames = Object.keys(sourceEndpoints);
220 async.each(
221 endpointNames,
222 (name, next) => {
223 const currentEndpoint = Object.assign({}, sourceEndpoints[name]);
224 if (currentEndpoint.schedule && !currentEndpoint.schedule.interval) {
225 currentEndpoint.schedule = currentEndpoint.schedule.start;
226 }
227
228 this._setServiceOnHookOrEndpoint(currentEndpoint, name, env, (err) => {
229 if (err) {
230 return next(err);
231 }
232
233 this.cliManager.log(LogLevel.INFO, `Creating endpoint: ${name}`);
234 currentEndpoint.name = name;
235 this.blService.createEndpoint(currentEndpoint, env.id, next);
236 });
237 },
238 done
239 );
240 }
241
242 _updateEnv(source, options, done) {
243 const env = options[EnvOptionsName.ENV];
244 const envId = env.id;
245
246 async.series([
247 (next) => {
248 let envData = { name: source.name || env.name };
249 envData = Object.assign(envData, source.settings);
250 this.cliManager.log(LogLevel.INFO, `Updating environment: ${envData.name}`);
251 this.environmentsService.update(envId, envData, next);
252 },
253 (next) => {
254 this._modifyCommonCode(source.commonCode, env, next);
255 },
256 (next) => {
257 this._modifyRoles(source.roles, env, next);
258 },
259 (next) => {
260 this._modifyGroups(source.groups, env, next);
261 },
262 (next) => {
263 this._modifyCollections(source.collections, env, next);
264 },
265 (next) => {
266 this._createOrUpdateCollectionHooks(env, source.collectionHooks, next);
267 },
268 (next) => {
269 this._modifyEndpoints(source.customEndpoints, env, next);
270 },
271 (next) => {
272 this._modifyPushSettings(source.push, envId, next);
273 }
274 ], (err) => {
275 if (err) {
276 return done(err);
277 }
278
279 done(null, { id: envId });
280 });
281 }
282
283 static _groupCollectionsPerOperationType(originalCollections, collections) {
284 const groupedEntities = FileProcessorHelper.groupEntitiesPerOperationType(originalCollections, collections);
285 if (!isEmpty(groupedEntities[OperationType.DELETE])) {
286 const collToDel = groupedEntities[OperationType.DELETE].filter(x => x !== 'user' && x !== '_blob');
287 groupedEntities[OperationType.DELETE] = collToDel;
288 }
289
290 return groupedEntities;
291 }
292
293 _modifyRoles(roles = {}, env, done) {
294 if (isEmpty(roles)) {
295 return setImmediate(done);
296 }
297
298 let groupedRoles;
299
300 async.series([
301 (next) => {
302 this.rolesService.getAll(env, (err, originalRoles) => {
303 if (err) {
304 return next(err);
305 }
306
307 try {
308 groupedRoles = this._groupRolesPerOperationType(originalRoles, roles);
309 } catch (ex) {
310 return next(ex);
311 }
312
313 next();
314 });
315 },
316 (next) => {
317 this._createRoles(env, groupedRoles[OperationType.CREATE], next);
318 },
319 (next) => {
320 this._updateRoles(env, groupedRoles[OperationType.UPDATE], next);
321 }
322 ], done);
323 }
324
325 _updateRoles(env, sourceRoles, done) {
326 if (isNullOrUndefined(sourceRoles) || isEmpty(sourceRoles)) {
327 return setImmediate(done);
328 }
329
330 async.each(
331 sourceRoles,
332 (currentRole, next) => {
333 this.cliManager.log(LogLevel.INFO, `Updating role: ${currentRole._id}(${currentRole.name})`);
334 this.rolesService.update(currentRole._id, currentRole, env, next);
335 },
336 done
337 );
338 }
339
340 /**
341 * Groups roles to modify per operation type that should be executed. Throws if it encounters a role that is expected
342 * to be updated by name but several roles with the same name already exist.
343 * @param {Array} originalRoles Already existing roles from the backend.
344 * @param {Object} rolesToModify Contains role names/IDs where each identifier holds an object defining the role.
345 * @returns {Object}
346 * @private
347 */
348 // eslint-disable-next-line
349 _groupRolesPerOperationType(originalRoles, rolesToModify) {
350 const entitiesToCreate = {};
351 const entitiesToUpdate = [];
352
353 const groupedRoles = this._groupRolesByIdentifiers(originalRoles);
354 const originalRolesGroupedById = groupedRoles.byId;
355 const originalRolesGroupedByName = groupedRoles.byName;
356 const originalRolesDuplicatedNames = groupedRoles.duplicatedNames;
357
358 const originalRolesIds = Object.keys(originalRolesGroupedById);
359 const originalRolesNames = Object.keys(originalRolesGroupedByName);
360 const rolesToModifyIdentifiers = Object.keys(rolesToModify);
361
362 rolesToModifyIdentifiers.forEach((modifiedRoleIdentifier) => {
363 const isIdAndExists = originalRolesIds.includes(modifiedRoleIdentifier);
364 const isNameAndExists = originalRolesNames.includes(modifiedRoleIdentifier);
365 const roleExists = isIdAndExists || isNameAndExists;
366 if (roleExists) {
367 let id;
368 if (isIdAndExists) {
369 id = modifiedRoleIdentifier;
370 } else {
371 if (originalRolesDuplicatedNames.includes(modifiedRoleIdentifier)) {
372 throw new Error(`Cannot update role '${modifiedRoleIdentifier}' using the name as identifier as there is more than one role with this name.`);
373 }
374
375 id = originalRolesGroupedByName[modifiedRoleIdentifier]._id;
376 }
377
378 const name = rolesToModify[modifiedRoleIdentifier].name || originalRolesGroupedById[id].name;
379 entitiesToUpdate.push(Object.assign({ _id: id, name }, rolesToModify[modifiedRoleIdentifier]));
380 } else {
381 entitiesToCreate[modifiedRoleIdentifier] = rolesToModify[modifiedRoleIdentifier];
382 }
383 });
384
385 return {
386 [OperationType.CREATE]: entitiesToCreate,
387 [OperationType.UPDATE]: entitiesToUpdate
388 };
389 }
390
391 // eslint-disable-next-line
392 _groupRolesByIdentifiers(roles) {
393 const result = {
394 byId: {},
395 byName: {},
396 duplicatedNames: []
397 };
398
399 roles.forEach((role) => {
400 result.byId[role._id] = role;
401 const name = role.name;
402 if (result.byName[name]) {
403 result.duplicatedNames.push(name);
404 }
405
406 result.byName[name] = role;
407 });
408
409 return result;
410 }
411
412 _modifyCollections(collections = {}, env, done) {
413 let groupedEntities;
414 const envId = env.id;
415 let existingRolesGrouped;
416
417 async.series([
418 (next) => {
419 this.collectionsService.getAll(envId, (err, originalCollections) => {
420 if (err) {
421 return next(err);
422 }
423
424 groupedEntities = EnvFileProcessor._groupCollectionsPerOperationType(originalCollections, collections);
425 next();
426 });
427 },
428 (next) => {
429 this.rolesService.getAll(env, (err, roles) => {
430 if (err) {
431 return next(err);
432 }
433
434 existingRolesGrouped = this._groupRolesByIdentifiers(roles);
435 next();
436 });
437 },
438 // we're not removing entities, at least for now
439 // (next) => {
440 // this._deleteCollections(collectionsToDelete, envId, next);
441 // },
442 (next) => {
443 this._updateCollections(groupedEntities[OperationType.UPDATE], env, existingRolesGrouped, next);
444 },
445 (next) => {
446 this._createCollections(groupedEntities[OperationType.CREATE], env, existingRolesGrouped, next);
447 }
448 ], done);
449 }
450
451 _modifyGroups(groups = {}, env, done) {
452 if (isEmpty(groups)) {
453 return setImmediate(done);
454 }
455
456 let groupedGroups;
457
458 async.series([
459 (next) => {
460 this.groupsService.getAll(env, (err, originalGroups) => {
461 if (err) {
462 return next(err);
463 }
464
465 try {
466 groupedGroups = this._groupRolesPerOperationType(originalGroups, groups, '_id');
467 } catch (ex) {
468 return next(ex);
469 }
470
471 next();
472 });
473 },
474 (next) => {
475 this._createGroups(env, groupedGroups[OperationType.CREATE], next);
476 },
477 (next) => {
478 this._updateGroups(env, groupedGroups[OperationType.UPDATE], next);
479 }
480 ], done);
481 }
482
483 _updateGroups(env, groups, done) {
484 if (isEmpty(groups)) {
485 return setImmediate(done);
486 }
487
488 async.each(
489 groups,
490 (currentGroup, next) => {
491 if (Array.isArray(currentGroup.groups)) {
492 currentGroup.groups = EnvFileProcessor._buildChildGroups(currentGroup.groups);
493 }
494
495 this.cliManager.log(LogLevel.INFO, `Updating group: ${currentGroup._id}`);
496 this.groupsService.update(currentGroup._id, currentGroup, env, next);
497 },
498 done
499 );
500 }
501
502 /**
503 * Modifies endpoints - create or update.
504 * @param {Object} endpoints Contains endpoints names along with data to modify.
505 * @param {Object} env
506 * @param done
507 * @returns {*}
508 * @private
509 */
510 _modifyEndpoints(endpoints = {}, env, done) {
511 // remove this check if we start deleting endpoints that are not included in config file
512 if (isEmpty(endpoints)) {
513 return setImmediate(done);
514 }
515
516 let groupedEntities;
517 let existingEndpoints;
518 const envId = env.id;
519
520 async.series([
521 (next) => {
522 this.blService.getEndpoints(envId, null, (err, originalEndpoints) => {
523 if (err) {
524 return next(err);
525 }
526
527 existingEndpoints = originalEndpoints;
528 groupedEntities = FileProcessorHelper.groupEntitiesPerOperationType(originalEndpoints, endpoints);
529
530 next();
531 });
532 },
533 (next) => {
534 this._updateEndpoints(groupedEntities[OperationType.UPDATE], env, next);
535 },
536 (next) => {
537 this._createEndpoints(env, groupedEntities[OperationType.CREATE], next);
538 }
539 ], done);
540 }
541
542 _updateEndpoints(endpointsToUpdate, env, done) {
543 const envId = env.id;
544
545 async.each(
546 endpointsToUpdate,
547 (endpoint, next) => {
548 const nameIdentifier = Object.keys(endpoint)[0];
549 this.cliManager.log(LogLevel.INFO, `Updating endpoint: ${nameIdentifier}`);
550 const updateData = endpoint[nameIdentifier];
551 updateData.name = updateData.name || nameIdentifier;
552 if (updateData.schedule && !updateData.schedule.interval) {
553 updateData.schedule = updateData.schedule.start;
554 }
555
556 this._setServiceOnHookOrEndpoint(updateData, nameIdentifier, env, (err) => {
557 if (err) {
558 return next(err);
559 }
560
561 this.blService.updateEndpoint(nameIdentifier, updateData, envId, next);
562 });
563 },
564 done
565 );
566 }
567
568 _modifyPushSettings(sourcePush, envId, done) {
569 if (isEmpty(sourcePush)) {
570 return setImmediate(done);
571 }
572
573 async.series([
574 (next) => {
575 const androidSettings = sourcePush.android;
576 if (isEmpty(androidSettings)) {
577 return setImmediate(next);
578 }
579
580 const data = {
581 projectId: androidSettings.senderId,
582 apiKey: androidSettings.apiKey
583 };
584 this.cliManager.log(LogLevel.INFO, 'Modifying Android push configuration');
585 this.pushService.configureAndroidSettings(envId, data, next);
586 },
587 (next) => {
588 const iosSettings = sourcePush.ios;
589 if (isEmpty(iosSettings)) {
590 return setImmediate(next);
591 }
592
593 this.cliManager.log(LogLevel.INFO, 'Modifying iOS push configuration');
594 this.pushService.configureIosSettings(envId, iosSettings, next);
595 }
596 ], done);
597 }
598
599 _modifyCommonCode(commonCode = {}, env, done) {
600 const envId = env.id;
601 let groupedEntities;
602
603 async.series([
604 (next) => {
605 this.blService.getCommonCode(envId, null, null, (err, originalCommonModules) => {
606 if (err) {
607 return next(err);
608 }
609
610 groupedEntities = FileProcessorHelper.groupEntitiesPerOperationType(originalCommonModules, commonCode);
611 next();
612 });
613 },
614 (next) => {
615 this._updateCommonCode(groupedEntities[OperationType.UPDATE], env, next);
616 },
617 (next) => {
618 this._createCommonCode(envId, groupedEntities[OperationType.CREATE], next);
619 }
620 ], done);
621 }
622
623 _updateCommonCode(commonCodeModules, env, done) {
624 const envId = env.id;
625
626 async.each(
627 commonCodeModules,
628 (commonModule, next) => {
629 const moduleName = Object.keys(commonModule)[0];
630 this.cliManager.log(LogLevel.INFO, `Updating common code module: ${moduleName}`);
631 const sourceModule = commonModule[moduleName];
632 sourceModule.name = sourceModule.name || moduleName;
633 this.blService.updateCommonCode(moduleName, sourceModule, envId, next);
634 },
635 done
636 );
637 }
638
639 static _getFilteredCollectionData(sourceCollection) {
640 const collBlacklist = ['type', 'service', 'serviceEnvironment', 'serviceObject'];
641 const filteredData = getObjectByOmitting(sourceCollection, collBlacklist);
642 return filteredData;
643 }
644
645 /**
646 * @param {Object} collections Object of objects. On first level each object contains one property that is the name of
647 * the collection.
648 * @param {Object} env
649 * @param {Object} groupedOriginalRoles Grouped roles from the backend.
650 * @param done
651 * @returns {*}
652 * @private
653 */
654 _createCollections(collections, env, groupedOriginalRoles, done) {
655 if (isEmpty(collections)) {
656 return setImmediate(done);
657 }
658
659 const collectionNames = Object.keys(collections);
660 async.each(
661 collectionNames,
662 (collName, next) => {
663 this._createCollection(collName, collections[collName], env, groupedOriginalRoles, next);
664 },
665 done
666 );
667 }
668
669 /**
670 * Sets proper dataLink property to a collection object if the collection is external.
671 * @param {Object} sourceColl
672 * @param {Object} targetColl
673 * @param {Object} env
674 * @param done
675 * @returns {*}
676 * @private
677 */
678 _setServiceOnCollection(sourceColl, targetColl, env, done) {
679 this._findServiceFromSource(sourceColl, targetColl.name, env, (err, data) => {
680 if (err) {
681 return done(err);
682 }
683
684 if (data) {
685 targetColl.dataLink = {
686 serviceObjectName: sourceColl.serviceObject,
687 id: data.service.id,
688 backingServerId: data.svcEnv.id
689 };
690 }
691
692 done();
693 });
694 }
695
696 _setServiceOnHookOrEndpoint(source, name, env, done) {
697 this._findServiceFromSource(source, name, env, (err, data) => {
698 if (err) {
699 return done(err);
700 }
701
702 if (data) {
703 source.host = data.svcEnv.id;
704 source.sdkHandlerName = source.handlerName;
705 }
706
707 done();
708 });
709 }
710
711 _findServiceFromSource(source, name, env, done) {
712 if (source.type !== 'external') {
713 return setImmediate(done);
714 }
715
716 const domainId = env.app;
717 this._getServiceInfo(source.service, source.serviceEnvironment, domainId, done);
718 }
719
720 _createCollection(collName, sourceColl, env, groupedOriginalRoles, done) {
721 this.cliManager.log(LogLevel.INFO, `Creating collection: ${collName}`);
722
723 const collData = EnvFileProcessor._getFilteredCollectionData(sourceColl);
724 collData.name = collName;
725
726 try {
727 EnvFileProcessor.setPermissionsOnCollection(collData, groupedOriginalRoles);
728 } catch (ex) {
729 return done(ex);
730 }
731
732 async.series([
733 (next) => {
734 this._setServiceOnCollection(sourceColl, collData, env, next);
735 },
736 (next) => {
737 const envId = env.id;
738 this.collectionsService.create(collData, envId, next);
739 }
740 ], done);
741 }
742
743 _getServiceInfo(serviceIdentifier, svcEnvIdentifier, appId, done) {
744 let service;
745 let svcEnv;
746 let app;
747
748 async.series([
749 (next) => {
750 this.applicationsService.getByIdOrName(appId, (err, data) => {
751 if (err) {
752 return next(err);
753 }
754
755 app = data;
756 next();
757 });
758 },
759 (next) => {
760 this.servicesService.getAllByDomainType(app, null, (err, data) => {
761 if (err) {
762 return next(err);
763 }
764
765 service = data.find(x => x.id === serviceIdentifier || x.name === serviceIdentifier);
766 if (!service) {
767 return next(getCustomNotFoundError(EntityType.SERVICE, serviceIdentifier));
768 }
769
770 next();
771 });
772 },
773 (next) => {
774 this.servicesService.getServiceEnvs(service.id, (err, data) => {
775 if (err) {
776 return next(err);
777 }
778
779 svcEnv = data.find(x => x.id === svcEnvIdentifier || x.name === svcEnvIdentifier);
780 if (!svcEnv) {
781 return next(getCustomNotFoundError(EntityType.SCV_ENV, svcEnvIdentifier));
782 }
783
784 next();
785 });
786 }
787 ], (err) => {
788 if (err) {
789 return done(err);
790 }
791
792 done(null, { service, svcEnv });
793 });
794 }
795
796 _updateCollections(collections, env, groupedOriginalRoles, done) {
797 const envId = env.id;
798
799 async.each(
800 collections,
801 (coll, next) => {
802 const collName = Object.keys(coll)[0];
803 this.cliManager.log(LogLevel.INFO, `Updating collection: ${collName}`);
804 const sourceColl = coll[collName];
805 const data = EnvFileProcessor._getFilteredCollectionData(sourceColl);
806 data.name = data.name || collName;
807
808 try {
809 EnvFileProcessor.setPermissionsOnCollection(data, groupedOriginalRoles);
810 } catch (ex) {
811 return next(ex);
812 }
813
814 this._setServiceOnCollection(sourceColl, data, env, (err) => {
815 if (err) {
816 return next(err);
817 }
818
819 this.collectionsService.update(envId, collName, data, next);
820 });
821 },
822 done
823 );
824 }
825
826 /**
827 * Sets permissions on a collection in a backend accepted format. Modifies the collection object. Throws if the
828 * specified role identifier does not exist or if it is a name and more than one role with this name exists.
829 * @param {Object} collection
830 * @param {Object} existingRoles Roles that exist on the backend.
831 * @param {Object} existingRoles.byId Existing roles grouped by ID.
832 * @param {Object} existingRoles.byName Existing roles grouped by name.
833 * @param {Array} existingRoles.duplicatedNames A list of non-unique names.
834 * @private
835 */
836 static setPermissionsOnCollection(collection, existingRoles) {
837 const initialPermissions = collection.permissions;
838 const isBasicPermissionsFormat = typeof initialPermissions === 'string';
839 if (isBasicPermissionsFormat) {
840 collection.permissions = BackendCollectionPermission[initialPermissions];
841 return;
842 }
843
844 const transformedPermissions = {};
845 AllowedPermissionsOperations.forEach((op) => { transformedPermissions[op] = []; });
846
847 const rolesIdentifiers = Object.keys(initialPermissions);
848 rolesIdentifiers.forEach((roleIdentifier) => {
849 const isIdAndExists = Object.prototype.hasOwnProperty.call(existingRoles.byId, roleIdentifier);
850 const isNameAndExists = Object.prototype.hasOwnProperty.call(existingRoles.byName, roleIdentifier);
851 const isSystemRole = roleIdentifier === 'all-users';
852 const roleExists = isIdAndExists || isNameAndExists || isSystemRole;
853 const basicErrMsg = `Cannot set permissions on collection '${collection.name}'.`;
854 if (!roleExists) {
855 throw new Error(`${basicErrMsg} Role with identifier '${roleIdentifier}' not found.`);
856 }
857
858 let id;
859 if (isIdAndExists || isSystemRole) {
860 id = roleIdentifier;
861 } else {
862 if (existingRoles.duplicatedNames.includes(roleIdentifier)) {
863 throw new Error(`${basicErrMsg} More than one role exists with name '${roleIdentifier}'.`);
864 }
865
866 id = existingRoles.byName[roleIdentifier]._id;
867 }
868
869 AllowedPermissionsOperations.forEach((op) => {
870 if (Object.prototype.hasOwnProperty.call(initialPermissions[roleIdentifier], op)) {
871 transformedPermissions[op].push({
872 roleId: id,
873 type: initialPermissions[roleIdentifier][op]
874 });
875 }
876 });
877 });
878
879 collection.permissions = transformedPermissions;
880 }
881
882 _deleteCollections(collections, envId, done) {
883 async.each(
884 collections,
885 (collName, next) => {
886 this.cliManager.log(LogLevel.INFO, `Deleting collection ${collName}`);
887 this.collectionsService.deleteById(envId, collName, next);
888 },
889 done
890 );
891 }
892}
893
894module.exports = EnvFileProcessor;