Scope project user payloads to visible users #42
@@ -59,6 +59,7 @@ class BuildProjectBacklogPayload
|
|||||||
)->resolve(),
|
)->resolve(),
|
||||||
'users' => UserResource::collection(
|
'users' => UserResource::collection(
|
||||||
User::query()
|
User::query()
|
||||||
|
->visibleToProject($project)
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get(),
|
->get(),
|
||||||
)->resolve(),
|
)->resolve(),
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class BuildProjectTicketDetailPayload
|
|||||||
'ticketTypes' => TicketTypeResource::collection($project->ticketTypes()->get())->resolve(),
|
'ticketTypes' => TicketTypeResource::collection($project->ticketTypes()->get())->resolve(),
|
||||||
'users' => UserResource::collection(
|
'users' => UserResource::collection(
|
||||||
User::query()
|
User::query()
|
||||||
|
->visibleToProject($project)
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get(),
|
->get(),
|
||||||
)->resolve(),
|
)->resolve(),
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use App\Actions\Permissions\PermissionService;
|
||||||
|
use App\Enums\Permission;
|
||||||
use Database\Factories\UserFactory;
|
use Database\Factories\UserFactory;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@@ -69,4 +72,22 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Ticket::class, 'author_user_id');
|
return $this->hasMany(Ticket::class, 'author_user_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function scopeVisibleToProject(Builder $query, Project $project): Builder
|
||||||
|
{
|
||||||
|
$permissionService = app(PermissionService::class);
|
||||||
|
|
||||||
|
$allowedUserIds = User::query()
|
||||||
|
->select('id')
|
||||||
|
->cursor()
|
||||||
|
->filter(fn (User $candidate) => $permissionService->allows(
|
||||||
|
$candidate,
|
||||||
|
Permission::ProjectRead,
|
||||||
|
$project
|
||||||
|
))
|
||||||
|
->pluck('id')
|
||||||
|
->all();
|
||||||
|
|
||||||
|
return $query->whereKey($allowedUserIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace Tests\Feature\Api;
|
|||||||
|
|
||||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
|
use App\Models\Team;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
@@ -149,6 +150,45 @@ class SettingsApiTest extends TestCase
|
|||||||
->assertNoContent();
|
->assertNoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_project_payloads_only_return_users_who_can_view_the_project(): void
|
||||||
|
{
|
||||||
|
[$user, $project] = $this->projectContext();
|
||||||
|
$viewer = User::factory()->create(['name' => 'Visible User']);
|
||||||
|
User::factory()->create(['name' => 'Hidden User']);
|
||||||
|
$teamViewer = User::factory()->create(['name' => 'Team Viewer']);
|
||||||
|
$team = Team::factory()->create();
|
||||||
|
|
||||||
|
$this->syncProjectPermissions($project, $viewer, ['view']);
|
||||||
|
$team->users()->attach($teamViewer);
|
||||||
|
$this->syncProjectPermissions($project, $team, ['view']);
|
||||||
|
|
||||||
|
$ticket = $project->tickets()->firstOrCreate([
|
||||||
|
'sequence_number' => 1,
|
||||||
|
], [
|
||||||
|
'ticket_key' => 'CORE-1',
|
||||||
|
'ticket_priority_id' => $project->ticketPriorities()->firstOrFail()->id,
|
||||||
|
'ticket_status_id' => $project->ticketStatuses()->firstOrFail()->id,
|
||||||
|
'ticket_type_id' => $project->ticketTypes()->where('is_parent', false)->firstOrFail()->id,
|
||||||
|
'title' => 'Scoped users ticket',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->getJson(route('api.projects.backlog', $project))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.users.0.name', $user->name)
|
||||||
|
->assertJsonPath('data.users.1.name', 'Team Viewer')
|
||||||
|
->assertJsonPath('data.users.2.name', 'Visible User')
|
||||||
|
->assertJsonCount(3, 'data.users');
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->getJson(route('api.projects.tickets.show', [$project, $ticket]))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.users.0.name', $user->name)
|
||||||
|
->assertJsonPath('data.users.1.name', 'Team Viewer')
|
||||||
|
->assertJsonPath('data.users.2.name', 'Visible User')
|
||||||
|
->assertJsonCount(3, 'data.users');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_project_repositories_and_user_git_connections_are_available(): void
|
public function test_project_repositories_and_user_git_connections_are_available(): void
|
||||||
{
|
{
|
||||||
Http::preventStrayRequests();
|
Http::preventStrayRequests();
|
||||||
|
|||||||
Reference in New Issue
Block a user