-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Check user ownership of groups parties cats #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Code Review Summary✨ The Pull Request significantly improves the security posture of the 🚀 Key Improvements
💡 Minor Suggestions
🚨 Critical Issues
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review complete. See the overview comment for a summary.
if (isset($data['categories'])) { | ||
$categories = $data['categories']; | ||
unset($data['categories']); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introduce a new private helper method authorizeTransactionOwnership
to centralize and reuse the ownership validation logic for groups, parties, and categories. This method will take the validated data and categories array as arguments, improving modularity and preventing code duplication across controller actions.
} | |
private function authorizeTransactionOwnership(array $data, array $categories = []): void | |
{ | |
/** @var \App\Models\User $user */ | |
$user = auth()->user(); | |
// check for group ownership | |
if (isset($data['group_id']) && $data['group_id'] && !$user->groups()->where('id', $data['group_id'])->exists()) { | |
throw new HttpException(400, 'The selected group does not belong to user'); | |
} | |
// check for party ownership | |
if (isset($data['party_id']) && $data['party_id'] && !$user->parties()->where('id', $data['party_id'])->exists()) { | |
throw new HttpException(400, 'The selected party does not belong to user'); | |
} | |
// check for categories ownership | |
if (!empty($categories)) { | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); | |
$invalid_categories = array_diff($categories, $user_category_ids); | |
if (!empty($invalid_categories)) { | |
throw new HttpException(400, 'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} | |
} | |
} |
$user = auth()->user(); | ||
|
||
//check for group | ||
if (isset($data['group_id']) && $data['group_id']){ | ||
if (!$user->groups()->where('id', $data['group_id'])->exists()){ | ||
throw new HttpException(400,'The selected group does not belong to user'); | ||
} | ||
} | ||
//check for party | ||
if (isset($data['party_id']) && $data['party_id']) { | ||
if (!$user->parties()->where('id', $data['party_id'])->exists()){ | ||
throw new HttpException(400,'The selected party does not belong to user'); | ||
} | ||
} | ||
|
||
//check for categories | ||
if (!empty($categories)){ | ||
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | ||
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | ||
if (!empty($invalid_categories)){ | ||
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These ownership checks are duplicated in both the store
and update
methods. Extracting this logic into a private helper method will significantly improve maintainability, reduce redundancy, and make your controller actions cleaner. Consider creating a private function authorizeTransactionOwnership(array $data, array $categories = []): void
.
$user = auth()->user(); | |
//check for group | |
if (isset($data['group_id']) && $data['group_id']){ | |
if (!$user->groups()->where('id', $data['group_id'])->exists()){ | |
throw new HttpException(400,'The selected group does not belong to user'); | |
} | |
} | |
//check for party | |
if (isset($data['party_id']) && $data['party_id']) { | |
if (!$user->parties()->where('id', $data['party_id'])->exists()){ | |
throw new HttpException(400,'The selected party does not belong to user'); | |
} | |
} | |
//check for categories | |
if (!empty($categories)){ | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | |
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | |
if (!empty($invalid_categories)){ | |
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} | |
$this->authorizeTransactionOwnership($data, $categories); |
$user = auth()->user(); | ||
|
||
//check for group | ||
if (isset($validatedData['group_id']) && $validatedData['group_id']){ | ||
if (!$user->groups()->where('id', $validatedData['group_id'])->exists()){ | ||
throw new HttpException(400,'The selected group does not belong to user'); | ||
|
||
} | ||
} | ||
//check for party | ||
if (isset($validatedData['party_id']) && $validatedData['party_id']) { | ||
if (!$user->parties()->where('id', $validatedData['party_id'])->exists()){ | ||
throw new HttpException(400,'The selected party does not belong to user'); | ||
} | ||
} | ||
|
||
//check for categories | ||
if (!empty($categories)){ | ||
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | ||
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | ||
if (!empty($invalid_categories)){ | ||
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block of ownership checks is duplicated from the store
method, indicating a need for refactoring into a shared private method. Additionally, the $categories
variable is not defined within the scope of the update
method's ownership check, leading to a PHP error if categories are present in the request. The refactored helper method should explicitly accept the categories from $validatedData
to correct this bug.
$user = auth()->user(); | |
//check for group | |
if (isset($validatedData['group_id']) && $validatedData['group_id']){ | |
if (!$user->groups()->where('id', $validatedData['group_id'])->exists()){ | |
throw new HttpException(400,'The selected group does not belong to user'); | |
} | |
} | |
//check for party | |
if (isset($validatedData['party_id']) && $validatedData['party_id']) { | |
if (!$user->parties()->where('id', $validatedData['party_id'])->exists()){ | |
throw new HttpException(400,'The selected party does not belong to user'); | |
} | |
} | |
//check for categories | |
if (!empty($categories)){ | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | |
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | |
if (!empty($invalid_categories)){ | |
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} | |
$this->authorizeTransactionOwnership($validatedData, $validatedData['categories'] ?? []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review complete. See the overview comment for a summary.
if (! empty($categories)) { | ||
$user_category_ids = $user->categories()->pluck('id')->toArray(); // get user category ids | ||
$invalid_categories = array_diff($categories, $user_category_ids); // find any ids not owned by user | ||
if (! empty($invalid_categories)) { | ||
throw new HttpException(400, 'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The update
method's ownership check for categories uses an undefined $categories
variable. This will lead to a PHP Undefined variable
error if categories are provided in the request. It should directly use $validatedData['categories']
as store
method uses $data['categories']
(after assigning it to a local $categories
variable). Making this change simplifies the logic and directly uses the validated input.
if (! empty($categories)) { | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); // get user category ids | |
$invalid_categories = array_diff($categories, $user_category_ids); // find any ids not owned by user | |
if (! empty($invalid_categories)) { | |
throw new HttpException(400, 'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} | |
if (isset($validatedData['categories']) && ! empty($validatedData['categories'])) { | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); | |
$invalid_categories = array_diff($validatedData['categories'], $user_category_ids); | |
if (! empty($invalid_categories)) { | |
throw new HttpException(400, 'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use $this->success()
and $this->failure()
for success and error messages respectively. Do not throw and HttpException except you want to write a try-catch to catch the exception and use the functions above to return the appriate message to the user.
@kofimokome Someone was already working on this ? |
Yes, I told her to work on it so that she gets familiar with the code. Also the guy hasn't responded to our feedback. If Mercy's work solves the issue and the other guy takes forever to reply, I would merge hers. For now I will leave both open. @iMercyvlogs split this into two pull requests. One for #99 and the other for #100. |
If I split them, the pull request with the checks will always fail the run
tests 🙏🏾.
…On Fri, 10 Oct 2025 at 10:19 Kofi Mokome ***@***.***> wrote:
*kofimokome* left a comment (trakli/webservice#109)
<#109 (comment)>
Yes, I told her to work on it so that she gets familiar with the code.
Also the guy hasn't responded to our feedback.
@iMercyvlogs <https://github.com/iMercyvlogs> split this into two pull
requests. One for #99 <#99>
and the other for #100 <#100>.
—
Reply to this email directly, view it on GitHub
<#109 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/A2PBDJBQ2LW26EH4EJCBJZD3W5225AVCNFSM6AAAAACIVT4PJ2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTGOBZGAZTCMZUGU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Split it, I will merge the other pull request first if it's okay |
2659af5
to
d3f93c3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review complete. See the overview comment for a summary.
// If an HttpException was thrown earlier (e.g., from ownership check), re-throw it with the correct status code. | ||
if ($e instanceof HttpException) { | ||
return $this->failure($e->getMessage(), $e->getStatusCode()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The update
method's catch
block does not correctly handle HttpException
s thrown by the newly added ownership checks. This means that a user-facing 400 error would be converted into a generic 500 error, obscuring the actual problem. This change aligns the error handling with the store
method, ensuring appropriate HTTP status codes are returned.
} | |
// If an HttpException was thrown earlier (e.g., from ownership check), re-throw it with the correct status code. | |
if ($e instanceof HttpException) { | |
return $this->failure($e->getMessage(), $e->getStatusCode()); | |
} | |
return $this->failure('Failed to update transaction', 500, [$e->getMessage()]); |
//ownership checks for authenticated user | ||
$user = auth()->user(); | ||
|
||
//check for group | ||
if (isset($data['group_id']) && $data['group_id']){ | ||
if (!$user->groups()->where('id', $data['group_id'])->exists()){ | ||
throw new HttpException(400,'The selected group does not belong to user'); | ||
} | ||
} | ||
//check for party | ||
if (isset($data['party_id']) && $data['party_id']) { | ||
if (!$user->parties()->where('id', $data['party_id'])->exists()){ | ||
throw new HttpException(400,'The selected party does not belong to user'); | ||
} | ||
} | ||
|
||
//check for categories | ||
if (!empty($categories)){ | ||
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | ||
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | ||
if (!empty($invalid_categories)){ | ||
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ownership checks for groups, parties, and categories are duplicated across the store
and update
methods. This is a prime candidate for refactoring. Extracting this logic into a private helper method will improve maintainability, reduce redundancy, and make the code cleaner. This suggestion replaces the duplicated block with a call to the new validateResourceOwnership
method.
//ownership checks for authenticated user | |
$user = auth()->user(); | |
//check for group | |
if (isset($data['group_id']) && $data['group_id']){ | |
if (!$user->groups()->where('id', $data['group_id'])->exists()){ | |
throw new HttpException(400,'The selected group does not belong to user'); | |
} | |
} | |
//check for party | |
if (isset($data['party_id']) && $data['party_id']) { | |
if (!$user->parties()->where('id', $data['party_id'])->exists()){ | |
throw new HttpException(400,'The selected party does not belong to user'); | |
} | |
} | |
//check for categories | |
if (!empty($categories)){ | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | |
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | |
if (!empty($invalid_categories)){ | |
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} | |
$user = auth()->user(); | |
$this->validateResourceOwnership($user, $data, $categories); |
$user = auth()->user(); | ||
|
||
//check for group | ||
if (isset($validatedData['group_id']) && $validatedData['group_id']){ | ||
if (!$user->groups()->where('id', $validatedData['group_id'])->exists()){ | ||
throw new HttpException(400,'The selected group does not belong to user'); | ||
|
||
} | ||
} | ||
//check for party | ||
if (isset($validatedData['party_id']) && $validatedData['party_id']) { | ||
if (!$user->parties()->where('id', $validatedData['party_id'])->exists()){ | ||
throw new HttpException(400,'The selected party does not belong to user'); | ||
} | ||
} | ||
|
||
//check for categories | ||
if (!empty($categories)){ | ||
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | ||
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | ||
if (!empty($invalid_categories)){ | ||
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the store
method, the ownership checks here are duplicated logic. Refactor this into a private helper method for better maintainability. Additionally, the original implementation of this block would have led to a bug where category ownership checks are skipped due to the $categories
variable being uninitialized in this scope. The suggested code properly initializes $inputCategories
for the check.
$user = auth()->user(); | |
//check for group | |
if (isset($validatedData['group_id']) && $validatedData['group_id']){ | |
if (!$user->groups()->where('id', $validatedData['group_id'])->exists()){ | |
throw new HttpException(400,'The selected group does not belong to user'); | |
} | |
} | |
//check for party | |
if (isset($validatedData['party_id']) && $validatedData['party_id']) { | |
if (!$user->parties()->where('id', $validatedData['party_id'])->exists()){ | |
throw new HttpException(400,'The selected party does not belong to user'); | |
} | |
} | |
//check for categories | |
if (!empty($categories)){ | |
$user_category_ids = $user->categories()->pluck('id')->toArray(); //get user category ids | |
$invalid_categories = array_diff($categories, $user_category_ids); //find any ids not owned by user | |
if (!empty($invalid_categories)){ | |
throw new HttpException(400,'Some of the selected categories do not belong to user. Invalid category IDs: '.implode(',', $invalid_categories)); | |
} | |
} | |
$user = auth()->user(); | |
$inputCategories = $validatedData['categories'] ?? []; | |
$this->validateResourceOwnership($user, $validatedData, $inputCategories); |
Checks the ownership of each category,party and group before saving.