CVE-2024-39902 (published 2024-07-22T14:15Z)

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.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/Enalean/tuleap
commit_details: repo_url: https://github.com/Enalean/tuleap
author: Thomas Gorka
author_date: 2024-07-09 14:51:09+02:00
msg: 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
repository: repo_url: https://github.com/Enalean/tuleap
repo_name: Enalean/tuleap
description: Tuleap is an Open Source Suite to improve management of software developments and collaboration. With a single web-based solution, project managers, developers & quality managers can easily build, deploy software projects.
repo_language: PHP
owner: Enalean
file_changes: filename: PermissionsUpdateFolderSubItems.test.ts
change_type: ModificationType.MODIFY
diff: @@ -23,6 +23,7 @@ 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", () => {
@@ -50,4 +51,24 @@ describe("PermissionsUpdateFolderSubItems", () => {
 
         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,
+            });
+        },
+    );
 });

programming_language: TypeScript
filename: PermissionsUpdateFolderSubItems.vue
change_type: ModificationType.MODIFY
diff: @@ -20,7 +20,11 @@
 <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>
@@ -29,18 +33,17 @@
 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,
+        });
     }
 }
 

programming_language: HTML
filename: PermissionsUpdateModal.test.js
change_type: ModificationType.MODIFY
diff: @@ -243,4 +243,34 @@ describe("PermissionsUpdateModal", () => {
         };
         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,
+        });
+    });
 });

programming_language: TypeScript
filename: PermissionsUpdateModal.vue
change_type: ModificationType.MODIFY
diff: @@ -40,10 +40,7 @@
                     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
@@ -119,6 +116,7 @@ export default {
         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();
     },
@@ -182,6 +180,13 @@ export default {
                 default:
             }
         },
+
+        setApplyPermissionsOnChildren(event) {
+            this.updated_permissions = {
+                ...this.updated_permissions,
+                apply_permissions_on_children: event.do_permissions_apply_on_children,
+            };
+        },
     },
 };
 </script>

programming_language: TypeScript
filename: emitter.ts
change_type: ModificationType.MODIFY
diff: @@ -88,6 +88,10 @@ export interface UpdateCriteriaDateEvent {
     value: SearchDate;
 }
 
+export type UpdateApplyPermissionsOnChildren = {
+    do_permissions_apply_on_children: boolean;
+};
+
 export type Events = {
     "update-status-property": string;
     "update-status-recursion": boolean;
@@ -129,6 +133,7 @@ export type Events = {
     "properties-recursion-list": UpdatePropertyListEvent;
 
     "update-permissions": string;
+    "update-apply-permissions-on-children": UpdateApplyPermissionsOnChildren;
     "update-global-criteria": string;
 
     "update-criteria": UpdateCriteriaEvent;

programming_language: TypeScript