Skip to content
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

[4.x] Fix user groups/roles querying #6131

Merged
merged 27 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c5d8e75
Update user stache store paths to use -> instead of /
ryanmitchell Jun 1, 2022
68be6bc
Add where check for groups and roles
ryanmitchell Jun 1, 2022
3cac8e9
Revert changes
ryanmitchell Jun 2, 2022
cc93f84
Replace slashes with `->` in index names
ryanmitchell Jun 2, 2022
24e32dd
Move changes to users tag
ryanmitchell Jun 2, 2022
9354f5a
StyleCI
ryanmitchell Jun 2, 2022
782f853
Expand logic to roles
ryanmitchell Jun 2, 2022
e3e697a
Update filter scopes to new approach
ryanmitchell Jun 2, 2022
8226c1c
StyleCI
ryanmitchell Jun 2, 2022
6b41e10
Merge branch '3.3' into fix/stache-user-groups-querying
ryanmitchell Jun 21, 2022
59995b9
Update tests
ryanmitchell Jun 21, 2022
6f66ab4
Add whereGroup/whereRole methods
ryanmitchell Jan 19, 2023
13ab34d
Update scopes to use new methods
ryanmitchell Jan 19, 2023
deef901
StyleCI
ryanmitchell Jan 19, 2023
16e2fa6
Bug fix
ryanmitchell Jan 19, 2023
1bf09ac
Use handle instead of name to align with #5686
ryanmitchell Jan 19, 2023
3c0c19c
Merge branch '4.x' into fix/stache-user-groups-querying
jasonvarga Jul 6, 2023
6ff8083
Handle split configs (eloquent users, stache roles/groups)
ryanmitchell Jul 10, 2023
9afc0e1
Fix
ryanmitchell Jul 11, 2023
f740483
Merge branch '4.x' into fix/stache-user-groups-querying
ryanmitchell Oct 19, 2023
2bd5f3a
Add UserQueryBuilder tests
ryanmitchell Oct 19, 2023
8873637
Add Eloquent user tests
ryanmitchell Oct 19, 2023
11d5e96
Merge branch '4.x' into fix/stache-user-groups-querying
ryanmitchell Nov 9, 2023
9158c5b
Merge branch '4.x' into fix/stache-user-groups-querying
jasonvarga Jan 22, 2024
a489b33
add/adjust tests
jasonvarga Jan 23, 2024
2f38605
remove custom relation logic
jasonvarga Jan 23, 2024
be0fcbd
Merge branch '4.x' into fix/stache-user-groups-querying
jasonvarga Jan 23, 2024
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
92 changes: 87 additions & 5 deletions src/Auth/Eloquent/UserQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,107 @@

namespace Statamic\Auth\Eloquent;

use Illuminate\Support\Facades\DB;
use Statamic\Auth\UserCollection;
use Statamic\Facades\User;
use Statamic\Query\EloquentQueryBuilder;

class UserQueryBuilder extends EloquentQueryBuilder
{
public function whereGroup($value, $operator = '=', $boolean = 'and')
{
$method = $boolean == 'or' ? 'orWhereExists' : 'whereExists';
$this->$method(function ($query) use ($operator, $value) {
$query->select(DB::raw(1))
->from($this->groupsTable())
->where('group_id', $operator, $value)
->whereColumn($this->groupsTable().'.user_id', 'users.id');
});

return $this;
}

public function orWhereGroup($value, $operator = '=')
{
$this->whereGroup($value, $operator, 'or');

return $this;
}

public function whereGroupIn($groups, $boolean = 'and')
{
$method = $boolean == 'or' ? 'orWhereExists' : 'whereExists';
$this->$method(function ($query) use ($groups) {
$query->select(DB::raw(1))
->from($this->groupsTable())
->whereIn('group_id', $groups)
->whereColumn($this->groupsTable().'.user_id', 'users.id');
});

return $this;
}

public function orWhereGroupIn($groups)
{
$this->whereGroupIn($groups, 'or');

return $this;
}

public function whereRole($value, $operator = '=', $boolean = 'and')
{
$method = $boolean == 'or' ? 'orWhereExists' : 'whereExists';
$this->$method(function ($query) use ($operator, $value) {
$query->select(DB::raw(1))
->from($this->rolesTable())
->where('role_id', $operator, $value)
->whereColumn($this->rolesTable().'.user_id', 'users.id');
});

return $this;
}

public function orWhereRole($value, $operator = '=')
{
$this->whereRole($value, $operator, 'or');

return $this;
}

public function whereRoleIn($roles, $boolean = 'and')
{
$method = $boolean == 'or' ? 'orWhereExists' : 'whereExists';
$this->$method(function ($query) use ($roles) {
$query->select(DB::raw(1))
->from($this->rolesTable())
->whereIn('role_id', $roles)
->whereColumn($this->rolesTable().'.user_id', 'users.id');
});

return $this;
}

public function orWhereRoleIn($roles)
{
$this->whereRoleIn($roles, 'or');

return $this;
}

protected function transform($items, $columns = ['*'])
{
return UserCollection::make($items)->map(function ($model) {
return User::make()->model($model);
});
}

protected function column($column)
private function groupsTable()
{
if ($column === 'id') {
return User::make()->model()->getKeyName();
}
return config('statamic.users.tables.group_user', 'group_user');
}

return $column;
private function rolesTable()
{
return config('statamic.users.tables.role_user', 'role_user');
}
}
2 changes: 1 addition & 1 deletion src/Query/Scopes/Filters/UserGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function fieldItems()

public function apply($query, $values)
{
$query->where('groups/'.$values['group'], true);
$query->whereGroup($values['group']);
}

public function badge($values)
Expand Down
2 changes: 1 addition & 1 deletion src/Query/Scopes/Filters/UserRole.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function apply($query, $values)
if ($values['role'] === 'super') {
$query->where('super', true);
} else {
$query->where('roles/'.$values['role'], true);
$query->whereRole($values['role']);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Stache/Indexes/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function cacheKey()
{
return vsprintf('stache::indexes::%s::%s', [
$this->store->key(),
str_replace('.', '::', $this->name),
str_replace(['.', '/'], ['::', '->'], $this->name),
]);
}

Expand Down
66 changes: 66 additions & 0 deletions src/Stache/Query/UserQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,72 @@

class UserQueryBuilder extends Builder
{
public function whereGroup($value, $operator = '=', $boolean = 'and')
{
$this->where('groups/'.$value, $operator, true, $boolean);

return $this;
}

public function orWhereGroup($value, $operator = '=')
{
$this->whereGroup($value, $operator, 'or');

return $this;
}

public function whereGroupIn($groups, $boolean = 'and')
{
$method = $boolean == 'or' ? 'orWhere' : 'where';
$this->$method(function ($query) use ($groups) {
foreach ($groups as $group) {
$query->orWhere('groups/'.$group, true);
}
});

return $this;
}

public function orWhereGroupIn($groups)
{
$this->whereGroupIn($groups, 'or');

return $this;
}

public function whereRole($value, $operator = '=', $boolean = 'and')
{
$this->where('roles/'.$value, $operator, true, $boolean);

return $this;
}

public function orWhereRole($value, $operator = '=')
{
$this->whereRole($value, $operator, 'or');

return $this;
}

public function whereRoleIn($roles, $boolean = 'and')
{
$method = $boolean == 'or' ? 'orWhere' : 'where';
$this->$method(function ($query) use ($roles) {
foreach ($roles as $role) {
$query->orWhere('roles/'.$role, true);
}
});

return $this;
}

public function orWhereRoleIn($roles)
{
$this->whereRoleIn($roles, 'or');

return $this;
}

protected function getFilteredKeys()
{
if (! empty($this->wheres)) {
Expand Down
8 changes: 4 additions & 4 deletions src/Tags/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ public function index()
{
$query = $this->query();

if ($group = $this->params->get('group')) {
$query->where('group', $group);
if ($groups = $this->params->explode('group', [])) {
$query->whereGroupIn($groups);
}

if ($role = $this->params->get('role')) {
$query->where('role', $role);
if ($roles = $this->params->explode('role', [])) {
$query->whereRoleIn($roles);
}

return $this->output($this->results($query));
Expand Down
110 changes: 110 additions & 0 deletions tests/Auth/Eloquent/EloquentUserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use Illuminate\Support\Facades\Event;
use Statamic\Auth\Eloquent\User as EloquentUser;
use Statamic\Auth\File\Role;
use Statamic\Auth\File\UserGroup;
use Statamic\Contracts\Auth\Role as RoleContract;
use Statamic\Contracts\Auth\UserGroup as UserGroupContract;
use Statamic\Facades;
use Tests\Auth\PermissibleContractTests;
use Tests\Auth\UserContractTests;
Expand Down Expand Up @@ -124,6 +126,114 @@ public function handle(?string $handle = null)
'c' => 'c',
'd' => 'd',
], $user->roles()->map->handle()->all());

$this->assertSame(Facades\User::query()->whereRole('a')->get()->first()->id(), $user->id());

$userTwo = $this->createPermissible();
$userThree = $this->createPermissible();
$userFour = $this->createPermissible();

\DB::table(config('statamic.users.tables.role_user', 'role_user'))->insert([
['user_id' => $userTwo->id(), 'role_id' => 'a'],
['user_id' => $userThree->id(), 'role_id' => 'b'],
['user_id' => $userFour->id(), 'role_id' => 'c'],
]);

$this->assertCount(2, Facades\User::query()->whereRole('a')->get());
$this->assertCount(2, Facades\User::query()->whereRole('b')->get());
$this->assertCount(2, Facades\User::query()->whereRole('c')->get());
$this->assertCount(1, Facades\User::query()->whereRole('d')->get());

$this->assertSame([$user->email(), $userTwo->email()], Facades\User::query()->whereRole('a')->get()->map->email()->all());
$this->assertSame([$user->email(), $userThree->email()], Facades\User::query()->whereRole('b')->get()->map->email()->all());
$this->assertSame([$user->email()], Facades\User::query()->whereRole('b')->whereRole('c')->get()->map->email()->all());
$this->assertSame([$user->email(), $userThree->email(), $userFour->email()], Facades\User::query()->whereRole('b')->orWhereRole('c')->get()->map->email()->all());

$this->assertSame([$user->email(), $userTwo->email(), $userThree->email()], Facades\User::query()->whereRoleIn(['a', 'b'])->get()->map->email()->all());
$this->assertSame([$user->email(), $userTwo->email(), $userThree->email(), $userFour->email()], Facades\User::query()->whereRoleIn(['a', 'b'])->orWhereRoleIn(['c'])->get()->map->email()->all());
}

/** @test */
public function it_gets_groups_already_in_the_db_without_explicitly_assigning_them()
{
$roleA = new class extends UserGroup
{
public function handle(?string $handle = null)
{
return 'a';
}
};
$roleB = new class extends UserGroup
{
public function handle(?string $handle = null)
{
return 'b';
}
};
$roleC = new class extends UserGroup
{
public function handle(?string $handle = null)
{
return 'c';
}
};
$roleD = new class extends UserGroup
{
public function handle(?string $handle = null)
{
return 'd';
}
};

Facades\UserGroup::shouldReceive('find')->with('a')->andReturn($roleA);
Facades\UserGroup::shouldReceive('find')->with('b')->andReturn($roleB);
Facades\UserGroup::shouldReceive('find')->with('c')->andReturn($roleC);
Facades\UserGroup::shouldReceive('find')->with('d')->andReturn($roleD);
Facades\UserGroup::shouldReceive('find')->with('unknown')->andReturnNull();

$user = $this->createPermissible();

\DB::table(config('statamic.users.tables.group_user', 'group_user'))->insert([
['user_id' => $user->id(), 'group_id' => 'a'],
['user_id' => $user->id(), 'group_id' => 'b'],
['user_id' => $user->id(), 'group_id' => 'c'],
['user_id' => $user->id(), 'group_id' => 'd'],
]);

$this->assertInstanceOf(Collection::class, $user->groups());
$this->assertCount(4, $user->groups());
$this->assertEveryItemIsInstanceOf(UserGroupContract::class, $user->groups());
$this->assertEquals([
'a' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
], $user->groups()->map->handle()->all());

$this->assertSame(Facades\User::query()->whereGroup('a')->get()->first()->id(), $user->id());

$userTwo = $this->createPermissible();
$userThree = $this->createPermissible();
$userFour = $this->createPermissible();

\DB::table(config('statamic.users.tables.group_user', 'group_user'))->insert([
['user_id' => $userTwo->id(), 'group_id' => 'a'],
['user_id' => $userThree->id(), 'group_id' => 'b'],
['user_id' => $userFour->id(), 'group_id' => 'c'],
]);

$this->assertCount(2, Facades\User::query()->whereGroup('a')->get());
$this->assertCount(2, Facades\User::query()->whereGroup('b')->get());
$this->assertCount(2, Facades\User::query()->whereGroup('c')->get());
$this->assertCount(1, Facades\User::query()->whereGroup('d')->get());

$this->assertSame([$user->email(), $userTwo->email()], Facades\User::query()->whereGroup('a')->get()->map->email()->all());
$this->assertSame([$user->email(), $userThree->email()], Facades\User::query()->whereGroup('b')->get()->map->email()->all());
$this->assertSame([$user->email()], Facades\User::query()->whereGroup('b')->whereGroup('c')->get()->map->email()->all());
$this->assertSame([$user->email(), $userThree->email(), $userFour->email()], Facades\User::query()->whereGroup('b')->orWhereGroup('c')->get()->map->email()->all());

$this->assertSame([$user->email(), $userTwo->email(), $userThree->email()], Facades\User::query()->whereGroupIn(['a', 'b'])->get()->map->email()->all());
$this->assertSame([$user->email(), $userTwo->email(), $userThree->email(), $userFour->email()], Facades\User::query()->whereGroupIn(['a', 'b'])->orWhereGroupIn(['c'])->get()->map->email()->all());
}

public function makeUser()
Expand Down
1 change: 1 addition & 0 deletions tests/Auth/FileUserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public function it_gets_permissions_from_a_cache()
$userGroup->shouldReceive('roles')->once()->andReturn(collect([$userGroupRole]));

Role::shouldReceive('find')->with('direct')->andReturn($directRole);
Role::shouldReceive('all')->andReturn(collect([$directRole])); // the stache calls this when getting a user. unrelated to test.
UserGroup::shouldReceive('find')->with('usergroup')->andReturn($userGroup);
Role::shouldReceive('all')->andReturn(collect([$directRole])); // the stache calls this when getting a user. unrelated to test.
UserGroup::shouldReceive('all')->andReturn(collect([$userGroup])); // the stache calls this when getting a user. unrelated to test.
Expand Down
2 changes: 2 additions & 0 deletions tests/Auth/PermissibleContractTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public function it_gets_and_checks_permissions()
$userGroup = (new UserGroup)->handle('usergroup')->assignRole($userGroupRole);

RoleAPI::shouldReceive('find')->with('direct')->andReturn($directRole);
RoleAPI::shouldReceive('all')->andReturn(collect([$directRole])); // the stache calls this when getting a user. unrelated to test.
UserGroupAPI::shouldReceive('find')->with('usergroup')->andReturn($userGroup);
RoleAPI::shouldReceive('all')->andReturn(collect([$directRole])); // the stache calls this when getting a user. unrelated to test.
UserGroupAPI::shouldReceive('all')->andReturn(collect([$userGroup])); // the stache calls this when getting a user. unrelated to test.
Expand Down Expand Up @@ -257,6 +258,7 @@ public function permissions($permissions = null)

RoleAPI::shouldReceive('find')->with('superrole')->andReturn($superRole);
RoleAPI::shouldReceive('find')->with('nonsuperrole')->andReturn($nonSuperRole);
RoleAPI::shouldReceive('all')->andReturn(collect([$superRole, $nonSuperRole])); // the stache calls this when getting a user. unrelated to test.
UserGroupAPI::shouldReceive('find')->with('supergroup')->andReturn($superGroup);
UserGroupAPI::shouldReceive('find')->with('nonsupergroup')->andReturn($nonSuperGroup);
RoleAPI::shouldReceive('all')->andReturn(collect([$superRole, $nonSuperRole])); // the stache calls this when getting a user. unrelated to test.
Expand Down
Loading
Loading