Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 21 additions & 23 deletions resources/js/common/reassignMixin.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getReassignUsers as getReassignUsersApi } from "../tasks/api";

export default {
data() {
return {
Expand All @@ -21,32 +23,28 @@ export default {
this.allowReassignment = response.data[this.task.id];
});
},
getReassignUsers(filter = null) {
const params = { };
if (filter) {
params.filter = filter;
}
if (this.task?.id) {
params.assignable_for_task_id = this.task.id;
// The variables are needed to calculate the rule expression.
if (this?.formData) {
params.form_data = this.formData;
delete params.form_data._user;
delete params.form_data._request;
delete params.form_data._process;
}
}
async getReassignUsers(filter = null) {
try {
const response = await getReassignUsersApi(
filter,
this.task?.id,
this?.formData,
this.currentTaskUserId
);

ProcessMaker.apiClient.post('users_task_count', params ).then(response => {
this.reassignUsers = [];
response.data.data.forEach((user) => {
this.reassignUsers.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count
if (response?.data) {
response.data.forEach((user) => {
this.reassignUsers.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count
});
});
});
});
}
} catch (error) {
console.error('Error loading reassign users:', error);
}
},
onReassignInput: _.debounce(function (filter) {
this.getReassignUsers(filter);
Expand Down
41 changes: 39 additions & 2 deletions resources/js/tasks/api/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
import { getApi } from "../variables/index";

export const getReassignUsers = async (filter = null, taskId = null, currentTaskUserId = null) => {
/**
* Get reassign users using POST with form_data (for rule expression evaluation)
* This replaces the obsolete GET method with the advanced POST logic from reassignMixin
*
* @param {string|null} filter - Filter string to search users
* @param {number|null} taskId - Task ID to get assignable users for
* @param {Object|null} formData - Form data needed to calculate rule expressions
* @param {number|null} currentTaskUserId - User ID to exclude from results (matches: task?.user_id ?? task?.user?.id)
* @returns {Promise<Object>} Response data with users array
*/
export const getReassignUsers = async (
filter = null,
taskId = null,
formData = null,
currentTaskUserId = null
) => {
const api = getApi();
const response = await api.get("users_task_count", { params: { filter, assignable_for_task_id: taskId, include_current_user: true } });
const params = {};

if (filter) {
params.filter = filter;
}

if (taskId) {
params.assignable_for_task_id = taskId;

// The variables are needed to calculate the rule expression.
if (formData) {
params.form_data = { ...formData };
// Remove internal variables
delete params.form_data._user;
delete params.form_data._request;
delete params.form_data._process;
}
}

const response = await api.post("users_task_count", params);
const data = response.data;

// Filter out current user to prevent self-reassignment (matches mixin logic)
if (currentTaskUserId && Array.isArray(data?.data)) {
data.data = data.data.filter((user) => user.id !== currentTaskUserId);
}

return data;
};

Expand Down
3 changes: 2 additions & 1 deletion resources/js/tasks/components/TasksPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@
<TaskPreviewAssignment
v-if="showReassignment"
:task="task"
:form-data="formData"
:current-task-user-id="currentTaskUserId"
@on-cancel-reassign="showReassignment = false"
@on-reassign-user="e=> reassignUser(e,false)"
/>
Expand Down Expand Up @@ -413,7 +415,6 @@ export default {
},
openReassignment() {
this.showReassignment = !this.showReassignment;
this.getReassignUsers();
},
getTaskDefinitionForReassignmentPermission() {
ProcessMaker.apiClient
Expand Down
41 changes: 30 additions & 11 deletions resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ const props = defineProps({
type: Object,
required: true,
},
formData: {
type: Object,
default: null,
},
currentTaskUserId: {
type: Number,
default: null,
},
});

const emit = defineEmits(["on-reassign-user"]);
Expand All @@ -80,18 +88,29 @@ const disabledAssign = ref(false);
// Computed properties
const disabled = computed(() => !selectedUser.value || !comments.value?.trim());

// Load the reassign users
// Load the reassign users using the centralized function with form_data
const loadReassignUsers = async (filter) => {
const response = await getReassignUsers(filter, props.task.id, props.task.user_id);

reassignUsers.value = [];
response.data.forEach((user) => {
reassignUsers.value.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count,
});
});
try {
const response = await getReassignUsers(
filter,
props.task?.id,
props.formData,
props.currentTaskUserId
);

reassignUsers.value = [];
if (response?.data) {
response.data.forEach((user) => {
reassignUsers.value.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count,
});
});
}
} catch (error) {
console.error('Error loading reassign users:', error);
}
};

/**
Expand Down
6 changes: 2 additions & 4 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,8 @@
Route::delete('users/{user}', [UserController::class, 'destroy'])->name('users.destroy')->middleware('can:delete-users');
Route::put('password/change', [ChangePasswordController::class, 'update'])->name('password.update');
Route::put('users/update_language', [UserController::class, 'updateLanguage'])->name('users.updateLanguage');
Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count')
->middleware('can:view-users');
Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post')
->middleware('can:view-users');
Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count');
Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed authorization allows any user to enumerate all users

The can:view-users middleware was removed from both GET and POST routes for users_task_count. The controller method getUsersTaskCount has no internal authorization check, so any authenticated user can now enumerate all active users in the system along with their usernames, names, and task counts. Without the assignable_for_task_id parameter, the endpoint returns all users, enabling information disclosure. Consider adding a more targeted permission check that validates the user's ability to reassign the specific task rather than removing authorization entirely.

Fix in Cursor Fix in Web


// User Groups
Route::put('users/{user}/groups', [UserController::class, 'updateGroups'])->name('users.groups.update')->middleware('can:edit-users');
Expand Down
Loading