UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

210 lines (186 loc) 7.24 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 5.3.0 */ import EntityV2Collection from "../abstract/entityV2Collection"; import EntityValidationError from "../abstract/entityValidationError"; import CustomFieldEntity from "./customFieldEntity"; export const CUSTOM_FIELD_COLLECTION_MAX_CONTENT_SIZE = 50_000; export default class CustomFieldsCollection extends EntityV2Collection { /** * @inheritDoc */ get entityClass() { return CustomFieldEntity; } /** * @inheritDoc * @throws {EntityCollectionError} Build Rule: Ensure all items in the collection are unique by ID. * @throws {EntityCollectionError} Build Rule: Ensure the total content do not exceed the maximum allowed. */ constructor(dtos = [], options = {}) { super(dtos, options); } /* * ================================================== * Validation * ================================================== */ /** * Get metadata private keys collection schema * * @returns {Object} schema */ static getSchema() { return { type: "array", items: CustomFieldEntity.getSchema(), maxItems: 128, }; } /** * @inheritDoc * @param {Set} [options.uniqueIdsSetCache] A set of unique ids. * @param {Set} [options.uniqueUserIdsSetCache] A set of unique user ids. * @throws {EntityValidationError} If a permission already exists with the same id. */ validateBuildRules(item, options = {}) { this.assertNotExist("id", item._props.id, { haystackSet: options?.uniqueIdsSetCache }); this.assertContentDoNotExceedMaxSize(item, options); } /** * Assert that no item in the collection already has the given value for the given property. * Note: The assertion ignore undefined prop value, it is the schema responsibility to ensure properties are defined. * * @param {CustomFieldEntity} item * @param {object} options Options. * @param {number} options.contentInformation.currentSize The current content size for excess checking * @throw {EntityValidationError} If the maximum content size allowed has been exceeded * @private */ assertContentDoNotExceedMaxSize(item, options) { const currentSize = options?.contentInformation?.currentSize || this.currentSize; if (currentSize + (item.value?.toString().length || 0) <= CUSTOM_FIELD_COLLECTION_MAX_CONTENT_SIZE) { return; } const error = new EntityValidationError(); error.addError( "items", "maxContentSize", `The collection items should not cumulate more than ${CUSTOM_FIELD_COLLECTION_MAX_CONTENT_SIZE} characters in total.`, ); throw error; } /** * @inheritDoc */ pushMany(data, entityOptions = {}, options = {}) { const uniqueIdsSetCache = new Set(this.extract("id")); const contentInformation = { currentSize: 0 }; const onItemPushed = (item) => { uniqueIdsSetCache.add(item._props.id); contentInformation.currentSize += item.value?.toString().length || 0; }; options = { onItemPushed: onItemPushed, validateBuildRules: { ...options?.validateBuildRules, uniqueIdsSetCache, contentInformation }, ...options, }; super.pushMany(data, entityOptions, options); } /** * Returns the current size. * Calculate the size of all custom field entity value. */ get currentSize() { return this.items.reduce((size, customFieldEntity) => (customFieldEntity.value?.toString().length || 0) + size, 0); } /** * Returns true if the collection is considered empty. * Even if an element is present in the collection, as long as this element is considered empty, the collection will be. */ isEmpty() { return this.length === 0 || !this.items.find((i) => !i.isEmpty()); } /** * Return a DTO for custom fields metadata * Delete all secret properties * @return {*[]} */ toMetadataDto() { return this.items.map((customField) => customField.toMetadataDto()); } /** * Return a DTO for custom fields secret * Delete all metadata properties * @return {*[]} */ toSecretDto() { return this.items.map((customField) => customField.toSecretDto()); } /** * Returns true if both collection are different * @param {CustomFieldsCollection} collectionA * @param {CustomFieldsCollection} collectionB * @returns {boolean} * @throws {TypeError} if any of the parameters are not of type CustomFieldsCollection */ static areCollectionsDifferent(collectionA, collectionB) { if (!(collectionA instanceof CustomFieldsCollection) || !(collectionB instanceof CustomFieldsCollection)) { throw new TypeError("Both parameters must be of type CustomFieldsCollection"); } const length = collectionA.length; if (length !== collectionB.length) { return true; } for (let i = 0; i < length; i++) { const areFieldsDifferent = CustomFieldEntity.areFieldsDifferent(collectionA.items[i], collectionB.items[i]); if (areFieldsDifferent) { return true; } } return false; } /** * Merge collection metadata and secret * @param collectionMetadata * @param collectionSecret * @throws {TypeError} if any of the parameters are not of type CustomFieldsCollection * @throws {TypeError} if any of the parameters have not the same length * @return {CustomFieldsCollection} */ static mergeCollectionsMetadataAndSecret(collectionMetadata, collectionSecret) { if ( !(collectionMetadata instanceof CustomFieldsCollection) || !(collectionSecret instanceof CustomFieldsCollection) ) { throw new TypeError("Both parameters must be of type CustomFieldsCollection"); } const length = collectionMetadata.length; if (length !== collectionSecret.length) { console.debug("Collections are corrupted, some data is missing"); } const collectionToMerge = collectionMetadata.toDto(); // Deep merge keeping the order from the source and add at the end the new entry const collectionMergedDto = collectionToMerge.reduce((columnsMerged, columnToMerge) => { const index = columnsMerged.findIndex((column) => column.id === columnToMerge.id); // Look for the columnsMerged has the same id while iterating // If column found need to merge with value of columnsToMerge if (index > -1) { columnsMerged[index] = Object.assign(columnsMerged[index], columnToMerge); return columnsMerged; } // should not happen, a key value pair is missing return [...columnsMerged, columnToMerge]; }, collectionSecret.toDto()); // Initial values of reduce to merge return new CustomFieldsCollection(collectionMergedDto); } }