[FIX] web_editor, knowledge: prevent modification of data-oe-protected
Change class="oe-blackbox" to data-oe-protected="true" (same purpose), because the name was decided in a rush before 16.0 When the editor history management and sanitization (sanitize.js) handle nodes of an embedded view, OWL can become corrupted, which will lead to a traceback. This commit uses the attribute data-oe-protected="true" to prevent historyRollbacks or sanitization, and ensure that protected elements children are removed during the cleanForSave operation. The collaborative DOMPurify sanitization is untouched, for security reasons. To properly use `data-oe-protected` in collaborative mode, a more complex mechanism should be put in place to ensure that the protected element children are properly reconstructed for each client, and that it is not part of the collaboration. (i.e.: embedded view in knowledge do not support the collaborative mode currently). This commit also blocks the selection handling in tables which would be in a data-oe-protected="true" node. Task-3012552 closes odoo/odoo#106255 X-original-commit: 394e4fe567bb2eecbd0865de82914aa7396fde63 Related: odoo/enterprise#34245 Signed-off-by: David Beguin (dbe) <dbe@odoo.com> Signed-off-by: Abeloos Damien (abd) <abd@odoo.com>
This commit is contained in:
@@ -929,6 +929,13 @@ export class OdooEditor extends EventTarget {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (record.target && [Node.TEXT_NODE, Node.ELEMENT_NODE].includes(record.target.nodeType)) {
|
||||
const closestProtected = closestElement(record.target, '[data-oe-protected="true"]');
|
||||
if (closestProtected && closestProtected.nodeType === Node.ELEMENT_NODE &&
|
||||
record.target !== closestProtected) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filteredRecords.push(record);
|
||||
}
|
||||
return this.options.filterMutationRecords(filteredRecords);
|
||||
@@ -2164,12 +2171,16 @@ export class OdooEditor extends EventTarget {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_handleSelectionInTable(ev=undefined) {
|
||||
const selection = this.document.getSelection();
|
||||
const anchorNode = selection.anchorNode;
|
||||
if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) {
|
||||
return false;
|
||||
}
|
||||
this.deselectTable();
|
||||
const traversedNodes = getTraversedNodes(this.editable);
|
||||
if (this._isResizingTable || !traversedNodes.some(node => !!closestElement(node, 'td'))) {
|
||||
return false;
|
||||
}
|
||||
const selection = this.document.getSelection();
|
||||
let range;
|
||||
if (selection.rangeCount > 1) {
|
||||
// Firefox selection in table works with multiple ranges.
|
||||
@@ -3362,7 +3373,7 @@ export class OdooEditor extends EventTarget {
|
||||
_onSelectionChange() {
|
||||
const selection = this.document.getSelection();
|
||||
const anchorNode = selection.anchorNode;
|
||||
if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) {
|
||||
if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3547,6 +3558,13 @@ export class OdooEditor extends EventTarget {
|
||||
emptyElement.removeAttribute('data-oe-zws-empty-inline');
|
||||
}
|
||||
}
|
||||
|
||||
// Clean all protected nodes because they are not sanitized
|
||||
const protectedNodes = element.querySelectorAll('[data-oe-protected="true"]');
|
||||
for (const node of protectedNodes) {
|
||||
node.replaceChildren();
|
||||
}
|
||||
|
||||
sanitize(element);
|
||||
|
||||
// Remove contenteditable=false on elements
|
||||
@@ -3578,7 +3596,7 @@ export class OdooEditor extends EventTarget {
|
||||
_handleCommandHint() {
|
||||
const selection = this.document.getSelection();
|
||||
const anchorNode = selection.anchorNode;
|
||||
if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) {
|
||||
if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3652,7 +3670,7 @@ export class OdooEditor extends EventTarget {
|
||||
_fixSelectionOnContenteditableFalse() {
|
||||
const selection = this.document.getSelection();
|
||||
const anchorNode = selection.anchorNode;
|
||||
if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) {
|
||||
if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) {
|
||||
return;
|
||||
}
|
||||
// When the browser set the selection inside a node that is
|
||||
|
||||
@@ -109,6 +109,10 @@ class Sanitize {
|
||||
|
||||
_parse(node) {
|
||||
while (node) {
|
||||
const closestProtected = closestElement(node, '[data-oe-protected="true"]');
|
||||
if (closestProtected && node !== closestProtected) {
|
||||
return;
|
||||
}
|
||||
// Merge identical elements together.
|
||||
while (
|
||||
areSimilarElements(node, node.previousSibling) &&
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { OdooEditor } from '../../src/OdooEditor.js';
|
||||
import { getTraversedNodes } from '../../src/utils/utils.js';
|
||||
import {
|
||||
getTraversedNodes,
|
||||
setSelection,
|
||||
} from '../../src/utils/utils.js';
|
||||
import {
|
||||
BasicEditor,
|
||||
deleteBackward,
|
||||
@@ -5688,4 +5691,57 @@ X[]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('oe-protected', () => {
|
||||
it('should ignore protected elements children mutations', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: unformat(`
|
||||
<div><p>a[]</p></div>
|
||||
<div data-oe-protected="true"><p>a</p></div>
|
||||
`),
|
||||
stepFunction: async editor => {
|
||||
await insertText(editor, 'bc');
|
||||
const protectedParagraph = editor.editable.querySelector('[data-oe-protected="true"] > p');
|
||||
setSelection(protectedParagraph, 1);
|
||||
await insertText(editor, 'b');
|
||||
editor.historyUndo();
|
||||
},
|
||||
contentAfterEdit: unformat(`
|
||||
<div><p>ab[]</p></div>
|
||||
<div data-oe-protected="true"><p>ab</p></div>
|
||||
`),
|
||||
});
|
||||
});
|
||||
it('should not sanitize protected elements children', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: unformat(`
|
||||
<div>
|
||||
<p><i class="fa"></i></p>
|
||||
<ul><li><p><br></p></li></ul>
|
||||
</div>
|
||||
<div data-oe-protected="true">
|
||||
<p><i class="fa"></i></p>
|
||||
<ul><li><p><br></p></li></ul>
|
||||
</div>
|
||||
`),
|
||||
stepFunction: async editor => editor.sanitize(),
|
||||
contentAfterEdit: unformat(`
|
||||
<div>
|
||||
<p><i class="fa" contenteditable="false">\u200B</i></p>
|
||||
<ul><li><br></li></ul>
|
||||
</div>
|
||||
<div data-oe-protected="true">
|
||||
<p><i class="fa"></i></p>
|
||||
<ul><li><p><br></p></li></ul>
|
||||
</div>
|
||||
`),
|
||||
});
|
||||
});
|
||||
it('should remove protected elements children during cleaning', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<div><p>a[]</p></div><div data-oe-protected="true"><p>a</p></div>',
|
||||
contentAfter: '<div><p>a[]</p></div><div data-oe-protected="true"></div>',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,7 +302,7 @@ const Wysiwyg = Widget.extend({
|
||||
|
||||
const selection = self.odooEditor.document.getSelection();
|
||||
const anchorNode = selection.anchorNode;
|
||||
if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) {
|
||||
if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1777,7 +1777,7 @@ const Wysiwyg = Widget.extend({
|
||||
_updateEditorUI: function (e) {
|
||||
let selection = this.odooEditor.document.getSelection();
|
||||
const anchorNode = selection.anchorNode;
|
||||
if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) {
|
||||
if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ safe_attrs = clean.defs.safe_attrs | frozenset(
|
||||
'data-publish', 'data-id', 'data-res_id', 'data-interval', 'data-member_id', 'data-scroll-background-ratio', 'data-view-id',
|
||||
'data-class', 'data-mimetype', 'data-original-src', 'data-original-id', 'data-gl-filter', 'data-quality', 'data-resize-width',
|
||||
'data-shape', 'data-shape-colors', 'data-file-name', 'data-original-mimetype',
|
||||
'data-oe-protected', # editor
|
||||
'data-behavior-props', 'data-prop-name', # knowledge commands
|
||||
])
|
||||
SANITIZE_TAGS = {
|
||||
|
||||
Reference in New Issue
Block a user