Skip to content

Commit 354286d

Browse files
imorlandStyleCIBot
andauthored
[2.x] fix: scope UserResource groups relationship to viewable groups (#4629)
* fix: scope UserResource groups relationship to viewable groups The groups ToMany relationship on UserResource resolved against User::groups() unfiltered, so relationships.groups.data leaked hidden group IDs to actors without viewHiddenGroups — even though GroupResource::scope() filtered out the hidden groups themselves from the included resources. Add a get() callback that returns User::visibleGroups() for actors without viewHiddenGroups, mirroring the visibility check used by ScopeGroupVisibility. Fixes #4618 * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot <bot@styleci.io>
1 parent 03adb0f commit 354286d

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

framework/core/src/Api/Resource/UserResource.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ public function fields(): array
310310
}),
311311

312312
Schema\Relationship\ToMany::make('groups')
313+
->get(function (User $user, Context $context) {
314+
return $context->getActor()->can('viewHiddenGroups')
315+
? $user->groups()->get()->all()
316+
: $user->visibleGroups()->get()->all();
317+
})
313318
->writable(fn (User $user, Context $context) => $context->updating() && $context->getActor()->can('editGroups', $user))
314319
->includable()
315320
->set(function (User $user, $value, Context $context) {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Flarum.
5+
*
6+
* For detailed copyright and license information, please view the
7+
* LICENSE file that was distributed with this source code.
8+
*/
9+
10+
namespace Flarum\Tests\integration\api\users;
11+
12+
use Flarum\Group\Group;
13+
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
14+
use Flarum\Testing\integration\TestCase;
15+
use Flarum\User\User;
16+
use Illuminate\Support\Arr;
17+
use PHPUnit\Framework\Attributes\Test;
18+
19+
class ShowGroupsRelationshipTest extends TestCase
20+
{
21+
use RetrievesAuthorizedUsers;
22+
23+
/**
24+
* @inheritDoc
25+
*/
26+
protected function setUp(): void
27+
{
28+
parent::setUp();
29+
30+
$this->prepareDatabase([
31+
User::class => [
32+
$this->normalUser(),
33+
],
34+
Group::class => [
35+
[
36+
'id' => 10,
37+
'name_singular' => 'Public',
38+
'name_plural' => 'Public',
39+
'is_hidden' => 0,
40+
],
41+
[
42+
'id' => 11,
43+
'name_singular' => 'Hidden',
44+
'name_plural' => 'Hidden',
45+
'is_hidden' => 1,
46+
],
47+
],
48+
'group_user' => [
49+
['user_id' => 2, 'group_id' => 10],
50+
['user_id' => 2, 'group_id' => 11],
51+
],
52+
]);
53+
}
54+
55+
private function groupRelationshipIds(array $body): array
56+
{
57+
return Arr::pluck($body['data']['relationships']['groups']['data'] ?? [], 'id');
58+
}
59+
60+
private function includedGroupIds(array $body): array
61+
{
62+
$included = $body['included'] ?? [];
63+
64+
$groups = array_filter($included, fn (array $resource) => ($resource['type'] ?? null) === 'groups');
65+
66+
return array_values(Arr::pluck($groups, 'id'));
67+
}
68+
69+
#[Test]
70+
public function guest_does_not_see_hidden_groups_in_user_groups_relationship()
71+
{
72+
$response = $this->send($this->request('GET', '/api/users/2'));
73+
74+
$this->assertEquals(200, $response->getStatusCode());
75+
$body = json_decode($response->getBody()->getContents(), true);
76+
77+
$this->assertEqualsCanonicalizing(['10'], $this->groupRelationshipIds($body));
78+
$this->assertNotContains('11', $this->includedGroupIds($body));
79+
}
80+
81+
#[Test]
82+
public function normal_user_does_not_see_hidden_groups_in_user_groups_relationship()
83+
{
84+
$response = $this->send(
85+
$this->request('GET', '/api/users/2', [
86+
'authenticatedAs' => 2,
87+
])
88+
);
89+
90+
$this->assertEquals(200, $response->getStatusCode());
91+
$body = json_decode($response->getBody()->getContents(), true);
92+
93+
$this->assertEqualsCanonicalizing(['10'], $this->groupRelationshipIds($body));
94+
$this->assertNotContains('11', $this->includedGroupIds($body));
95+
}
96+
97+
#[Test]
98+
public function admin_sees_hidden_groups_in_user_groups_relationship()
99+
{
100+
$response = $this->send(
101+
$this->request('GET', '/api/users/2', [
102+
'authenticatedAs' => 1,
103+
])
104+
);
105+
106+
$this->assertEquals(200, $response->getStatusCode());
107+
$body = json_decode($response->getBody()->getContents(), true);
108+
109+
$this->assertEqualsCanonicalizing(['10', '11'], $this->groupRelationshipIds($body));
110+
$this->assertEqualsCanonicalizing(['10', '11'], $this->includedGroupIds($body));
111+
}
112+
}

0 commit comments

Comments
 (0)