permissions refactoring

This commit is contained in:
2026-04-12 19:21:04 +02:00
parent 51014395a2
commit 9dcdfb981a
64 changed files with 1842 additions and 656 deletions
@@ -2,8 +2,9 @@
namespace App\Http\Controllers\Admin;
use App\Actions\Permissions\PermissionService;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UpdateUserAdminStatusRequest;
use App\Http\Requests\Admin\SyncUserGlobalPermissionsRequest;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
@@ -12,28 +13,30 @@ use Inertia\Response;
class UserManagementController extends Controller
{
public function __construct(
private readonly PermissionService $permissionService,
) {}
public function index(): Response
{
return Inertia::render('admin/users/Index', [
'directPermissions' => $this->permissionService->assignmentMap(User::class),
'permissionOptions' => $this->permissionService->permissionOptions(User::class, null),
'status' => session('status'),
'users' => User::query()
->orderByDesc('is_admin')
->orderBy('name')
->get(),
]);
}
public function update(UpdateUserAdminStatusRequest $request, User $user): RedirectResponse
public function update(SyncUserGlobalPermissionsRequest $request, User $user): RedirectResponse
{
$validated = $request->validated();
$user->forceFill([
'is_admin' => Arr::get($validated, 'is_admin'),
])->save();
$permissions = Arr::get($validated, 'permissions', []);
$status = $user->is_admin
? "{$user->name} now has admin access."
: "{$user->name} no longer has admin access.";
$this->permissionService->sync($user, null, $permissions);
return to_route('admin.users.index')->with('status', $status);
return to_route('admin.users.index')
->with('status', "{$user->name} global permissions were updated.");
}
}
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Actions\Tickets\CreateProjectTicket;
use App\Enums\Permission;
use App\Http\Requests\StoreProjectTicketRequest;
use App\Models\Project;
use App\Models\Ticket;
@@ -16,7 +17,7 @@ class ProjectBacklogController extends Controller
{
public function __construct(
private readonly CreateProjectTicket $createProjectTicket,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function index(Request $request, Project $project): Response
@@ -24,14 +25,14 @@ class ProjectBacklogController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanViewProject($user, $project),
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return Inertia::render('projects/Backlog', [
'boardColumns' => $project->boardColumns()->get(),
'canEditTickets' => $this->projectPermissionService->userHasPermission($user, $project, 'edit'),
'canCreateTickets' => $this->projectPermissionService->userHasPermission($user, $project, 'create'),
'canEditTickets' => $this->permissionService->allows($user, Permission::ProjectWrite, $project),
'canCreateTickets' => $this->permissionService->allows($user, Permission::ProjectWrite, $project),
'project' => $project,
'status' => session('status'),
'ticketPriorities' => $project->ticketPriorities()->get(),
@@ -51,7 +52,7 @@ class ProjectBacklogController extends Controller
$validated = $request->validated();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'create'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Actions\Tickets\CreateTicketWorklogEntry;
use App\Enums\Permission;
use App\Http\Requests\MoveProjectTicketRequest;
use App\Models\Project;
use App\Models\Ticket;
@@ -17,7 +18,7 @@ class ProjectBoardController extends Controller
{
public function __construct(
private readonly CreateTicketWorklogEntry $createTicketWorklogEntry,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function index(Request $request, Project $project): Response
@@ -25,7 +26,7 @@ class ProjectBoardController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanViewProject($user, $project),
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
@@ -36,7 +37,7 @@ class ProjectBoardController extends Controller
->with(['ticketPriority', 'ticketType']),
])
->get(),
'canEditTickets' => $this->projectPermissionService->userHasPermission($user, $project, 'edit'),
'canEditTickets' => $this->permissionService->allows($user, Permission::ProjectWrite, $project),
'project' => $project,
'status' => session('status'),
]);
@@ -50,7 +51,7 @@ class ProjectBoardController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'edit'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -2,7 +2,8 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Enums\Permission;
use App\Http\Requests\StoreBoardColumnRequest;
use App\Http\Requests\UpdateBoardColumnRequest;
use App\Models\BoardColumn;
@@ -18,13 +19,13 @@ use Inertia\Response;
class ProjectBoardSettingsController extends Controller
{
public function __construct(
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function show(Request $request, Project $project): Response
{
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
@@ -40,7 +41,7 @@ class ProjectBoardSettingsController extends Controller
Project $project,
): RedirectResponse {
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
$validated = $request->validated();
@@ -68,7 +69,7 @@ class ProjectBoardSettingsController extends Controller
BoardColumn $boardColumn,
): RedirectResponse {
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
$validated = $request->validated();
@@ -91,7 +92,7 @@ class ProjectBoardSettingsController extends Controller
BoardColumn $boardColumn,
): RedirectResponse {
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
+23 -16
View File
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
use App\Enums\Permission;
use App\Http\Requests\DeleteProjectRequest;
use App\Http\Requests\StoreProjectRequest;
use App\Http\Requests\SyncProjectPermissionsRequest;
@@ -20,7 +21,7 @@ class ProjectController extends Controller
{
public function __construct(
private readonly InitializeProjectTicketMetadata $initializeProjectTicketMetadata,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function index(Request $request): Response
@@ -30,12 +31,13 @@ class ProjectController extends Controller
$projectQuery = Project::query()
->orderByDesc('id');
if (! $user->is_admin) {
$viewableProjectIds = $this->projectPermissionService->viewableProjectIds($user);
if (! $this->permissionService->allows($user, Permission::GlobalAdmin)) {
$viewableProjectIds = $this->permissionService->viewableProjectIds($user);
if ($viewableProjectIds === []) {
return Inertia::render('projects/Index', [
'projects' => [],
'status' => session('status'),
]);
}
@@ -49,8 +51,13 @@ class ProjectController extends Controller
]);
}
public function create(): Response
public function create(Request $request): Response
{
abort_unless(
$this->permissionService->allows($request->user(), Permission::ProjectCreate),
403,
);
return Inertia::render('projects/Create');
}
@@ -58,7 +65,7 @@ class ProjectController extends Controller
{
$project = Project::query()->create($request->validated());
$this->initializeProjectTicketMetadata->handle($project);
$this->projectPermissionService->grantAllProjectPermissions($project, $request->user());
$this->permissionService->grant($request->user(), Permission::ProjectAdmin, $project);
return to_route('projects.show', $project);
}
@@ -68,12 +75,12 @@ class ProjectController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanViewProject($user, $project),
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return Inertia::render('projects/Show', [
'canManageAccess' => $this->projectPermissionService->userCanManageProjectAccess($user, $project),
'canManageAccess' => $this->permissionService->allows($user, Permission::ProjectAdmin, $project),
'project' => $project,
]);
}
@@ -90,16 +97,16 @@ class ProjectController extends Controller
{
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($user, $project),
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
return Inertia::render('projects/Access', [
'directPermissions' => $this->projectPermissionService->userPermissionMap($project),
'permissionOptions' => $this->projectPermissionService->permissionOptions(),
'directPermissions' => $this->permissionService->assignmentMap(User::class, $project),
'permissionOptions' => $this->permissionService->permissionOptions(User::class, Project::class),
'project' => $project,
'status' => session('status'),
'teamPermissions' => $this->projectPermissionService->teamPermissionMap($project),
'teamPermissions' => $this->permissionService->assignmentMap(Team::class, $project),
'teams' => Team::query()
->with('users')
->orderBy('name')
@@ -125,14 +132,14 @@ class ProjectController extends Controller
): RedirectResponse {
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
$validated = $request->validated();
$permissions = Arr::get($validated, 'permissions', []);
$this->projectPermissionService->syncUserPermissions($project, $user, $permissions);
$this->permissionService->sync($user, $project, $permissions);
return to_route('projects.access', $project)
->with('status', "{$user->name} permissions were updated.");
@@ -144,14 +151,14 @@ class ProjectController extends Controller
Team $team,
): RedirectResponse {
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
$validated = $request->validated();
$permissions = Arr::get($validated, 'permissions', []);
$this->projectPermissionService->syncTeamPermissions($project, $team, $permissions);
$this->permissionService->sync($team, $project, $permissions);
return to_route('projects.access', $project)
->with('status', "{$team->name} permissions were updated.");
@@ -4,7 +4,8 @@ namespace App\Http\Controllers;
use App\Actions\Git\Data\GitRepository;
use App\Actions\Git\GitProviderManager;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Enums\Permission;
use App\Http\Requests\StoreRepositoryConnectionRequest;
use App\Http\Requests\UpdateRepositoryConnectionRequest;
use App\Models\Project;
@@ -24,14 +25,14 @@ class ProjectRepositorySettingsController extends Controller
{
public function __construct(
private readonly GitProviderManager $gitProviderManager,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function show(Request $request, Project $project): Response
{
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($user, $project),
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
@@ -66,7 +67,7 @@ class ProjectRepositorySettingsController extends Controller
): RedirectResponse {
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($user, $project),
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
@@ -96,7 +97,7 @@ class ProjectRepositorySettingsController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($user, $project),
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
@@ -128,7 +129,7 @@ class ProjectRepositorySettingsController extends Controller
RepositoryConnection $repositoryConnection,
): RedirectResponse {
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
@@ -5,7 +5,8 @@ namespace App\Http\Controllers;
use App\Actions\Git\Data\GitBranch;
use App\Actions\Git\GitProviderManager;
use App\Actions\Git\ValidateGitBranchName;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Enums\Permission;
use App\Http\Requests\StoreProjectTicketBranchRequest;
use App\Http\Requests\ValidateProjectTicketBranchRequest;
use App\Models\Project;
@@ -23,7 +24,7 @@ class ProjectTicketBranchController extends Controller
public function __construct(
private readonly GitProviderManager $gitProviderManager,
private readonly ValidateGitBranchName $validateGitBranchName,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function validate(
@@ -34,7 +35,7 @@ class ProjectTicketBranchController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'edit'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -110,7 +111,7 @@ class ProjectTicketBranchController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'edit'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -5,8 +5,9 @@ namespace App\Http\Controllers;
use App\Actions\Git\BuildSuggestedTicketBranchName;
use App\Actions\Git\ResolveTicketBranchMatches;
use App\Actions\Git\ResolveTicketPullRequestMatches;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Actions\Tickets\UpdateProjectTicket;
use App\Enums\Permission;
use App\Http\Requests\UpdateProjectTicketRequest;
use App\Models\Project;
use App\Models\Ticket;
@@ -22,7 +23,7 @@ class ProjectTicketController extends Controller
{
public function __construct(
private readonly BuildSuggestedTicketBranchName $buildSuggestedTicketBranchName,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
private readonly ResolveTicketBranchMatches $resolveTicketBranchMatches,
private readonly ResolveTicketPullRequestMatches $resolveTicketPullRequestMatches,
private readonly UpdateProjectTicket $updateProjectTicket,
@@ -33,14 +34,14 @@ class ProjectTicketController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userCanViewProject($user, $project),
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
$excludedParentIds = [$ticket->id, ...$this->descendantIds($ticket)];
return Inertia::render('projects/Ticket', [
'canEditTicket' => $this->projectPermissionService->userHasPermission($user, $project, 'edit'),
'canEditTicket' => $this->permissionService->allows($user, Permission::ProjectWrite, $project),
'childTicketTree' => $this->buildChildTicketTree($ticket),
'linkedTickets' => $this->linkedTickets($ticket),
'parentTicketOptions' => Ticket::query()
@@ -103,7 +104,7 @@ class ProjectTicketController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'edit'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Actions\Tickets\CreateTicketWorklogEntry;
use App\Enums\Permission;
use App\Http\Requests\StoreTicketLinkRequest;
use App\Models\Project;
use App\Models\Ticket;
@@ -17,7 +18,7 @@ class ProjectTicketLinkController extends Controller
{
public function __construct(
private readonly CreateTicketWorklogEntry $createTicketWorklogEntry,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function store(
@@ -28,7 +29,7 @@ class ProjectTicketLinkController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'edit'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -93,7 +94,7 @@ class ProjectTicketLinkController extends Controller
$user = $request->user();
abort_unless(
$this->projectPermissionService->userHasPermission($user, $project, 'edit'),
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
use App\Enums\Permission;
use App\Http\Requests\StoreProjectNamedSettingRequest;
use App\Http\Requests\StoreTicketFieldDefinitionRequest;
use App\Http\Requests\UpdateProjectNamedSettingRequest;
@@ -26,7 +27,7 @@ class ProjectTicketMetadataSettingsController extends Controller
{
public function __construct(
private readonly InitializeProjectTicketMetadata $initializeProjectTicketMetadata,
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function show(Request $request, Project $project): Response
@@ -355,7 +356,7 @@ class ProjectTicketMetadataSettingsController extends Controller
private function abortUnlessCanManage(Request $request, Project $project): void
{
abort_unless(
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
}
+56 -11
View File
@@ -2,12 +2,15 @@
namespace App\Http\Controllers;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Actions\Permissions\PermissionService;
use App\Enums\Permission;
use App\Http\Requests\StoreTeamRequest;
use App\Http\Requests\SyncTeamUserPermissionsRequest;
use App\Http\Requests\UpdateTeamMembersRequest;
use App\Models\Team;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Inertia\Inertia;
use Inertia\Response;
@@ -15,17 +18,36 @@ use Inertia\Response;
class TeamController extends Controller
{
public function __construct(
private readonly ProjectPermissionService $projectPermissionService,
private readonly PermissionService $permissionService,
) {}
public function index(): Response
public function index(Request $request): Response
{
$user = $request->user();
$canCreateTeams = $this->permissionService->allows($user, Permission::GlobalAdmin);
$manageableTeamIds = $this->permissionService->manageableTeamIds($user);
abort_unless($canCreateTeams || $manageableTeamIds !== [], 403);
$teams = Team::query()
->with('users')
->when(
! $canCreateTeams,
fn ($query) => $query->whereIn('id', $manageableTeamIds),
)
->orderBy('name')
->get();
return Inertia::render('teams/Index', [
'canCreateTeams' => $canCreateTeams,
'permissionOptions' => $this->permissionService->permissionOptions(User::class, Team::class),
'status' => session('status'),
'teams' => Team::query()
->with('users')
->orderBy('name')
->get()
'teamPermissions' => $teams
->mapWithKeys(fn (Team $team): array => [
$team->id => $this->permissionService->assignmentMap(User::class, $team),
])
->all(),
'teams' => $teams
->map(fn (Team $team): array => [
'id' => $team->id,
'name' => $team->name,
@@ -44,19 +66,42 @@ class TeamController extends Controller
{
$team = Team::query()->create($request->validated());
return to_route('admin.teams.index')
return to_route('teams.index')
->with('status', "{$team->name} was created.");
}
public function updateMembers(UpdateTeamMembersRequest $request, Team $team): RedirectResponse
{
$validated = $request->validated();
abort_unless(
$this->permissionService->allows($request->user(), Permission::TeamManage, $team),
403,
);
$validated = $request->validated();
$userIds = Arr::get($validated, 'user_ids', []);
$this->projectPermissionService->syncTeamMembers($team, $userIds);
$team->users()->sync($userIds);
return to_route('admin.teams.index')
return to_route('teams.index')
->with('status', "{$team->name} members were updated.");
}
public function updateUserPermissions(
SyncTeamUserPermissionsRequest $request,
Team $team,
User $user,
): RedirectResponse {
abort_unless(
$this->permissionService->allows($request->user(), Permission::TeamManage, $team),
403,
);
$validated = $request->validated();
$permissions = Arr::get($validated, 'permissions', []);
$this->permissionService->sync($user, $team, $permissions);
return to_route('teams.index')
->with('status', "{$user->name} permissions on {$team->name} were updated.");
}
}