From 04d7b8068aa91cd4a43d523a388a21d60eb935c3 Mon Sep 17 00:00:00 2001 From: Nishant0928 Date: Mon, 7 Aug 2023 12:43:20 +0530 Subject: [PATCH] ELEMENTS-1652: prevent replacing and removing an attachment under retention --- ui/actions/nuxeo-delete-blob-button.js | 39 ++++++++- ui/test/nuxeo-delete-blob.test.js | 117 +++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 ui/test/nuxeo-delete-blob.test.js diff --git a/ui/actions/nuxeo-delete-blob-button.js b/ui/actions/nuxeo-delete-blob-button.js index 3c51fae141..2fc6a6d593 100644 --- a/ui/actions/nuxeo-delete-blob-button.js +++ b/ui/actions/nuxeo-delete-blob-button.js @@ -137,11 +137,46 @@ import '../nuxeo-button-styles.js'; !this.isImmutable(doc) && !this.hasType(doc, 'Root') && !this.isTrashed(doc) && - !(doc.isRecord && this.xpath !== 'file:content') && - !(this.isUnderRetentionOrLegalHold(doc) && this.xpath === 'file:content') + !this._isPropUnderRetention(doc) ); } + _isPropUnderRetention(doc) { + if (doc && doc.isUnderRetentionOrLegalHold && doc.retainedProperties && doc.retainedProperties.length > 0) { + const { retainedProperties } = doc; + /* if retained property is multivalued attachment, and all files are to be retained, denoted by '*', + then return true. + if retained property is multivalued attachment, but only a single file is to be retained, + then return true only for that file */ + return retainedProperties.find( + (prop) => + this._transformXpathRegex(prop, this.xpath) || // xpath = docname:files/*/file + prop.startsWith(this.xpath) || // xpath = docname:files/1/file + (prop.includes(this.xpath.split('/')[0]) && !prop.includes('/')), // xpath = docname:files + ); + } + return false; + } + + _transformXpathRegex(prop, xpath) { + const transformedArray = []; + const splitter = '/'; + const star = '*'; + if (prop.includes(star)) { + let xpathArray = xpath.split(splitter); + + for (let i = 0; i < xpathArray.length; i++) { + if (!Number.isNaN(parseInt(xpathArray[i], 10))) { + xpathArray[i] = star; + } + transformedArray.push(xpathArray[i]); + } + xpathArray = transformedArray; + xpath = xpathArray.join(splitter); + } + return prop === xpath; + } + _computeLabel() { return this.i18n('deleteBlobButton.tooltip'); } diff --git a/ui/test/nuxeo-delete-blob.test.js b/ui/test/nuxeo-delete-blob.test.js new file mode 100644 index 0000000000..a9895c7e85 --- /dev/null +++ b/ui/test/nuxeo-delete-blob.test.js @@ -0,0 +1,117 @@ +/** + @license + ©2023 Hyland Software, Inc. and its affiliates. All rights reserved. +All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { fixture, flush, html, isElementVisible } from '@nuxeo/testing-helpers'; +import { dom } from '@polymer/polymer/lib/legacy/polymer.dom'; +import '../actions/nuxeo-delete-blob-button.js'; + +const isActionDivVisible = (button) => isElementVisible(dom(button.root).querySelector('.action')); + +suite('nuxeo-delete-blob-button', () => { + let button; + + setup(async () => { + button = await fixture( + html` + + `, + ); + sinon.stub(button, 'isImmutable').returns(false); + sinon.stub(button, 'hasType').returns(false); + sinon.stub(button, 'isTrashed').returns(false); + }); + + suite('Button Visibility', () => { + test('Should not be visible when no permission is granted', async () => { + button.document = { + contextParameters: { + permissions: [], + }, + }; + await flush(); + expect(isActionDivVisible(button)).to.be.false; + }); + + test('Should not be visible when only the "Read" permission is granted', async () => { + button.document = { + contextParameters: { + permissions: ['Read'], + }, + }; + await flush(); + expect(isActionDivVisible(button)).to.be.false; + }); + + test('Should be visible when the "WriteProperties" permission is granted', async () => { + button.document = { + contextParameters: { + permissions: ['WriteProperties'], + }, + }; + await flush(); + expect(isActionDivVisible(button)).to.be.true; + }); + }); + + suite('should return whether property is under retention', () => { + const document = { + isUnderRetentionOrLegalHold: true, + retainedProperties: [ + 'checkext:single', + 'checkext:field1/2/item', + 'files:files/*/file', + 'checkext:multiple', + 'file:content', + ], + }; + test('when xpath = checkext:single, for document blob', () => { + button.xpath = 'checkext:single'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:multiple/0, for document attachement', () => { + button.xpath = 'checkext:multiple/0'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:multiple/1, for document attachement', () => { + button.xpath = 'checkext:multiple/1'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:field1/0, for custom property - document attachment', () => { + button.xpath = 'checkext:field1/0'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(true); + }); + test('when xpath = checkext:field1/2, for custom property - document attachment', () => { + button.xpath = 'checkext:field1/2'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(false); + }); + test('when xpath = files:files/0/file, for document attachement', () => { + button.xpath = 'files:files/0/file'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(false); + }); + test('when xpath = file:content, for document viewer', () => { + button.xpath = 'file:content'; + sinon.stub(button, 'hasPermission').returns(true); + expect(button._isAvailable(document)).to.eql(false); + }); + }); +}); \ No newline at end of file