CVE-2024-39902: Insufficient Information (NVD-CWE-noinfo)
value: Tuleap is an open source suite to improve management of software developments and collaboration. Prior to Tuleap Community Edition 15.10.99.128 and Tuleap Enterprise Edition 15.10-6 and 15.9-8, the checkbox "Apply same permissions to all sub-items of this folder" in the document manager permissions modal is not taken into account and always considered as unchecked. In situations where the permissions are being restricted some users might still keep, incorrectly, the possibility to edit or manage items. Only change made via the web UI are affected, changes directly made via the REST API are not impacted. This vulnerability is fixed in Tuleap Community Edition 15.10.99.128 and Tuleap Enterprise Edition 15.10-6 and 15.9-8.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-07-22T14:15Z
Repository: https://github.com/Enalean/tuleap
Language: TypeScript
Files changed: plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.test.ts, plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.vue, plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.test.js, plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.vue, plugins/document/scripts/document/helpers/emitter.ts
Commit date: 2024-07-09 14:23:20 +0000
Commit message: fix: Recursive permissions to document manager folder are not properly applied

Closes request #38675 Recursive permissions to document manager folder are not properly applied

How to test:
- Pick up a folder containing some folders and documents
- Open its permissions modal
- Set some permissions and click "Apply permissions to all sub-items"
- Submit
--> The permissions have been set on the current folder and all its
descendants.

Change-Id: Id10c3eb04677f21cb4b2ec237622022e84491e1e

Diff: diff --git a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.test.ts b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.test.ts
index c3a2326187671..d7ed80885e801 100644
--- a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.test.ts
+++ b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.test.ts
@@ -21,10 +21,11 @@ import type { VueWrapper } from "@vue/test-utils";
 import { shallowMount } from "@vue/test-utils";
 
 import PermissionsUpdateFolderSubItems from "./PermissionsUpdateFolderSubItems.vue";
 import { TYPE_FOLDER, TYPE_EMPTY } from "../../../constants";
 import { getGlobalTestOptions } from "../../../helpers/global-options-for-test";
+import emitter from "../../../helpers/emitter";
 import type { Empty, Folder, Item } from "../../../type";
 
 describe("PermissionsUpdateFolderSubItems", () => {
     function getWrapper(
         item: Item,
@@ -48,6 +49,26 @@ describe("PermissionsUpdateFolderSubItems", () => {
         const item = { type: TYPE_EMPTY } as Empty;
         const wrapper = getWrapper(item);
 
         expect(wrapper.html()).toMatchInlineSnapshot(`<!--v-if-->`);
     });
+
+    it.each([
+        ["checked", true],
+        ["unchecked", false],
+    ])(
+        'When the checkbox is %s, then it should emit a "update-apply-permissions-on-children" event containing the checkbox state.',
+        (state, is_checked) => {
+            const emit = jest.spyOn(emitter, "emit");
+            const wrapper = getWrapper({ type: TYPE_FOLDER } as Folder);
+            const checkbox = wrapper.find<HTMLInputElement>(
+                "[data-test=checkbox-apply-permissions-on-children]",
+            );
+
+            checkbox.setValue(is_checked);
+            checkbox.trigger("input");
+            expect(emit).toHaveBeenCalledWith("update-apply-permissions-on-children", {
+                do_permissions_apply_on_children: is_checked,
+            });
+        },
+    );
 });
diff --git a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.vue b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.vue
index fc42449690de6..c450f17de8980 100644
--- a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.vue
+++ b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateFolderSubItems.vue
@@ -18,31 +18,34 @@
   -
   -->
 <template>
     <div class="tlp-form-element" v-if="is_item_a_folder">
         <label class="tlp-label tlp-checkbox">
-            <input type="checkbox" v-bind:value="props.value" v-on:input="onInput" />
+            <input
+                type="checkbox"
+                v-on:input="onInput"
+                data-test="checkbox-apply-permissions-on-children"
+            />
             {{ $gettext("Apply same permissions to all sub-items of this folder") }}
         </label>
     </div>
 </template>
 <script setup lang="ts">
 import { isFolder } from "../../../helpers/type-check-helper";
 import type { Folder } from "../../../type";
 import { computed } from "vue";
+import emitter from "../../../helpers/emitter";
 
 const props = defineProps<{ item: Folder }>();
 
-const emit = defineEmits<{
-    (e: "input", value: string): void;
-}>();
-
 function onInput($event: Event): void {
     const event_target = $event.target;
 
     if (event_target instanceof HTMLInputElement) {
-        emit("input", event_target.value);
+        emitter.emit("update-apply-permissions-on-children", {
+            do_permissions_apply_on_children: event_target.checked,
+        });
     }
 }
 
 const is_item_a_folder = computed((): boolean => {
     return isFolder(props.item);
diff --git a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.test.js b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.test.js
index 61b49e39077bf..a904089e44bcd 100644
--- a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.test.js
+++ b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.test.js
@@ -241,6 +241,36 @@ describe("PermissionsUpdateModal", () => {
             can_write: item.permissions_for_groups.can_write,
             can_manage: item.permissions_for_groups.can_manage,
         };
         expect(wrapper.vm.updated_permissions).toEqual(expected_permissions_to_update_state);
     });
+
+    it('When an event "update-apply-permissions-on-children" is received, then it should update do_permissions_apply_on_children', () => {
+        const item = {
+            id: 104,
+            title: "My item",
+            permissions_for_groups: {
+                can_read: [],
+                can_write: [],
+                can_manage: [],
+            },
+        };
+
+        const wrapper = factory({ item });
+
+        wrapper.setData({
+            updated_permissions: {
+                ...item.permissions_for_groups,
+                apply_permissions_on_children: false,
+            },
+        });
+
+        emitter.emit("update-apply-permissions-on-children", {
+            do_permissions_apply_on_children: true,
+        });
+
+        expect(wrapper.vm.updated_permissions).toStrictEqual({
+            ...item.permissions_for_groups,
+            apply_permissions_on_children: true,
+        });
+    });
 });
diff --git a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.vue b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.vue
index 28573cdd6950d..cd24ae9b24a1f 100644
--- a/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.vue
+++ b/plugins/document/scripts/document/components/Folder/Permissions/PermissionsUpdateModal.vue
@@ -38,14 +38,11 @@
                 <permissions-for-groups-selector
                     v-bind:project_ugroups="project_ugroups ? project_ugroups : []"
                     v-model="updated_permissions"
                     v-bind:value="updated_permissions"
                 />
-                <permissions-update-folder-sub-items
-                    v-bind:item="item"
-                    v-model="updated_permissions.apply_permissions_on_children"
-                />
+                <permissions-update-folder-sub-items v-bind:item="item" />
             </div>
         </div>
         <modal-footer
             v-bind:is-loading="!can_be_submitted"
             v-bind:submit-button-label="submit_button_label"
@@ -117,10 +114,11 @@ export default {
     },
     mounted() {
         this.modal = createModal(this.$el);
         emitter.on("show-update-permissions-modal", this.show);
         emitter.on("update-permissions", this.updateUGroup);
+        emitter.on("update-apply-permissions-on-children", this.setApplyPermissionsOnChildren);
         this.modal.addEventListener("tlp-modal-hidden", this.reset);
         this.show();
     },
     beforeUnmount() {
         emitter.off("show-update-permissions-modal", this.show);
@@ -180,8 +178,15 @@ export default {
                     this.updated_permissions.can_manage = event.value;
                     break;
                 default:
             }
         },
+
+        setApplyPermissionsOnChildren(event) {
+            this.updated_permissions = {
+                ...this.updated_permissions,
+                apply_permissions_on_children: event.do_permissions_apply_on_children,
+            };
+        },
     },
 };
 </script>
diff --git a/plugins/document/scripts/document/helpers/emitter.ts b/plugins/document/scripts/document/helpers/emitter.ts
index 03720b5c7a9d8..87a18dfe543a2 100644
--- a/plugins/document/scripts/document/helpers/emitter.ts
+++ b/plugins/document/scripts/document/helpers/emitter.ts
@@ -86,10 +86,14 @@ export interface UpdateCriteriaEvent {
 export interface UpdateCriteriaDateEvent {
     criteria: string;
     value: SearchDate;
 }
 
+export type UpdateApplyPermissionsOnChildren = {
+    do_permissions_apply_on_children: boolean;
+};
+
 export type Events = {
     "update-status-property": string;
     "update-status-recursion": boolean;
     "update-title-property": string;
     "update-version-title": string;
@@ -127,10 +131,11 @@ export type Events = {
     "update-recursion-option": string;
     "update-file-properties": { FileProperties: FileProperties };
     "properties-recursion-list": UpdatePropertyListEvent;
 
     "update-permissions": string;
+    "update-apply-permissions-on-children": UpdateApplyPermissionsOnChildren;
     "update-global-criteria": string;
 
     "update-criteria": UpdateCriteriaEvent;
     "update-criteria-date": UpdateCriteriaDateEvent;
 };

Vulnerable code:             <input type="checkbox" v-bind:value="props.value" v-on:input="onInput" />
const emit = defineEmits<{
    (e: "input", value: string): void;
}>();

        emit("input", event_target.value);
                <permissions-update-folder-sub-items
                    v-bind:item="item"
                    v-model="updated_permissions.apply_permissions_on_children"
                />
Fixed code: import emitter from "../../../helpers/emitter";

    it.each([
        ["checked", true],
        ["unchecked", false],
    ])(
        'When the checkbox is %s, then it should emit a "update-apply-permissions-on-children" event containing the checkbox state.',
        (state, is_checked) => {
            const emit = jest.spyOn(emitter, "emit");
            const wrapper = getWrapper({ type: TYPE_FOLDER } as Folder);
            const checkbox = wrapper.find<HTMLInputElement>(
                "[data-test=checkbox-apply-permissions-on-children]",
            );

            checkbox.setValue(is_checked);
            checkbox.trigger("input");
            expect(emit).toHaveBeenCalledWith("update-apply-permissions-on-children", {
                do_permissions_apply_on_children: is_checked,
            });
        },
    );
            <input
                type="checkbox"
                v-on:input="onInput"
                data-test="checkbox-apply-permissions-on-children"
            />
import emitter from "../../../helpers/emitter";
        emitter.emit("update-apply-permissions-on-children", {
            do_permissions_apply_on_children: event_target.checked,
        });

    it('When an event "update-apply-permissions-on-children" is received, then it should update do_permissions_apply_on_children', () => {
        const item = {
            id: 104,
            title: "My item",
            permissions_for_groups: {
                can_read: [],
                can_write: [],
                can_manage: [],
            },
        };

        const wrapper = factory({ item });

        wrapper.setData({
            updated_permissions: {
                ...item.permissions_for_groups,
                apply_permissions_on_children: false,
            },
        });

        emitter.emit("update-apply-permissions-on-children", {
            do_permissions_apply_on_children: true,
        });

        expect(wrapper.vm.updated_permissions).toStrictEqual({
            ...item.permissions_for_groups,
            apply_permissions_on_children: true,
        });
    });
                <permissions-update-folder-sub-items v-bind:item="item" />
        emitter.on("update-apply-permissions-on-children", this.setApplyPermissionsOnChildren);

        setApplyPermissionsOnChildren(event) {
            this.updated_permissions = {
                ...this.updated_permissions,
                apply_permissions_on_children: event.do_permissions_apply_on_children,
            };
        },
export type UpdateApplyPermissionsOnChildren = {
    do_permissions_apply_on_children: boolean;
};

    "update-apply-permissions-on-children": UpdateApplyPermissionsOnChildren;